Skip to content
Merged
15 changes: 12 additions & 3 deletions packages/agents/content/skills/design-and-plan/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
name: design-and-plan
description: Interactive design exploration followed by ticket refinement and implementation planning
user-invocable: true
dependencies:
skills:
- emit-event
---

# Design and plan
Expand All @@ -26,9 +29,11 @@ Do NOT generate the implementation plan until the design has been agreed upon an

## Process

**Narrate every ask.** Throughout the phases below, before presenting any ask to the user — a clarifying question, a design or ticket or plan approval, a drift-verdict decision, the closing next-steps menu — emit `input.requested` with a short label of the ask (e.g. payload `{"prompt":"tier selection"}`) per [Lifecycle events](#lifecycle-events), so a watching surface shows which question this session is waiting on. Compose the label fresh for each ask and keep it to a few words. Emit before presenting, in the same turn — never at the end of the turn.

### Phase 1: Resolve task source and assess relevancy

1. **Resolve the task source** using the [ticket source resolution](../_data/ticket-source-resolution.md) table. Request the `updatedAt` field for use in the relevancy check. Store the resolved metadata for use in the relevancy check and Phase 6's optional remote update. When the source resolves to a URL, persist it to the branch manifest per [Stored ticket URL](../_data/ticket-source-resolution.md#stored-ticket-url) so a later session needs no ticket argument.
1. **Resolve the task source** using the [ticket source resolution](../_data/ticket-source-resolution.md) table. Request the `updatedAt` field for use in the relevancy check. Store the resolved metadata for use in the relevancy check and Phase 6's optional remote update. When the source resolves to a URL, persist it to the branch manifest per [Stored ticket URL](../_data/ticket-source-resolution.md#stored-ticket-url) so a later session needs no ticket argument. Once the source is resolved, emit `skill.started` (payload `{"skill":"design-and-plan"}`) per [Lifecycle events](#lifecycle-events).

2. **Assess relevancy** — determine whether the ticket may be stale and, if so, verify it is still relevant.

Expand All @@ -54,7 +59,7 @@ Invoke the `{skill:assess-ticket}` skill with the resolved ticket source and mod
**After the check** — interpret the drift verdict:

- 🟢 `none`: Continue silently into Phase 2.
- 🟠 `partial` or 🔴 `severe`: Present the assessment findings to the user. Ask whether to proceed as-is, adjust the scope, or stop.
- 🟠 `partial` or 🔴 `severe`: Present the assessment findings to the user. Ask whether to proceed as-is, adjust the scope, or stop. If the user stops, emit `skill.completed` (payload `{"outcome":"stopped: ticket drift"}`) per [Lifecycle events](#lifecycle-events).

### Phase 2: Understand the task

Expand Down Expand Up @@ -145,7 +150,9 @@ Then save both artifacts:

Prepend the resolved frontmatter to each artifact content before writing.

4. Report paths and present next steps.
Once both artifacts are saved, emit `artifact.written` for each (payloads `{"path":"<ticket path>","kind":"ticket"}` and `{"path":"<plan path>","kind":"plan"}`) per [Lifecycle events](#lifecycle-events), then emit `skill.completed` (payload `{"outcome":"designed-and-planned"}`) on the same turn, before the next-steps prompt below. Emitting completion at the save point folds an abandoned session to a finished state.

4. Report paths and present next steps (the next-steps menu is an ask — the standing rule above applies, with payload `{"prompt":"next-steps"}`).

```
Design and plan complete:
Expand Down Expand Up @@ -193,3 +200,5 @@ Follow the options, output format, and recommendation rules in [next-steps optio
<!-- include: ../_partials/next-steps-after-plan.md / -->

<!-- include: ../_partials/option-format.md / -->

<!-- include: ../_partials/lifecycle-events.md / -->
15 changes: 11 additions & 4 deletions packages/agents/content/skills/merge-pr/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
name: merge-pr
description: Merge a pull request by composing a merge-commit message, validating PR state, and delegating to the platform's merge API
user-invocable: true
dependencies:
skills:
- emit-event
---

# Merge pull request
Expand All @@ -26,7 +29,7 @@ Merge a pull request on the appropriate platform. Composes the merge-commit titl

### 1. Get session context

Invoke `node {harness_home_dir}/skills/derive-session-context/derive-session-context.mjs` via Bash. The bundle emits the session-context manifest JSON to stdout; extract `ticket_ref`, `branch_name`, `default_branch`, `scm`, `project_slug`, `ticket_id`, `artifact_base_dir`, and `pr_url` from it.
Invoke `node {harness_home_dir}/skills/derive-session-context/derive-session-context.mjs` via Bash. The bundle emits the session-context manifest JSON to stdout; extract `ticket_ref`, `branch_name`, `default_branch`, `scm`, `project_slug`, `ticket_id`, `artifact_base_dir`, and `pr_url` from it. Then emit `skill.started` (payload `{"skill":"merge-pr"}`) per [Lifecycle events](#lifecycle-events).

### 2. Resolve the PR

Expand All @@ -38,7 +41,7 @@ Read the PR's metadata for the steps below:
gh pr view {pr} --json number,title,body,labels,headRefName,baseRefName,url
```

If no PR can be resolved or discovered, stop with: "No open PR found for branch `{branch_name}`. Create one with `{skill:create-pr}` first."
If no PR can be resolved or discovered, emit `skill.completed` (payload `{"outcome":"stopped: no PR"}`) per [Lifecycle events](#lifecycle-events), then stop with: "No open PR found for branch `{branch_name}`. Create one with `{skill:create-pr}` first."

Capture `title` (PR title), `body` (PR body), `labels` (label objects), `number`, and `headRefName` (head branch) from the response. These feed the steps below.

Expand Down Expand Up @@ -131,7 +134,7 @@ If `scope.status` or `type.status` from step 3 is `ambiguous`, ask one question
- When asking option-style questions, follow [option format](#option-format). (Reinforces the rule in `AGENTS.md` — intentional redundancy.)
- After the user resolves each ambiguous dimension, re-render the title (step 5) with the now-concrete values.

Then render the proposed merge to the user:
Emit `input.requested` (payload `{"prompt":"merge-approval"}`) per [Lifecycle events](#lifecycle-events), then render the proposed merge to the user:

```
Proposed merge for PR #{pr_number}:
Expand All @@ -155,7 +158,7 @@ Render `{confirmation}` so the ask itself names every destructive side effect th
- `remote` → `Merge PR #{pr_number} and delete the remote branch {headRefName}? 👍🏼👎🏼`
- `both` → `Merge PR #{pr_number} and delete the local and remote branch {headRefName}? 👍🏼👎🏼`

If the user declines, stop with no API call and no artifact. If they approve, continue.
If the user declines, emit `skill.completed` (payload `{"outcome":"stopped: declined"}`) per [Lifecycle events](#lifecycle-events), then stop with no API call and no artifact. If they approve, continue.

<!-- include: ../_partials/action-items.md / -->

Expand Down Expand Up @@ -184,6 +187,8 @@ Pass the following inputs to the selected delegate per the delegate interface:

The orchestrator never passes ambiguous-status dimensions or `prompt` sentinels to the delegate — all values are concrete by this point.

After the delegate returns, emit `skill.completed` (payload `{"outcome":"merged"}`, or `{"outcome":"stopped: <reason>"}` when the delegate stopped or failed) per [Lifecycle events](#lifecycle-events).

## Important

- The orchestrator owns all decisions (PR resolution, scope/type/strategy/deletion-strategy resolution, body composition, approval gate). Delegates own only execution (platform API calls + state validation).
Expand All @@ -192,3 +197,5 @@ The orchestrator never passes ambiguous-status dimensions or `prompt` sentinels
- Never list automated checks (formatting, linting, typechecking, unit tests) in the merge body. They run automatically in CI.

<!-- include: ../_partials/option-format.md / -->

<!-- include: ../_partials/lifecycle-events.md / -->
18 changes: 12 additions & 6 deletions packages/agents/content/skills/orchestrate/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ name: orchestrate
description: Pipeline execution engine for multi-phase development workflows using specialized subagents
user-invocable: false
dependencies:
skills:
- emit-event
subagents:
- aspect-code-reviewer
- aspect-silent-failure-reviewer
Expand Down Expand Up @@ -163,7 +165,7 @@ Prefix the status line with a colored emoji for visual distinction:

## Run initialization

1. **Get context**: Invoke `node {harness_home_dir}/skills/derive-session-context/derive-session-context.mjs` via Bash. The bundle emits the session-context manifest JSON to stdout; extract `project_slug`, `ticket_id`, `default_branch`, and `artifact_base_dir` from it. Resolve the diff base: Use `--diff-base` if provided, otherwise use `default_branch` from the manifest. Then compute the merge-base SHA once: run `git merge-base HEAD {diff-base}` and store the result as `{merge-base-sha}` -- this concrete SHA is what you pass to all downstream agents. The ticket ID is optional -- if unavailable, `init_run` will auto-generate one.
1. **Get context**: Invoke `node {harness_home_dir}/skills/derive-session-context/derive-session-context.mjs` via Bash. The bundle emits the session-context manifest JSON to stdout; extract `project_slug`, `ticket_id`, `default_branch`, and `artifact_base_dir` from it. Resolve the diff base: Use `--diff-base` if provided, otherwise use `default_branch` from the manifest. Then compute the merge-base SHA once: run `git merge-base HEAD {diff-base}` and store the result as `{merge-base-sha}` -- this concrete SHA is what you pass to all downstream agents. The ticket ID is optional -- if unavailable, `init_run` will auto-generate one. Then emit `skill.started` (payload `{"skill":"orchestrate-dev"}` or `{"skill":"orchestrate-review"}`, naming the wrapper skill that invoked this engine) per [Lifecycle events](#lifecycle-events).
2. **Read ticket** (if available): If the ticket ID resolves to a GitHub issue, read it via `gh issue view {number}` and store the content as `{ticket-content}`. If the read fails (not a GitHub issue, CLI unavailable), continue without ticket content.
3. **Detect external plan and evaluate trust**: Determine whether the task description contains or references an **external plan** — step-by-step implementation instructions with specific file paths or code changes. If it does, set `{externalPlan}` to `true` and extract the plan content. Otherwise, set `{externalPlan}` to `false` and set `{planTrust}` to `null`.

Expand Down Expand Up @@ -238,8 +240,8 @@ Prefix the status line with a colored emoji for visual distinction:
Only write the breadcrumb after a successful `init_run` (MCP available). Do not write it on the MCP-unavailable fallback path, where no run directory is created and there is no `run_id` to resolve.

**Failure — MCP unavailable** (tool not found / server not connected): Resolve `mcp_policy` (see "Resolving MCP policy" above) and apply the policy:
- `required`: Abort with a clear message explaining that MCP is unavailable and the policy requires it.
- `prompt`: Ask the developer: "MCP server is unavailable — no run-index.json, run-log.jsonl, or Factory visualization will be produced. Continue without MCP tracking? (yes / no)". Abort if the developer declines; continue on confirmation.
- `required`: Emit `skill.completed` (payload `{"outcome":"stopped: MCP unavailable"}`) per [Lifecycle events](#lifecycle-events), then abort with a clear message explaining that MCP is unavailable and the policy requires it.
- `prompt`: Emit `input.requested` (payload `{"prompt":"continue without MCP"}`) per [Lifecycle events](#lifecycle-events), then ask the developer: "MCP server is unavailable — no run-index.json, run-log.jsonl, or Factory visualization will be produced. Continue without MCP tracking? (yes / no)". Abort if the developer declines, emitting `skill.completed` (payload `{"outcome":"stopped: declined"}`); continue on confirmation.
- `optional`: Print one-line notice "MCP unavailable — continuing without tracking" and proceed.

**Fallback local context generation** (when policy permits continuing without MCP):
Expand Down Expand Up @@ -331,7 +333,7 @@ Store the full path as `{run-manifest-path}`; increment `{seq}`.

### MCP call policy

When `{mcp-available}` is `false`, skip ALL `emit_event`, `register_artifact`, and `complete_run` calls silently. No per-call-site guards are needed — this one policy governs every call site in this file and in loaded modules.
When `{mcp-available}` is `false`, skip ALL `emit_event`, `register_artifact`, and `complete_run` calls silently. No per-call-site guards are needed — this one policy governs every call site in this file and in loaded modules. It governs MCP tool calls only: lifecycle emissions per [Lifecycle events](#lifecycle-events) are a separate channel (a Bash helper, not an MCP tool) and run regardless of `{mcp-available}`.

`get_run_state` retains its existing conversation-tracked fallback (see "Error handling" and `review-cycle.md` fallback policy note).

Expand Down Expand Up @@ -726,7 +728,7 @@ Run `{harness_home_dir}/scripts/resolve-frontmatter.sh --skill orchestrate --int

The orchestrator's `provenance.model` is omitted — the run-summary aggregates work from many subagents, each with its own model recorded in its own artifact. The summary itself is composed by the orchestrator and is not a single-model artifact.

After writing the artifact, call `register_artifact` for the run-summary artifact. Present the same summary to the user in the conversation. The conversational output should match the artifact content — do not abbreviate or omit sections.
After writing the artifact, emit `artifact.written` (payload `{"path":"<run-summary path>","kind":"run-summary"}`) per [Lifecycle events](#lifecycle-events) and call `register_artifact` for the run-summary artifact. Present the same summary to the user in the conversation. The conversational output should match the artifact content — do not abbreviate or omit sections.

After the savings-analyzer {tool:Task} completes (it runs concurrently and will finish while or after the run-summary is written), call `register_artifact` with:

Expand All @@ -742,6 +744,8 @@ phase: summary

Call MCP tool `complete_run` with `{ runDir: {run-dir}, status: "completed" | "failed" | "needs_manual_review", reason?: string }`. When `status` is `"failed"`, this emits a `run_failed` event (the optional `reason` field is included if provided); otherwise it emits a `run_completed` event. Either way, `completedAt` is stamped on the run-index.json header.

Then emit `skill.completed` (payload `{"outcome":"<completed|failed|needs_manual_review>"}`, matching the run status) per [Lifecycle events](#lifecycle-events) — on the MCP-unavailable path too, where `complete_run` itself is skipped.

**Clean up breadcrumb** (MCP success path only): After `complete_run`, remove the breadcrumb file:

```
Expand All @@ -750,7 +754,7 @@ rm -f .claude/tmp/active-run-dir

## Phase 6: Wrap-up (prompted, conditional)

After the summary is presented and `complete_run` has been called, check whether the run-summary contains a non-empty `## Deferred items` or `## Insights` section. If either section is present and non-empty, invoke `{skill:wrap-up}` to offer post-run housekeeping.
After the summary is presented and `complete_run` has been called, check whether the run-summary contains a non-empty `## Deferred items` or `## Insights` section. If either section is present and non-empty, invoke `{skill:wrap-up}` to offer post-run housekeeping, emitting `input.requested` (payload `{"prompt":"wrap-up"}`) per [Lifecycle events](#lifecycle-events) as the offer is presented.

Like Phase 5, this is an inherent engine responsibility — not a pipeline phase. It does not get `phase_decision` or `phase_started`/`phase_completed` events.

Expand Down Expand Up @@ -814,3 +818,5 @@ Subagents include a structured return block at the end of their {tool:Task} resp
- Keep context lean: Only pass relevant information downstream
- All orchestration artifacts go in the artifact directory
- **Prefer exhausting iteration budget over escaping findings.** A defect that escapes to remote review costs an order of magnitude more in developer time than an additional local review cycle. Agent compute is cheap; context-switching and manual rework are not. When findings exist and review rounds remain, fix and re-review.

<!-- include: ../_partials/lifecycle-events.md / -->
11 changes: 10 additions & 1 deletion packages/agents/content/skills/plan/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
name: plan
description: Create an implementation plan from a ticket or task description
user-invocable: true
dependencies:
skills:
- emit-event
---

# Plan
Expand All @@ -15,7 +18,7 @@ Create an implementation plan from a ticket or task. `plan` is the standalone pl

## Resolve the task source

Resolve the task source using the [ticket source resolution](../_data/ticket-source-resolution.md) table, then read the resolved ticket or description and plan against it. When the source resolves to a URL, persist it to the branch manifest per [Stored ticket URL](../_data/ticket-source-resolution.md#stored-ticket-url) so a later session needs no ticket argument. `plan` does not run the staleness check or interactive design Q&A; that ceremony belongs to `design-and-plan`. When the source is a free-form description rather than a ticket, plan directly from the description.
Resolve the task source using the [ticket source resolution](../_data/ticket-source-resolution.md) table, then read the resolved ticket or description and plan against it. When the source resolves to a URL, persist it to the branch manifest per [Stored ticket URL](../_data/ticket-source-resolution.md#stored-ticket-url) so a later session needs no ticket argument. `plan` does not run the staleness check or interactive design Q&A; that ceremony belongs to `design-and-plan`. When the source is a free-form description rather than a ticket, plan directly from the description. Once the source is resolved, emit `skill.started` (payload `{"skill":"plan"}`) per [Lifecycle events](#lifecycle-events).

When the resolved source is a local artifact, read its `provenance.skill`: `design-and-plan` means an interactive design phase ran; another skill means the ticket was authored without one. Remote issues and free-form descriptions carry no provenance.

Expand Down Expand Up @@ -77,16 +80,22 @@ Example: `20260223-143000Z_migrate-auth-to-oauth2_plan.md`

## Completion

Once the plan is saved, emit `artifact.written` (payload `{"path":"<path>","kind":"plan"}`) per [Lifecycle events](#lifecycle-events), then emit `skill.completed` (payload `{"outcome":"plan-saved"}`) on the same turn, before the next-steps prompt below. Emitting completion at the save point folds an abandoned session to a finished state.

Report the file path when done.

```
Plan saved: {plan_path}
```

As you present the next-steps menu, emit `input.requested` (payload `{"prompt":"next-steps"}`) per [Lifecycle events](#lifecycle-events).

<HARD-GATE>
Follow the options, output format, and recommendation rules in [next-steps options](#next-steps-options) exactly. Do not improvise the options. For recommendation context, supply the source's design provenance from the resolve step — `plan` adds no interactive design phase of its own. Include both `{plan_path}` and `{ticket_source}` in each skill-invoking option line; omit the ticket path when the source was a free-form description rather than a ticket.
</HARD-GATE>

<!-- include: ../_partials/next-steps-after-plan.md / -->

<!-- include: ../_partials/option-format.md / -->

<!-- include: ../_partials/lifecycle-events.md / -->
Loading
Loading