# Issue Parser Estimation Strategy

**Status:** Proposed (Phase 0 Enhancement)
**Related Issues:** COORD-002 (Issue Parser Agent)
**Priority:** Critical (P0) - Required for real-world usage

---

## Problem Statement

Not all issues will follow the formatted metadata structure used in COORD-XXX issues. The issue parser must handle:

1. **Unformatted issues** - Just title and description, no metadata
2. **Incomplete metadata** - Some fields present, others missing
3. **Oversized issues** - Exceed 50% rule, need decomposition
4. **Varying formats** - Different teams use different templates

**The parser must make intelligent estimates when metadata is missing.**

---

## Estimation Strategy

### Level 1: Structured Metadata (Best Case)

**When issue has formatted metadata:**

```markdown
## Context Estimate

- Files to modify: 3
- Implementation complexity: medium (20000 tokens)
- **Total estimated: 46800 tokens**
- **Recommended agent: glm**

## Difficulty

medium
```

**Action:**

- Extract directly from markdown
- **Confidence: HIGH** (95%+)
- Use values as-is

---

### Level 2: Content Analysis (Common Case)

**When metadata is missing, analyze issue content:**

#### 2.1 Analyze Title and Description

```python
async def estimate_from_content(issue: dict) -> dict:
    """Estimate metadata from issue content using AI."""

    client = anthropic.Anthropic()

    response = await client.messages.create(
        model="claude-sonnet-4-5",
        max_tokens=2000,
        messages=[{
            "role": "user",
            "content": f"""Analyze this issue and estimate resource requirements.

Title: {issue['title']}
Description:
{issue['body']}

Estimate:
1. **Files to modify**: How many files will likely be touched?
   - Count mentions of specific files, modules, components
   - Look for scope indicators (refactor, add feature, fix bug)

2. **Implementation complexity**:
   - Low: Simple CRUD, config changes, one-file fixes
   - Medium: Multi-file changes, business logic, API development
   - High: Architecture changes, complex refactoring, new systems

3. **Context estimate**:
   - Use formula: (files × 7000) + complexity + tests + docs
   - Low: ~20-40K tokens
   - Medium: ~40-80K tokens
   - High: ~80-150K tokens

4. **Difficulty**: low/medium/high

5. **Confidence**: 0-100% (based on clarity of issue description)

Return JSON:
{{
  "estimated_context": <integer>,
  "difficulty": "low" | "medium" | "high",
  "assigned_agent": "haiku" | "sonnet" | "glm" | "opus",
  "confidence": <integer 0-100>,
  "reasoning": "Brief explanation of estimates"
}}
"""
        }]
    )

    metadata = json.loads(response.content[0].text)

    # Add metadata source
    metadata['source'] = 'content_analysis'

    return metadata
```

**Confidence factors:**

| Factor                  | High Confidence              | Low Confidence          |
| ----------------------- | ---------------------------- | ----------------------- |
| **Description length**  | >500 chars, detailed         | <100 chars, vague       |
| **Specific mentions**   | Files, modules, APIs named   | Generic "fix the thing" |
| **Acceptance criteria** | Clear checklist              | None provided           |
| **Technical details**   | Stack traces, logs, examples | "It's broken"           |
| **Scope clarity**       | Well-defined boundaries      | Open-ended              |

**Confidence scoring:**

````python
def calculate_confidence(issue: dict, analysis: dict) -> int:
    """Calculate confidence score 0-100."""

    score = 50  # Start at neutral

    # Description length
    if len(issue['body']) > 500:
        score += 15
    elif len(issue['body']) < 100:
        score -= 20

    # Specific file/module mentions
    code_patterns = r'(`[^`]+`|\.ts|\.py|\.js|src/|components/)'
    mentions = len(re.findall(code_patterns, issue['body']))
    score += min(mentions * 5, 20)

    # Acceptance criteria
    if '- [ ]' in issue['body'] or '- [x]' in issue['body']:
        score += 10

    # Technical details (stack traces, logs, code blocks)
    if '```' in issue['body']:
        score += 10

    # Scope keywords
    scope_keywords = ['refactor', 'implement', 'add', 'fix', 'update']
    if any(kw in issue['title'].lower() for kw in scope_keywords):
        score += 5

    return max(0, min(100, score))
