diff --git a/packages/agents/content/skills/design-and-plan/SKILL.md b/packages/agents/content/skills/design-and-plan/SKILL.md index 4c610171..b5ef0e0e 100644 --- a/packages/agents/content/skills/design-and-plan/SKILL.md +++ b/packages/agents/content/skills/design-and-plan/SKILL.md @@ -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 @@ -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. @@ -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 @@ -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":"","kind":"ticket"}` and `{"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: @@ -193,3 +200,5 @@ Follow the options, output format, and recommendation rules in [next-steps optio + + diff --git a/packages/agents/content/skills/merge-pr/SKILL.md b/packages/agents/content/skills/merge-pr/SKILL.md index 76fc7db6..74f676f3 100644 --- a/packages/agents/content/skills/merge-pr/SKILL.md +++ b/packages/agents/content/skills/merge-pr/SKILL.md @@ -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 @@ -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 @@ -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. @@ -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}: @@ -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. @@ -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: "}` 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). @@ -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. + + diff --git a/packages/agents/content/skills/orchestrate/SKILL.md b/packages/agents/content/skills/orchestrate/SKILL.md index dbcc93cf..e0e0ae41 100644 --- a/packages/agents/content/skills/orchestrate/SKILL.md +++ b/packages/agents/content/skills/orchestrate/SKILL.md @@ -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 @@ -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`. @@ -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): @@ -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). @@ -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":"","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: @@ -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":""}`, 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: ``` @@ -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. @@ -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. + + diff --git a/packages/agents/content/skills/plan/SKILL.md b/packages/agents/content/skills/plan/SKILL.md index 9791dc7e..52e3c713 100644 --- a/packages/agents/content/skills/plan/SKILL.md +++ b/packages/agents/content/skills/plan/SKILL.md @@ -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 @@ -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. @@ -77,12 +80,16 @@ Example: `20260223-143000Z_migrate-auth-to-oauth2_plan.md` ## Completion +Once the plan is saved, emit `artifact.written` (payload `{"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). + 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. @@ -90,3 +97,5 @@ Follow the options, output format, and recommendation rules in [next-steps optio + + diff --git a/packages/agents/content/skills/refine-plan/SKILL.md b/packages/agents/content/skills/refine-plan/SKILL.md index e2492342..5ccaec9e 100644 --- a/packages/agents/content/skills/refine-plan/SKILL.md +++ b/packages/agents/content/skills/refine-plan/SKILL.md @@ -3,6 +3,8 @@ name: refine-plan description: Review and refine an implementation plan for completeness and correctness user-invocable: true dependencies: + skills: + - emit-event subagents: - plan-reviewer - plan-reviser @@ -37,6 +39,7 @@ Before every {tool:Task} call and after every phase completion, output a status 4. Invoke `node {harness_home_dir}/skills/derive-session-context/derive-session-context.mjs` via Bash to obtain `ticket_id`, `project_slug`, and `artifact_base_dir` from the manifest JSON emitted on stdout. 5. Resolve artifact directory: `{artifact_base_dir}/projects/{project_slug}/tickets/{ticket_id}/` 6. `mkdir -p {artifact_dir}` +7. Emit `skill.started` (payload `{"skill":"refine-plan"}`) per [Lifecycle events](#lifecycle-events). ### 2. Detect plan format @@ -79,7 +82,7 @@ Parse the return block: Evaluate the finding counts: -- **0 total findings** (AutoResolvable = 0 AND UserQuestions = 0): Skip the reviser entirely. Report that the plan needs no refinement, then present next steps. +- **0 total findings** (AutoResolvable = 0 AND UserQuestions = 0): Skip the reviser entirely. Emit `skill.completed` (payload `{"outcome":"no findings"}`) per [Lifecycle events](#lifecycle-events). Report that the plan needs no refinement, then present next steps, emitting `input.requested` (payload `{"prompt":"next-steps"}`) as you present the menu. ``` Plan reviewed -- no findings. The plan is ready for implementation. @@ -92,7 +95,7 @@ Evaluate the finding counts: - **0 user questions** (UserQuestions = 0, AutoResolvable > 0): Skip user interaction. Proceed to step 5 with empty user answers. -- **User questions present** (UserQuestions > 0): Read the review artifact. Extract all findings from the "Decision gaps" section (these may be C or X findings -- the section is organized by resolution type, not finding category). Present each finding's question using the finding's ID (e.g., `C1`, `X2`) as the question identifier. When asking option-style questions, follow [option format](#option-format). (Reinforces the rule in `AGENTS.md` — intentional redundancy.) +- **User questions present** (UserQuestions > 0): Emit `input.requested` (payload `{"prompt":"plan-review questions"}`) per [Lifecycle events](#lifecycle-events) before presenting the questions. Read the review artifact. Extract all findings from the "Decision gaps" section (these may be C or X findings -- the section is organized by resolution type, not finding category). Present each finding's question using the finding's ID (e.g., `C1`, `X2`) as the question identifier. When asking option-style questions, follow [option format](#option-format). (Reinforces the rule in `AGENTS.md` — intentional redundancy.) ``` The plan review identified {UserQuestions} question(s) that need your input: @@ -151,7 +154,7 @@ Plan revision failed -- the plan-reviser did not complete successfully. Review: {review_output_path} ``` -Stop here. Do not attempt provenance update or report completion. +Emit `skill.completed` (payload `{"outcome":"stopped: revision failed"}`) per [Lifecycle events](#lifecycle-events). Stop here. Do not attempt provenance update or report completion. Stamp the revised plan with frontmatter conforming to the [universal artifact frontmatter](../_data/artifact-conventions.md#universal-artifact-frontmatter) schema plus the [plan provenance](../_data/artifact-conventions.md#plan-provenance) extensions. This is the single write point for the revised plan's frontmatter — `plan-reviser` outputs no frontmatter of its own; `refine-plan` owns it. @@ -227,9 +230,11 @@ The `provenance:` block is **not** populated from the script. Construct it manua --- ``` +Once the revised plan is written, emit `artifact.written` (payload `{"path":"","kind":"plan"}`) per [Lifecycle events](#lifecycle-events), then emit `skill.completed` (payload `{"outcome":"plan-refined"}`) on the same turn. Emitting completion at the save point folds an abandoned session to a finished state. + ### 6. Offer ticket update if approach diverged -Compare the revised plan's approach/solution with the source ticket's solution section. If they materially diverge, offer to update the ticket to match the revised plan. If the approaches haven't diverged, skip this step silently. +Compare the revised plan's approach/solution with the source ticket's solution section. If they materially diverge, emit `input.requested` (payload `{"prompt":"ticket-update"}`) per [Lifecycle events](#lifecycle-events), then offer to update the ticket to match the revised plan. If the approaches haven't diverged, skip this step silently. **Material divergence** means a different technical approach (e.g., build-time flag changed to runtime detection) or changed scope boundaries (features added or removed). **Non-divergence** means refined details within the same approach (e.g., different function names, reordered steps). @@ -246,6 +251,8 @@ Plan refined: Revised: {revision_output_path} ``` +As you present the next-steps menu, emit `input.requested` (payload `{"prompt":"next-steps"}`) per [Lifecycle events](#lifecycle-events). + Follow the options, output format, and recommendation rules in [next-steps options](#next-steps-options) exactly. Do not improvise the options. The plan was just reviewed. If the review surfaced significant scope changes or unresolved questions that led to a dramatic revision, the plan may warrant another refinement round; otherwise, either orchestration or implementation may apply depending on whether the work's consequences fit a single review pass. Use this as recommendation context. Include both `{revision_output_path}` (as the plan path) and `{ticket_source}` in each skill-invoking option line. @@ -264,3 +271,5 @@ Follow the options, output format, and recommendation rules in [next-steps optio + +