# Ticket Work Orchestration

You are executing contract-driven ticket work. This prompt defines the complete orchestration logic.

## Autonomous Mode

If `--autonomous` was passed, gate behavior changes across all phases. Before running any phase
handler, check whether autonomous mode is active. In autonomous mode:

- Hard keyboard gates are replaced by Slack soft-gates or auto-advance (see SKILL.md for the
  full gate behavior table).
- The phase handlers below describe **interactive mode** (default) gate behavior.
- For each phase exit, substitute the autonomous-mode behavior from SKILL.md when `--autonomous`
  is active.

## Initialization

When `/ticket-work {ticket_id} [--autonomous]` is invoked:

1. **Fetch the ticket:**
   ```
   mcp__linear-server__get_issue(id="{ticket_id}")
   ```

2. **Parse the contract** from the ticket description:
   - Look for `## Contract` section followed by a YAML code block
   - If found: parse and validate the YAML
   - If not found: create initial contract (phase: intake)

3. **Announce current state:**
   ```
   Resuming {ticket_id} in {phase} phase.

   Pending items:
   - {list pending questions if any}
   - {list blocking verification if any}
   - {list pending gates if any}
   ```

4. **Guide based on current phase** (see Phase Handlers below)

---

## Contract Schema

```yaml
# Identity (set at intake)
ticket_id: "OMN-1234"
title: "Feature title"
repo: "omnibase_core"
branch: null  # set during implementation

# State
phase: intake  # intake|research|questions|spec|implementation|review|done
created_at: "2026-02-01T12:00:00Z"
updated_at: "2026-02-01T12:00:00Z"

# Research context (populated during research phase)
context:
  relevant_files: []
  patterns_found: []
  notes: ""

# Clarification (populated during questions phase)
questions:
  - id: "q1"
    text: "What authentication method should we use?"
    category: "architecture"  # architecture|behavior|integration|scope
    required: true
    answer: null
    answered_at: null

# Specification (populated during spec phase)
requirements:
  - id: "r1"
    statement: "System shall authenticate users via OAuth2"
    rationale: "Matches existing auth patterns"
    acceptance:
      - "OAuth2 flow implemented"
      - "Token refresh works"

verification:
  - id: "v1"
    title: "Unit tests pass"
    kind: "unit_tests"  # unit_tests|lint|mypy|integration|manual_check|script
    command: "uv run pytest tests/"
    expected: "exit 0"
    blocking: true
    status: "pending"  # pending|passed|failed|skipped
    evidence: null
    executed_at: null

gates:
  - id: "g1"
    title: "Human approval"
    kind: "human_approval"  # human_approval|policy_check|security_check
    required: true
    status: "pending"  # pending|approved|rejected|skipped
    notes: null
    resolved_at: null

# Completion tracking
commits: []
pr_url: null
hardening_tickets: []
```

---

## Phase Handlers

### Phase: intake

**Entry:** Always allowed (initial phase)

**Actions:**
1. Create contract if not exists
2. Set identity fields:
   - `ticket_id`: From Linear ticket identifier
   - `title`: From Linear ticket title
   - `repo`: From current working directory basename (e.g., `/Volumes/PRO-G40/Code/omni_worktrees/OMN-2068/omniclaude` → `omniclaude`)  # local-path-ok
3. Save contract to ticket description
4. **Auto-advance to research phase** (no human gate needed)

**Mutations allowed:** Create contract only

**Exit to research:** Automatic after contract creation (no confirmation needed)

---

### Phase: research

**Entry invariant:** Contract exists

**Actions:**
1. **Load architecture handshake** (if available):
   - Read `.claude/architecture-handshake.md` using the Read tool
   - If file exists: review repo-specific constraints and patterns
   - If file doesn't exist: continue without (graceful fallback)
   - Include relevant constraints in `context.notes`
2. Analyze the ticket requirements
3. Use codebase exploration to identify:
   - Relevant files (`context.relevant_files`)
   - Existing patterns (`context.patterns_found`)
   - Notes about approach (`context.notes`)