````

**Action:**

- Use AI to estimate from content
- **Confidence: MEDIUM** (50-80%)
- Comment confidence on issue

**Example comment:**

```markdown
🤖 Estimated metadata (confidence: 65%):

- Estimated context: 52,000 tokens
- Difficulty: medium
- Recommended agent: glm

📊 Reasoning:

- Mentions 3 components (UserService, AuthMiddleware, tests)
- Requires API changes (medium complexity)
- Has acceptance criteria (+confidence)
- Description is detailed (+confidence)

Note: These are estimates. Actual usage may vary.
```

---

### Level 3: Minimal Information (Worst Case)

**When issue is very vague:**

```markdown
Title: Fix the login bug
Body: Login doesn't work
```

**Action:**

- Use conservative defaults
- **Confidence: LOW** (<50%)
- Warn user, suggest more details

**Default estimates:**

```python
DEFAULT_ESTIMATES = {
    'estimated_context': 50000,      # Conservative default
    'difficulty': 'medium',
    'assigned_agent': 'sonnet',      # Safe middle-tier agent
    'confidence': 30,
    'reasoning': 'Minimal information provided, using defaults'
}
```

**Example comment:**

```markdown
⚠️ Low confidence estimate (30%):

- Estimated context: 50,000 tokens
- Difficulty: medium
- Recommended agent: sonnet

📝 Suggestion: For better estimates, please add:

- Which files/components are affected
- Expected scope (one file? multiple modules?)
- Acceptance criteria or definition of "done"
- Any relevant logs, stack traces, or examples
```

---

## Oversized Issue Detection & Decomposition

### 50% Rule Enforcement

**Before queuing, check if issue exceeds 50% of target agent's limit:**

```python
async def check_and_decompose(issue: dict, metadata: dict) -> List[dict]:
    """Check if issue exceeds 50% rule. If so, decompose."""

    # Get agent limit
    agent = metadata['assigned_agent']
    agent_limit = AGENT_PROFILES[agent]['context_limit']
    max_issue_size = agent_limit * 0.5

    # Check if oversized
    if metadata['estimated_context'] > max_issue_size:
        logger.warning(
            f"Issue #{issue['number']} exceeds 50% rule: "
            f"{metadata['estimated_context']} > {max_issue_size}"
        )

        # Decompose into sub-issues
        sub_issues = await decompose_epic(issue, metadata)

        # Comment on parent issue
        await gitea_client.comment_on_issue(
            issue['number'],
            f"⚠️ Issue exceeds 50% rule ({metadata['estimated_context']:,} tokens)\n\n"
            f"Auto-decomposing into {len(sub_issues)} sub-issues...\n\n"
            f"This issue will be converted to an EPIC tracking the sub-issues."
        )

        # Label as epic
        await gitea_client.add_label(issue['number'], 'epic')

        return sub_issues

    else:
        # Single issue, good to go
        return [issue]
```

### Automatic Epic Decomposition

**When issue is oversized, use AI to break it down:**

