| name | capture-issue |
| description | Use when asked to capture or create an issue from conversation or natural language. |
| args | [description] [--quick] [--parent EPIC-NNN] |
| argument-hint | [description] |
| allowed-tools | ["Read","Glob","Grep","Write","Bash(ll-issues:*, git:*)","Bash(ll-session:*)"] |
| arguments | [{"name":"input","description":"Natural language description of the issue (optional - analyzes conversation if omitted)","required":false},{"name":"flags","description":"Optional flags (--quick for minimal template, --parent EPIC-NNN to link as child)","required":false}] |
| metadata | {"short-description":"Use when asked to capture or create an issue from conversation or natural langua"} |
Capture Issue
You are tasked with capturing issues from either a natural language description or the current conversation context, with automatic duplicate detection and support for reopening completed issues.
Configuration
This command uses project configuration from .ll/ll-config.json:
- Issues base:
{{config.issues.base_dir}}
- Template style:
{{config.issues.capture_template}} (full or minimal)
- Exact duplicate threshold:
{{config.issues.duplicate_detection.exact_threshold}} (default: 0.8)
- Similar issue threshold:
{{config.issues.duplicate_detection.similar_threshold}} (default: 0.5)
- Status enum:
open, in_progress, blocked, deferred, done, cancelled — see .claude/CLAUDE.md § Issue File Format for full enum and forbidden synonyms.
Arguments
$ARGUMENTS
- input (optional): Natural language description of the issue
- If provided, parse and create single issue
- If omitted, analyze conversation for potential issues
- flags (optional): Modify command behavior
--quick - Use minimal template regardless of config setting
--parent EPIC-NNN - Link the new issue as a child of the given EPIC: sets parent: in child frontmatter and updates the EPIC's relates_to: list and ## Children section
Process
Phase 1: Determine Mode and Extract Issues
Parse flags:
FLAGS="${flags:-}"
QUICK_MODE=false
if [[ "$FLAGS" == *"--quick"* ]]; then QUICK_MODE=true; fi
PARENT_ID=""
if [[ "$FLAGS" =~ --parent[[:space:]]+([A-Z]+-[0-9]+) ]]; then
PARENT_ID="${BASH_REMATCH[1]}"
fi
If --parent was given, validate it before proceeding:
- Search
{{config.issues.base_dir}}/epics/ for a file whose frontmatter id: matches PARENT_ID.
- If no matching EPIC file is found, abort with:
❌ Parent EPIC not found: [PARENT_ID]. Check the ID and try again.
- Store the resolved EPIC file path as
PARENT_EPIC_PATH for use in Phase 4c.
Check the arguments to determine mode:
IF input argument is provided:
MODE = "direct"
ELSE:
MODE = "conversation"
Direct Mode (description provided)
Parse the natural language description to extract:
- Issue Title: Create a concise summary (5-10 words max)
- Issue Type: Infer from keywords:
- BUG: "broken", "error", "crash", "fails", "doesn't work", "wrong", "bug", "issue with", "problem"
- FEAT: "add", "new feature", "implement", "create", "support for", "need", "want", "should have"
- ENH: "improve", "enhance", "better", "optimize", "refactor", "cleanup", "update", "upgrade"
- EPIC: "epic", "initiative", "milestone", "large effort", "multi-issue", "decompose into", "rollup of", "umbrella"
- Default to ENH if unclear. Use EPIC only when the user explicitly signals coordination scope (a container that will be decomposed into child BUG/FEAT/ENH issues).
- Priority: Infer from severity language:
- P0-P1: "critical", "urgent", "blocking", "security", "data loss", "production down"
- P2: "important", "high priority", "significant"
- P3: Default for most issues
- P4-P5: "minor", "low priority", "nice to have", "someday"
- Description: The full description text
Conversation Mode (no description)
Analyze the current conversation session to identify potential issues:
- Problems discussed but not resolved - bugs, errors, failures mentioned
- Improvements mentioned but deferred - "we should...", "it would be better if..."
- Feature ideas that came up - "we could add...", "what if we..."
- TODOs or action items mentioned - explicit tasks identified
For each potential issue found, extract:
- Source context (brief quote or summary of what prompted it)
- Issue title
- Issue type (BUG/FEAT/ENH)
- Priority suggestion
- Brief description
Present all identified issues to the user:
## Issues Identified from Conversation
| # | Type | Priority | Title |
|---|------|----------|-------|
| 1 | BUG | P2 | [title] |
| 2 | ENH | P3 | [title] |
| 3 | FEAT | P3 | [title] |
| 4 | EPIC | P2 | [title] |
### Issue 1: [Title]
- **Type**: BUG (inferred from: "this keeps failing...")
- **Context**: [Brief quote from conversation]
### Issue 2: [Title]
- **Type**: ENH (inferred from: "we should improve...")
- **Context**: [Brief quote from conversation]
Use AskUserQuestion to let user select which issues to capture (multi-select):
questions:
- question: "Which issues would you like to capture?"
header: "Select issues"
options:
- label: "Issue 1: [title]"
description: "[type] - [brief context]"
- label: "Issue 2: [title]"
description: "[type] - [brief context]"
multiSelect: true
If no issues are identified, inform the user:
No actionable issues found in this conversation. You can run this command with an input argument:
/ll:capture-issue "description of the issue"
Phase 2: Duplicate Detection
For each issue to capture, search for existing duplicates. This phase performs three checks: (1) Jaccard scoring against active issues, (2) Jaccard scoring against completed/cancelled issues, and (3) an FTS5 near-duplicate check against the session history DB using ll-session search --fts "<keywords>" --kind issue --limit 5 2>/dev/null || true. If .ll/history.db is absent or the query returns no results, proceed silently without warning.
Search Active Issues
Issue status lives in YAML frontmatter (status: open|done|deferred|cancelled),
not in directory location. Active issues are those with status: open (or
absent, which defaults to open).
for dir in {{config.issues.base_dir}}/bugs/ {{config.issues.base_dir}}/features/ {{config.issues.base_dir}}/enhancements/ {{config.issues.base_dir}}/epics/; do
for f in "$dir"*.md; do
[ -f "$f" ] || continue
status=$(awk '/^---$/{n++; next} n==1 && /^status:/{print $2; exit}' "$f")
case "${status:-open}" in
open|in_progress|blocked) echo "$f" ;;
esac
done
done
For each existing issue file:
- Read the file content
- Extract the title from the
# [TYPE]-[NNN]: [Title] header
- Calculate word overlap between new issue title and existing title
- Also check for file path matches if the issue mentions specific files
Scoring:
- Extract significant words (3+ chars, excluding common words like "the", "and", "for")
- Calculate Jaccard similarity:
intersection / union of word sets
- Score >= {{config.issues.duplicate_detection.exact_threshold}} = exact duplicate
- Score {{config.issues.duplicate_detection.similar_threshold}}-{{config.issues.duplicate_detection.exact_threshold}} = similar issue
- Score < {{config.issues.duplicate_detection.similar_threshold}} = likely new issue
Search Completed Issues
Completed issues live alongside active issues in their type directories,
distinguished by status: done (or cancelled) in frontmatter:
ll-issues list --status done --format path
Apply same scoring. If a completed issue has score >= {{config.issues.duplicate_detection.similar_threshold}}, it's a candidate for reopening.
Search History DB for Near-Duplicates
After Jaccard scoring, query the session history for recently closed or deferred issues matching the new issue's title keywords:
KEYWORDS=$(echo "<title>" | tr '[:upper:]' '[:lower:]' | grep -oE '\b[a-z]{3,}\b' | grep -vE '^(the|and|for|are|was|but|not|all|can|had|its|our|out|who|did|how|get|has|let|use|via|were|with|from|they|that|this|have|will|been|into|also|just|more|some|when|what|then|than|them|your|does|both|like)$' | tr '\n' ' ')
HIST_DUPES=$(ll-session search --fts "$KEYWORDS" --kind issue --limit 5 2>/dev/null || true)
If results include issues with status: done or status: deferred and >{{config.history.capture_issue.dup_overlap_threshold}} (default 0.7) title word overlap with the new issue title, surface a warning before writing the file:
Warning: Similar closed issue found: [ID] ([status]) — closed/deferred [N] days ago
Title: [existing issue title]
Proceed with new capture, or link to the existing issue instead?
Ask the user whether to proceed with capture or link to the existing issue. If .ll/history.db is absent or the query returns no results, proceed silently without warning.
Phase 3: Handle Duplicates/Similar Issues
Based on duplicate detection results, take appropriate action. See templates.md for detailed duplicate/similar handling flows including:
- Exact duplicate detection with user prompts
- Similar issue handling options
- Completed issue reopening flows
- "View Existing" / "View Completed" interaction patterns
If No Match Found (score < {{config.issues.duplicate_detection.similar_threshold}})
Proceed directly to issue creation without user confirmation.
Phase 4: Execute Action
Action: Create New Issue
-
Get next globally unique issue number:
ll-issues next-id
This prints the next available issue number as 3 digits (e.g., 071).
-
Determine target directory based on type:
- BUG ->
{{config.issues.base_dir}}/bugs/
- FEAT ->
{{config.issues.base_dir}}/features/
- ENH ->
{{config.issues.base_dir}}/enhancements/
- EPIC ->
{{config.issues.base_dir}}/epics/
-
Generate filename:
- Slugify the title: lowercase, replace spaces/special chars with hyphens
- Format:
P[priority]-[TYPE]-[NNN]-[slug].md
- Example:
P3-BUG-071-login-button-unresponsive.md
-
Create issue file:
Determine template style:
IF QUICK_MODE is true:
TEMPLATE_STYLE = "minimal"
ELSE IF config.issues.capture_template is set:
TEMPLATE_STYLE = {{config.issues.capture_template}}
ELSE:
TEMPLATE_STYLE = "full"
Build issue from shared template:
- Run
ll-issues sections {type} to get the per-type template where {type} is bug, feat, enh, or epic based on the issue type (v2.0 - optimized for AI implementation)
- Look up
creation_variants.[TEMPLATE_STYLE] to determine which sections to include
- For each section name in
include_common, use common_sections.[name].creation_template as placeholder content
- If
include_type_sections is true, also include sections from type_sections that have a creation_template
- Always include YAML frontmatter with
captured_at (ISO 8601 UTC timestamp, e.g. "2026-04-18T14:32:07Z" — use shell date -u +"%Y-%m-%dT%H:%M:%SZ" format), discovered_date (date-only, same day), and discovered_by: capture-issue. If PARENT_ID is set, also include parent: [PARENT_ID] in the frontmatter.
- Infer
testable: false — after building the frontmatter, scan the issue title and description for doc-only signal keywords:
- Signal keywords: "doc", "docs", "documentation", "broken link", "broken anchor", "readme", "changelog", "spelling", "typo", "guide", "fix link"
- Threshold: 2+ keyword matches (case-insensitive) in the combined title + description text
- If threshold met: add
testable: false to frontmatter and log ℹ️ Set testable: false (inferred: documentation-only issue)
- If threshold not met: omit
testable from frontmatter (absence means testable)
- This field is never added when < 2 signals match, to avoid false positives on issues that merely mention a guide or doc in passing
New sections in v2.0 (auto-included based on template variant):
- Motivation: Why this matters (replaces Current Pain Point for ENH)
- Implementation Steps: High-level outline for agent guidance
- Root Cause (BUG): File + function anchor + explanation
- API/Interface (FEAT/ENH): Public contract changes
- Use Case (FEAT): Concrete scenario (renamed from User Story)
See templates.md for the complete issue file template structure.
- Append session log entry to the newly created issue file:
## Session Log
- `/ll:capture-issue` - [ISO timestamp] - `[path to current session JSONL]`
To find the current session JSONL: look in ~/.claude/projects/ for the directory matching the current project (path encoded with dashes), find the most recently modified .jsonl file (excluding agent-*). Add the ## Session Log section before the --- / ## Status footer.
For FEAT or EPIC captures, append a decision entry to the log (silent no-op when the decisions log is absent; skip entirely for BUG type). The log is hybrid storage — a legacy .ll/decisions.yaml flat file and/or .ll/decisions.d/*.json fragments — so gate on either (a fresh, never-compacted install has only the fragment dir):
if [ "$ISSUE_TYPE" != "BUG" ] && { [ -f .ll/decisions.yaml ] || [ -d .ll/decisions.d ]; }; then
ll-issues decisions add \
--type=decision \
--category="architecture" \
--issue="$ISSUE_ID" \
--rule="Captured: $ISSUE_TITLE" \
--rationale="$ISSUE_SUMMARY" \
--scope=issue \
2>/dev/null || true
fi
- Stage the new file:
git add "{{config.issues.base_dir}}/[category]/[filename]"
Duplicate-ID recovery: If the PostToolUse hook reports that the just-written file was deleted (duplicate integer ID detected), the Write call will have returned success but the file no longer exists. Re-allocate a fresh ID by calling ll-issues next-id again, generate a new filename with the new number, and repeat from step 3. Do not reuse the original ID.
Phase 4b: Link Relevant Documents (if documents.enabled)
See templates.md for the complete document linking process including:
- Loading configured documents from
.ll/ll-config.json
- Extracting key concepts and scoring relevance
- Selecting top matches (max 3 documents)
- Updating the "Related Key Documentation" section with a table format
Skip this phase if:
documents.enabled is not true in .ll/ll-config.json
- OR no documents are configured in
documents.categories
Phase 4c: Wire Parent EPIC (if --parent was given)
Skip this phase if PARENT_ID is empty.
After the child issue file is created and staged, update the EPIC at PARENT_EPIC_PATH:
1. Add child ID to relates_to: frontmatter
Read the EPIC file's frontmatter. The relates_to: field may be:
- absent — insert
relates_to: [CHILD_ID] after the last frontmatter field
- an empty list
relates_to: [] — replace with relates_to: [CHILD_ID]
- a populated list — append the new ID to the list
Use Edit to apply the change in-place. Example:
# Before
relates_to: [ENH-100, ENH-101]
# After
relates_to: [ENH-100, ENH-101, CHILD_ID]
2. Append child to ## Children section
If the EPIC body already contains a ## Children section, append a new bullet at the end of it:
- **CHILD_ID** — [one-sentence child title from the child's Summary]
If no ## Children section exists, insert one before ## Status (or at end of file if no Status footer):
## Children
- **CHILD_ID** — [one-sentence child title]
Use Edit to apply the change. Do not rewrite the whole file.
3. Stage the EPIC file
git add "PARENT_EPIC_PATH"
Action: Update Existing Issue
Append an "Additional Context" section to the existing issue:
cat >> "[path-to-existing-issue]" << 'EOF'
---
- **Date**: [YYYY-MM-DD]
- **Source**: capture-issue
[New context/findings from the description or conversation]
EOF
Stage the updated file:
git add "[path-to-existing-issue]"
Action: Reopen Completed Issue
Issue status lives in frontmatter — reopening means flipping status: done
back to status: open. The file stays where it is in its type directory.
-
Update the file's frontmatter and append a Reopened section:
- Find the closed issue file (in its type dir with
status: done).
- Run
ll-issues set-status ISSUE_ID open to flip the status atomically.
If the issue has no id: field (legacy file), fall back to Edit to insert
status: open into the YAML frontmatter block.
- Append a Reopened section to the body:
---
## Reopened
- **Date**: [YYYY-MM-DD]
- **By**: capture-issue
- **Reason**: Issue recurred or was not fully resolved
### New Findings
[Context from the new description or conversation that prompted reopening]
-
Stage the changes:
git add "[path-to-issue]"
Phase 5: Output Report
See templates.md for complete output report templates including:
- Single issue report format
- Multiple issues summary table
- Next steps recommendations
Examples
/ll:capture-issue "The login button doesn't respond on mobile Safari"
/ll:capture-issue "We should add dark mode support to the settings page"
/ll:capture-issue "The API response time could be improved with caching"
/ll:capture-issue
/ll:capture-issue "Quick note: cache is slow" --quick
/ll:capture-issue --quick
/ll:capture-issue "Add retry logic to sprint runner" --parent EPIC-1663
/ll:capture-issue "Fix log output truncation" --parent EPIC-1626 --quick
Integration
After capturing issues:
- Review:
cat [issue-path] to verify content
- Validate:
/ll:ready-issue [ID] to check accuracy
- Prioritize:
/ll:prioritize-issues if priority needs adjustment
- Link:
/ll:link-epics to assign parentless issues to open epics
- Commit:
/ll:commit to save new issues
- Process:
/ll:manage-issue [type] [action] [ID] to implement