4. **Query code graph for relevant entities** *(graceful degradation)*:

   Run the code graph semantic query with the ticket title as search text.
   Always pass `repos` to scope results to the ticket's target repo:
   ```bash
   source ~/.omnibase/.env
   echo '{"mode": "semantic", "query": "{ticket_title}", "repos": ["{ticket_repo}"], "limit": 20}' \
     | python3 "${OMNICLAUDE_PROJECT_ROOT:-$CLAUDE_PLUGIN_ROOT}/plugins/onex/hooks/lib/code_graph_query.py"
   ```

   If the command succeeds and returns `"status": "ok"`:
   - Take only the **top 5** results by relevance_score (do not dump all 20 into context)
   - For each, append to `context.patterns_found`:
     `"{entity_type} {qualified_name} ({source_repo}) — {classification}"`
   - Append to `context.notes`: "Code graph queried for: {ticket_title} — {N} of {total} entities surfaced (top-5 by relevance)"

   If the command fails or returns `"status": "service_unavailable"`:
   - Skip silently. Do not block research phase.
   - Append to `context.notes`: "Code graph unavailable — skipped entity enrichment"

5. Generate clarifying questions (add to `questions[]`)
5. Save contract after each mutation

**Mutations allowed:**
- `context.relevant_files`
- `context.patterns_found`
- `context.notes`
- `questions[]` (append only)

**Research boundaries:**
- MAY populate context
- MAY generate questions
- MAY NOT add requirements
- MAY NOT add verification steps
- MAY NOT answer questions
- MAY NOT advance phase

**Exit to questions:**
- Invariant: `len(context.relevant_files) >= 1`
- User says "questions ready" or similar

---

### Phase: questions

**Entry invariant:** `len(context.relevant_files) >= 1`

**Actions:**
1. Present unanswered questions one at a time using `AskUserQuestion`
2. Record answers in `questions[].answer` and `questions[].answered_at`
3. Save contract after each answer

**Mutations allowed:**
- `questions[].answer`
- `questions[].answered_at`

**Exit to spec:**
- Invariant: All required questions have non-empty answers
- Human gate: User says "requirements clear", "proceed to spec", or confirms via AskUserQuestion:
  ```
  All questions answered. Ready to proceed to spec phase?
  - Yes, requirements are clear
  - No, I have more questions
  ```

---

### Phase: spec

**Entry invariant:** `is_questions_complete()` AND human signal

**Actions:**
1. Generate requirements based on ticket + context + answers
2. Generate verification steps (default: unit_tests, lint, mypy)
3. Generate gates (default: human_approval)
4. Present spec for review
5. Allow edits via user feedback

**Mutations allowed:**
- `requirements[]` (append; edits allowed during spec phase only)
- `verification[]` (append; edits allowed during spec phase only)
- `gates[]` (append; edits allowed during spec phase only)

**Exit to implementation:**
- Invariant: `len(requirements) >= 1` AND `len(verification) >= 1`
- Human gate: User says "approve spec", "build it", or confirms via AskUserQuestion:
  ```
  Spec complete with {N} requirements and {M} verification steps. Ready to implement?
  - Yes, build it
  - No, needs changes
  ```

**🚨 MANDATORY AUTOMATION on spec→implementation transition:**

**BEFORE ANY IMPLEMENTATION WORK BEGINS**, when user approves spec (says "approve spec", "build it", etc.), you MUST execute these steps IN ORDER:

1. **Create git branch** using Linear's suggested branch name:
   - Get branch name from `mcp__linear-server__get_issue(id="{ticket_id}")` response field `branchName`
   - Linear auto-generates this based on ticket ID and title
   - **Check if branch already exists before creating:**
   ```bash
   # Check if branch exists
   if git show-ref --verify --quiet refs/heads/{branchName}; then
       # Branch exists - checkout existing branch
       git checkout {branchName}
       BRANCH_CREATED=false
   else
       # Branch doesn't exist - create new
       git checkout -b {branchName}
       BRANCH_CREATED=true
   fi
   # Example: git checkout -b jonah/omn-1830-m4-hook-integration-session-continuity

   # MANDATORY: pre-commit hooks are not inherited by worktrees.
   # Run this before any commit; without it hooks are silently skipped.
   pre-commit install
   ```
   Track `BRANCH_CREATED` for rollback safety.

2. **Update Linear status** to "In Progress":
   ```
   mcp__linear-server__update_issue(id="{ticket_id}", state="In Progress")
   ```

   > **Note**: This assumes the workspace uses "In Progress" as the active state name.
   > If the update fails with "state not found", query available states with
   > `mcp__linear-server__list_issue_statuses(team="{team_id}")` and use the appropriate state name.

3. **Update contract** with branch name:
   - Set `branch` field to the git branch name
   - Persist to both Linear and local