```python
async def decompose_epic(issue: dict, metadata: dict) -> List[dict]:
    """Decompose oversized issue into sub-issues."""

    client = anthropic.Anthropic()

    # Get max issue size for target agent
    agent = metadata['assigned_agent']
    max_size = AGENT_PROFILES[agent]['context_limit'] * 0.5

    response = await client.messages.create(
        model="claude-opus-4-5",  # Use Opus for decomposition
        max_tokens=4000,
        messages=[{
            "role": "user",
            "content": f"""This issue is too large ({metadata['estimated_context']:,} tokens)
and must be broken into smaller sub-issues.

**Original Issue:**
Title: {issue['title']}
Body: {issue['body']}

**Constraints:**
- Each sub-issue must be ≤ {max_size:,} tokens
- Sub-issues should be independently completable
- Maintain logical order (dependencies)
- Cover all aspects of original issue

**Instructions:**
1. Identify logical breakdown points
2. Create 3-6 sub-issues
3. Estimate context for each
4. Define dependencies (what must come first)

Return JSON array:
[
  {{
    "title": "Sub-issue title",
    "description": "Detailed description",
    "estimated_context": <integer>,
    "difficulty": "low" | "medium" | "high",
    "depends_on": [<array of titles this depends on>]
  }},
  ...
]

Ensure NO sub-issue exceeds {max_size:,} tokens.
"""
        }]
    )

    sub_issues = json.loads(response.content[0].text)

    # Validate all sub-issues fit 50% rule
    for sub in sub_issues:
        if sub['estimated_context'] > max_size:
            raise ValueError(
                f"Sub-issue '{sub['title']}' still exceeds limit: "
                f"{sub['estimated_context']} > {max_size}"
            )

    # Create sub-issues in Gitea
    created_issues = []
    issue_map = {}  # title -> issue number

    for sub in sub_issues:
        # Create issue
        new_issue = await gitea_client.create_issue(
            title=f"[SUB] {sub['title']}",
            body=f"""**Parent Epic:** #{issue['number']} - {issue['title']}

## Objective
{sub['description']}

## Context Estimate
- **Total estimated: {sub['estimated_context']:,} tokens**
- Difficulty: {sub['difficulty']}

## Dependencies
{format_dependencies(sub['depends_on'], issue_map)}

## Notes
Auto-generated from epic decomposition.
""",
            labels=['sub-issue', f"p{issue.get('priority', 1)}"],
            milestone=issue.get('milestone')
        )

        created_issues.append(new_issue)
        issue_map[sub['title']] = new_issue['number']

    # Update parent issue to reference sub-issues
    sub_issue_list = '\n'.join(
        f"- #{i['number']} {i['title']}"
        for i in created_issues
    )

    await gitea_client.comment_on_issue(
        issue['number'],
        f"## Sub-Issues Created\n\n{sub_issue_list}\n\n"
        f"This issue is now an EPIC. Close this when all sub-issues complete."
    )

    return created_issues
```

**Example decomposition:**

```markdown
Original Issue #200: "Refactor authentication system"
Estimated: 180,000 tokens (EXCEEDS 50% rule for Opus: 100K limit)

Auto-decomposed into:
├─ #201 [SUB] Extract auth middleware (45K tokens) → Ready
├─ #202 [SUB] Implement JWT service (38K tokens) → Blocked by #201
├─ #203 [SUB] Add token refresh logic (32K tokens) → Blocked by #202
├─ #204 [SUB] Update auth guards (28K tokens) → Blocked by #202
└─ #205 [SUB] Add integration tests (35K tokens) → Blocked by #201,#202,#203,#204

Total: 178K tokens across 5 sub-issues
Each sub-issue: ≤50K tokens ✅
```

---

## Confidence-Based Workflow

### High Confidence (95%+)

- **Source:** Structured metadata in issue body
- **Action:** Use values directly, queue immediately
- **Comment:** "✅ Metadata detected, high confidence"

### Medium Confidence (50-95%)

- **Source:** Content analysis
- **Action:** Use estimates, queue with note
- **Comment:** "📊 Estimated from content (confidence: X%)"

### Low Confidence (<50%)

- **Source:** Minimal info, using defaults
- **Action:** Use defaults, warn user
- **Comment:** "⚠️ Low confidence - please add details"
- **Optional:** Wait for user to update issue before queuing

---

## Confidence Thresholds

```python
class ConfidenceThresholds:
    """Confidence-based behavior thresholds."""

    AUTO_QUEUE = 60      # ≥60% confidence: Queue automatically
    WARN_USER = 50       # <50% confidence: Warn user
    WAIT_FOR_UPDATE = 30 # <30% confidence: Don't queue, wait for update
```

**Workflow:**