4. **Announce readiness and dispatch implementation:**
   ```
   Branch created: {branchName}
   Ticket moved to In Progress
   Ready for implementation

   Dispatching {N} requirements to implementation agent...
   ```

   Then immediately dispatch implementation to a separate agent:

   > **Note**: This dispatch block is intentionally duplicated from the implementation phase handler. Both are needed because the spec-to-implementation transition and the phase handler context are different entry points.

   ```
   Task(
     subagent_type="onex:polymorphic-agent",
     description="Implement {ticket_id}: {title}",
     prompt="Implement the following requirements for {ticket_id}: {title}.

       Requirements:
       {requirements_list}

       Relevant files:
       {relevant_files}

       Execute the implementation. Do NOT commit changes (the orchestrator handles git).
       Report: files changed, what was implemented, any issues encountered."
   )
   ```

**⚠️ DO NOT proceed to implementation actions until ALL automation steps complete successfully.**

**Error handling for automation steps:**

If any step fails, follow this rollback sequence:

| Failure Point | Rollback Action |
|---------------|-----------------|
| Git checkout fails | Stop. Do not update Linear or contract. Report error to user. |
| Linear update fails | If `BRANCH_CREATED=true`, checkout previous branch (`git checkout -`) then delete (`git branch -D {branchName}`). Report error to user. |
| Contract persistence fails | Log warning and continue (Linear is source of truth). |

Each step should check for success before proceeding to the next:
```python
# Pseudo-code for safe automation
try:
    # Step 1: Check for existing branch and create/checkout
    branch_exists = run("git show-ref --verify refs/heads/{branchName}").success
    branch_created = False

    if branch_exists:
        git_result = run("git checkout {branchName}")
    else:
        git_result = run("git checkout -b {branchName}")
        branch_created = True

    if git_result.failed:
        raise AutomationError("Git checkout failed", step=1)

    # Step 2: Update Linear
    try:
        mcp__linear_server__update_issue(id=ticket_id, state="In Progress")
    except Exception as e:
        if branch_created:  # Only delete if we created it
            checkout_result = run("git checkout -")  # Return to previous branch first
            if checkout_result.success:
                run("git branch -D {branchName}")
            else:
                log_warning(f"Could not checkout previous branch. Branch '{branchName}' must be deleted manually.")
        raise AutomationError(f"Linear update failed: {e}", step=2)

    # Step 3: Persist contract locally
    try:
        persist_contract_locally(ticket_id, contract)
    except Exception as e:
        # Don't rollback - Linear is source of truth
        log_warning(f"Local persistence failed: {e}")
except AutomationError as e:
    report_to_user(e)
```

---

### Phase: implementation

**Entry invariant:** `is_spec_complete()` AND human signal AND branch created AND Linear status = "In Progress"

**Pre-conditions (set by spec→implementation automation):**
- Git branch exists and is checked out
- Linear ticket status is "In Progress"
- Contract `branch` field is populated

**Actions:**
1. Verify branch is checked out (should already exist from transition automation)
2. **Dispatch implementation to a separate agent:**
   ```
   Task(
     subagent_type="onex:polymorphic-agent",
     description="Implement {ticket_id}: {title}",
     prompt="Implement the following requirements for {ticket_id}: {title}.

       Requirements:
       {requirements_summary}

       Relevant files:
       {context.relevant_files}

       Execute the implementation. Do NOT commit changes (the orchestrator handles git).
       Report: files changed, what was implemented, any issues encountered."
   )
   ```

   This spawns a polymorphic agent with its own context window to implement the requirements.

3. After the implementation agent completes, commit changes (append to `commits[]`)
4. Update `pr_url` if PR created