```python
async def handle_issue_assignment(issue: dict):
    """Handle issue assigned to @mosaic."""

    # Parse metadata (structured or estimated)
    metadata = await parse_issue_metadata(issue)

    # Check confidence
    if metadata['confidence'] >= ConfidenceThresholds.AUTO_QUEUE:
        # High/medium confidence - queue it
        await queue_manager.enqueue(issue, metadata)

        await gitea_client.comment_on_issue(
            issue['number'],
            f"🤖 Added to coordinator queue\n\n"
            f"**Metadata** (confidence: {metadata['confidence']}%):\n"
            f"- Estimated context: {metadata['estimated_context']:,} tokens\n"
            f"- Difficulty: {metadata['difficulty']}\n"
            f"- Assigned agent: {metadata['assigned_agent']}\n\n"
            f"{metadata.get('reasoning', '')}"
        )

    elif metadata['confidence'] >= ConfidenceThresholds.WARN_USER:
        # Low confidence - queue but warn
        await queue_manager.enqueue(issue, metadata)

        await gitea_client.comment_on_issue(
            issue['number'],
            f"⚠️ Low confidence estimate ({metadata['confidence']}%)\n\n"
            f"Using best-guess estimates:\n"
            f"- Estimated context: {metadata['estimated_context']:,} tokens\n"
            f"- Difficulty: {metadata['difficulty']}\n"
            f"- Assigned agent: {metadata['assigned_agent']}\n\n"
            f"💡 For better estimates, please add:\n"
            f"- Which files/components are affected\n"
            f"- Expected scope\n"
            f"- Acceptance criteria\n\n"
            f"Queued anyway - work will proceed with these estimates."
        )

    else:
        # Very low confidence - don't queue
        await gitea_client.comment_on_issue(
            issue['number'],
            f"❌ Cannot queue - insufficient information ({metadata['confidence']}%)\n\n"
            f"Please add more details:\n"
            f"- What files/components need changes?\n"
            f"- What is the expected scope?\n"
            f"- What are the acceptance criteria?\n\n"
            f"Re-assign to @mosaic when ready."
        )

        # Unassign from coordinator
        await gitea_client.unassign_issue(issue['number'], 'mosaic')
```

---

## Edge Cases

### Case 1: Issue Updated After Queuing

**User adds details after low-confidence queuing:**

```python
@app.post('/webhook/gitea')
async def handle_webhook(payload: dict):
    """Handle Gitea webhooks."""

    if payload['action'] == 'edited':
        issue = payload['issue']

        # Check if already in queue
        if queue_manager.has_issue(issue['number']):
            # Re-parse with updated content
            new_metadata = await parse_issue_metadata(issue)

            # Update queue
            queue_manager.update_metadata(issue['number'], new_metadata)

            await gitea_client.comment_on_issue(
                issue['number'],
                f"🔄 Issue updated - re-estimated metadata:\n"
                f"- Estimated context: {new_metadata['estimated_context']:,} tokens\n"
                f"- Difficulty: {new_metadata['difficulty']}\n"
                f"- Confidence: {new_metadata['confidence']}%"
            )
```

### Case 2: Decomposition Creates More Oversized Issues

**If decomposed sub-issue still exceeds 50% rule:**

```python
# Recursive decomposition
async def decompose_epic(issue: dict, metadata: dict, depth: int = 0) -> List[dict]:
    """Decompose with recursion limit."""

    if depth > 2:
        raise ValueError(
            f"Issue #{issue['number']} cannot be decomposed enough. "
            f"Manual intervention required."
        )

    sub_issues = await ai_decompose(issue, metadata)

    # Check if any sub-issue is still too large
    oversized = [s for s in sub_issues if s['estimated_context'] > max_size]

    if oversized:
        # Recursively decompose oversized sub-issues
        final_issues = []
        for sub in sub_issues:
            if sub['estimated_context'] > max_size:
                # Decompose further
                sub_sub_issues = await decompose_epic(sub, sub, depth + 1)
                final_issues.extend(sub_sub_issues)
            else:
                final_issues.append(sub)
        return final_issues

    return sub_issues
```

### Case 3: No Clear Decomposition

**If AI can't find good breakdown points:**

```python
# Comment on issue, unassign from coordinator
await gitea_client.comment_on_issue(
    issue['number'],
    f"❌ Cannot auto-decompose this issue.\n\n"
    f"Estimated at {metadata['estimated_context']:,} tokens "
    f"(exceeds {max_size:,} limit), but no clear breakdown found.\n\n"
    f"**Manual action needed:**\n"
    f"1. Break this into smaller sub-issues manually\n"
    f"2. Assign sub-issues to @mosaic\n"
    f"3. This issue can become an EPIC tracking sub-issues\n\n"
    f"Unassigning from coordinator."
)

await gitea_client.unassign_issue(issue['number'], 'mosaic')
```