**Implementation via Task dispatch:**
- Requirements from the contract are passed to a polymorphic agent via Task()
- The agent runs in its own context window (avoids exhausting the orchestrator's context)
- The orchestrator reads the agent's result to determine success/failure
- Quality gates are enforced after the agent completes

**Mutations allowed:**
- `branch`
- `commits[]` (append only)
- `pr_url`

**Exit to review:**
- Invariant: `len(commits) >= 1`
- Human gate: User says "create PR", "ready for review", or confirms via AskUserQuestion:
  ```
  Implementation complete with {N} commits. Ready for review?
  - Yes, run verification
  - No, more changes needed
  ```

---

### Phase: review

**Entry invariant:** `len(commits) >= 1` AND human signal

**Actions:**

1. **Push branch and create PR:**
   ```bash
   git push -u origin {branch}
   # Use heredoc for PR body to safely handle special characters in title
   gh pr create --title "$(cat <<'EOF'
   {ticket_id}: {title}
   EOF
   )" --body "$(cat <<'EOF'
   ...PR body content...
   EOF
   )"
   ```

   **Shell safety**: The heredoc syntax (`<<'EOF'`) with single-quoted delimiter prevents shell expansion of special characters in ticket titles. This protects against command injection if titles contain backticks, dollar signs, or semicolons.

   Update `pr_url` in contract.

2. **Code review loop** (repeat until done):
   ```
   Review code locally → Find issues → Fix issues → Commit → Re-review
   ```

   - Review all changed files for:
     - Logic errors
     - Missing edge cases
     - Inconsistencies with requirements
     - Documentation accuracy
   - Fix issues found (do NOT push between iterations)
   - Re-review locally
   - Continue until:
     - All issues fixed, OR
     - Only nitpicks/minor issues remain (can defer)

3. **Run verification steps:**
   ```bash
   uv run pytest tests/           # unit_tests
   uv run ruff check .            # lint
   uv run mypy src/               # mypy
   ```

4. **Update contract:**
   - `verification[].status` (passed/failed)
   - `verification[].evidence` (command output)
   - `verification[].executed_at`

5. **Return to user** with review summary:
   - Issues found and fixed
   - Remaining minor/nitpick issues (if any)
   - Verification results
   - Ask for approval to proceed

6. If hardening tickets needed, create and add to `hardening_tickets[]`

**Important:** Do NOT merge the PR. That decision belongs to the user.

**Mutations allowed:**
- `commits[]` (append only - from review fixes)
- `verification[].status`
- `verification[].evidence`
- `verification[].executed_at`
- `gates[].status`
- `gates[].notes`
- `gates[].resolved_at`
- `hardening_tickets[]` (append only)

**Exit to done:**
- Invariant: All blocking verification passed/skipped AND all required gates approved
- Human gate: User says "approve merge", "ship it", or confirms via AskUserQuestion:
  ```
  All verification passed and gates approved. Ready to complete?
  - Yes, ship it
  - No, not yet
  ```

---

### Phase: done

**Entry invariant:** `is_verification_complete()` AND `is_gates_complete()` AND human signal

**Actions:**
1. Mark contract as complete
2. Update Linear ticket status
3. Announce completion

**Mutations allowed:** None (contract is immutable in done phase)

---

## Contract Persistence

### Detecting Repo

```python
def get_current_repo() -> str:
    """Extract repo name from current working directory.

    Example: /Volumes/PRO-G40/Code/omni_worktrees/OMN-2068/omniclaude -> omniclaude  # local-path-ok
    """
    import os
    return os.path.basename(os.getcwd())
```

### Reading Contract

```python
def extract_contract(description: str) -> dict | None:
    """Extract contract YAML from ticket description."""
    marker = "## Contract"
    if marker not in description:
        return None

    # Find last occurrence of ## Contract
    idx = description.rfind(marker)
    contract_section = description[idx:]

    # Extract YAML from fenced code block
    import re
    match = re.search(r'```(?:yaml|YAML)?\s*\n(.*?)\n\s*```', contract_section, re.DOTALL)
    if not match:
        return None

    import yaml
    try:
        return yaml.safe_load(match.group(1))
    except yaml.YAMLError:
        return None
```

### Writing Contract

```python
def update_description_with_contract(description: str, contract: dict) -> str:
    """Update ticket description with contract, preserving original content."""
    import yaml
    import re

    marker = "## Contract"
    contract_yaml = yaml.dump(contract, default_flow_style=False, sort_keys=False)
    contract_block = f"\n---\n{marker}\n\n```yaml\n{contract_yaml}```\n"

    if marker in description:
        # Replace existing contract section
        idx = description.rfind(marker)
        # Find --- on its own line immediately before ## Contract
        delimiter_match = re.search(r'\n---\n\s*$', description[:idx])
        if delimiter_match:
            return description[:delimiter_match.start()] + contract_block
        return description[:idx] + contract_block
    else:
        # Append new contract section
        return description.rstrip() + contract_block
```

### Saving to Linear

After any contract mutation:
```
mcp__linear-server__update_issue(
    id="{ticket_id}",
    description="{updated_description}"
)
```

### Local Persistence

After saving to Linear, also persist locally for hook access:

```python
def persist_contract_locally(ticket_id: str, contract: dict) -> None:
    """Persist contract to local filesystem for hook injection.

    Path: $ONEX_STATE_DIR/tickets/{ticket_id}/contract.yaml
    """
    import yaml
    from pathlib import Path

    tickets_dir = Path.home() / ".claude" / "tickets" / ticket_id
    tickets_dir.mkdir(parents=True, exist_ok=True)

    contract_path = tickets_dir / "contract.yaml"

    # Atomic write
    tmp_path = contract_path.with_suffix('.yaml.tmp')
    tmp_path.write_text(yaml.dump(contract, default_flow_style=False, sort_keys=False))
    tmp_path.rename(contract_path)
```

**Error handling:**

Wrap calls to `persist_contract_locally()` in try-except. Failures should log a warning but not block the workflow (Linear is the source of truth):

```python
try:
    persist_contract_locally(ticket_id, contract)
except PermissionError as e:
    print(f"Warning: Cannot write to tickets directory: {e}")
except OSError as e:
    print(f"Warning: Failed to persist contract locally: {e}")
# Continue workflow - Linear has the authoritative copy
```

Call this after every `mcp__linear-server__update_issue()` that modifies the contract.

---

## Verification Commands (v1 Hardcoded)

```python
VERIFICATION_COMMANDS = {
    "unit_tests": "uv run pytest tests/",
    "lint": "uv run ruff check .",
    "mypy": "uv run mypy src/",
    "integration": "uv run pytest tests/integration/",
}
```

For `manual_check` kind: Present description and ask user to confirm pass/fail.
For `script` kind: Use the `command` field from the verification step.

---

## Error Handling

| Error | Behavior |
|-------|----------|
| Linear MCP failure | Fail closed, explain, do not advance |
| YAML parse failure | Fail closed, show raw content, ask user to fix |
| Contract validation failure | Fail closed, show errors, do not persist |
| Verification crash | Mark `failed`, capture error output, do not advance |
| Invariant violation | Refuse transition, explain which invariant failed |

**Never:**
- Silently swallow errors
- Persist invalid contract state
- Advance phase without explicit human signal (except intake→research which is automatic)

---

## Completion Checks

These are the logic for completion checks (mirrors ModelTicketContract from omnibase_core):

```python
def is_questions_complete(contract) -> bool:
    """All required questions have non-empty answers."""
    return all(
        q.get("answer") and q["answer"].strip()
        for q in contract.get("questions", [])
        if q.get("required", True)
    )

def is_spec_complete(contract) -> bool:
    """At least one requirement with acceptance criteria."""
    reqs = contract.get("requirements", [])
    return len(reqs) > 0 and all(
        len(r.get("acceptance", [])) > 0
        for r in reqs
    )

def is_verification_complete(contract) -> bool:
    """All blocking verification passed or skipped."""
    return all(
        v.get("status") in ("passed", "skipped")
        for v in contract.get("verification", [])
        if v.get("blocking", True)
    )

def is_gates_complete(contract) -> bool:
    """All required gates approved."""
    return all(
        g.get("status") == "approved"
        for g in contract.get("gates", [])
        if g.get("required", True)
    )

def is_done(contract) -> bool:
    """Contract is complete."""
    return (
        contract.get("phase") == "done"
        and is_questions_complete(contract)
        and is_spec_complete(contract)
        and is_verification_complete(contract)
        and is_gates_complete(contract)
    )
```

---

## Mutation Rules

| Rule | Description |
|------|-------------|
| Active phase only | Only mutate fields belonging to active phase |
| No deletion | Never delete answered questions |
| No rewrite of history | Never rewrite past commits or verification results |
| Append-only | Questions, requirements, verification, commits are append-only (requirements/verification/gates editable during spec phase) |
| Local persistence | Always persist locally after Linear save |

---

## Human Gate Detection

When detecting human signals for phase transitions:

1. **Keyword matching:** Look for trigger keywords in user messages
2. **Explicit confirmation:** Use AskUserQuestion for critical transitions
3. **Phase-gated:** Only accept signals relevant to current phase

**Do not:**
- Auto-advance based on task completion alone (except intake→research)
- Skip confirmation for meaningful phase transitions
- Accept signals for phases we're not ready to enter

---

## Resume Behavior

When `/ticket-work {ticket_id}` is invoked on an existing contract:

1. Fetch ticket, parse contract
2. Validate contract structure
3. **Re-read architecture handshake** (if available):
   - Read `.claude/architecture-handshake.md` using the Read tool
   - If file exists: refresh understanding of repo-specific constraints
   - If file doesn't exist: continue without (graceful fallback)
4. Report current state:
   ```
   Resuming {ticket_id} in {phase} phase.

   Status:
   - Questions: {N} answered / {M} total ({K} pending)
   - Requirements: {N} defined
   - Verification: {N} passed / {M} total
   - Gates: {N} approved / {M} total
   - Commits: {N}

   Next action: {describe what needs to happen in current phase}
   ```
5. Continue from current phase