---

## Implementation Checklist

**Phase 0 (COORD-002) must include:**

- [ ] Structured metadata extraction (existing plan)
- [ ] Content analysis estimation (NEW)
- [ ] Confidence scoring (NEW)
- [ ] Best-guess defaults (NEW)
- [ ] 50% rule validation (NEW)
- [ ] Automatic epic decomposition (NEW)
- [ ] Recursive decomposition handling (NEW)
- [ ] Confidence-based workflow (NEW)
- [ ] Update issue handling (NEW)

---

## Success Criteria

**Parser handles all issue types:**

- ✅ Formatted issues → High confidence, extract directly
- ✅ Unformatted issues → Medium confidence, estimate from content
- ✅ Vague issues → Low confidence, use defaults + warn
- ✅ Oversized issues → Auto-decompose, create sub-issues
- ✅ Updated issues → Re-parse, update queue

**No manual intervention needed for:**

- Well-formatted issues
- Clear descriptions (even without metadata)
- Oversized issues (auto-decompose)

**Manual intervention only for:**

- Very vague issues (<30% confidence)
- Issues that can't be decomposed
- Edge cases requiring human judgment

---

## Example Scenarios

### Scenario 1: Well-Formatted Issue

```markdown
Issue #300: [COORD-020] Implement user profile caching

## Context Estimate

- Files: 4
- Total: 52,000 tokens
- Agent: glm

## Difficulty

medium
```

**Result:**

- ✅ Extract directly
- Confidence: 95%
- Queue immediately

---

### Scenario 2: Clear But Unformatted Issue

```markdown
Issue #301: Add caching to user profile API

Need to cache user profiles to reduce database load.

Files affected:

- src/api/users/users.service.ts
- src/cache/cache.service.ts
- src/api/users/users.controller.ts
- tests/users.service.spec.ts

Acceptance criteria:

- [ ] Cache GET /users/:id requests
- [ ] 5 minute TTL
- [ ] Invalidate on update/delete
- [ ] Add tests
```

**Result:**

- 📊 Estimate from content
- Files: 4 → 28K base
- Clear scope → Medium complexity (20K)
- Tests mentioned → 10K
- **Total: ~58K tokens**
- Confidence: 75%
- Queue with note

---

### Scenario 3: Vague Issue

```markdown
Issue #302: Fix user thing

Users are complaining
```

**Result:**

- ⚠️ Minimal info
- Use defaults (50K, medium, sonnet)
- Confidence: 25%
- Comment: "Please add details"
- Don't queue (<30% threshold)
- Unassign from @mosaic

---

### Scenario 4: Oversized Issue

```markdown
Issue #303: Refactor entire authentication system

We need to modernize our auth:

- Replace session-based auth with JWT
- Add OAuth2 support
- Implement refresh tokens
- Add MFA
- Update all protected routes
- Migration for existing users
```

**Result:**

- 📊 Estimate: 180K tokens
- ⚠️ Exceeds 50% rule (>100K)
- Auto-decompose into sub-issues:
  - #304: Extract JWT service (35K)
  - #305: Add OAuth2 integration (40K)
  - #306: Implement refresh tokens (28K)
  - #307: Add MFA support (32K)
  - #308: Update route guards (22K)
  - #309: User migration script (18K)
- Label #303 as EPIC
- Queue sub-issues

---

## Conclusion

The issue parser must be **robust and intelligent** to handle real-world issues:

- ✅ Extract structured metadata when available
- ✅ Estimate from content when missing
- ✅ Use confidence scores to guide behavior
- ✅ Auto-decompose oversized issues
- ✅ Warn users on low confidence
- ✅ Handle edge cases gracefully

**This makes the coordinator truly autonomous** - it can handle whatever issues users throw at it.

---

**Document Version:** 1.0
**Created:** 2026-01-31
**Status:** Proposed - Update COORD-002
**Priority:** Critical (P0) - Required for real-world usage
