Skip to content
Back to skills

Capture Issue

BSecurity

Use when asked to capture or create an issue from conversation or natural language.

  • 6 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 6, 2026
ai-agentsgoshellbashgitapisecuritydocumentation

Works with

  • api

Security analysis

B88/100
  • criticalSends environment variables or credentials to an external URL

Pro scans all 3 files and shows the line behind each finding

Scanned October 6, 2026

npx -y skills add BrennonTWilliams/little-loops --skill capture-issue --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Capture Issue?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Capture Issue
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/brennontwilliams-capture-issue-little-loops/badge)](https://www.skillsdirectory.com/skills/brennontwilliams-capture-issue-little-loops)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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
trigger_fixtures:
  should_fire:
    - "capture this bug as a new issue from our conversation"
    - "create an issue from this natural language description"
  should_not_fire:
    - "fix this issue's template structure"
    - "detect conflicting requirements across open issues"
---

# 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:**

```bash
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:**

1. Search `{{config.issues.base_dir}}/epics/` for a file whose frontmatter `id:` matches `PARENT_ID`.
2. If no matching EPIC file is found, abort with:
   ```
   ❌ Parent EPIC not found: [PARENT_ID]. Check the ID and try again.
   ```
3. 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:

1. **Issue Title**: Create a concise summary (5-10 words max)
2. **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).
3. **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"
4. **Description**: The full description text

#### Conversation Mode (no description)

Analyze the current conversation session to identify potential issues:

1. **Problems discussed but not resolved** - bugs, errors, failures mentioned
2. **Improvements mentioned but deferred** - "we should...", "it would be better if..."
3. **Feature ideas that came up** - "we could add...", "what if we..."
4. **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:**

```markdown
## 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):**

```yaml
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 two checks: (1) title word-overlap scoring against active and completed/cancelled issues via `ll-issues find-similar`, and (2) 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.

#### Word-Overlap Scoring (Active + Completed Issues)

`find-similar` scores the candidate title against every issue's title using the canonical Jaccard implementation in `text_utils.py`. `--against all` includes `done`/`cancelled` issues alongside active ones, so one call replaces the separate active/completed passes:

```bash
ll-issues find-similar "<new issue title>" --against all
```

Returns ranked `{id, title, path, score}` candidates:
- Score >= {{config.issues.duplicate_detection.exact_threshold}} = exact duplicate
- Score {{config.issues.duplicate_detection.similar_threshold}}-{{config.issues.duplicate_detection.exact_threshold}} = similar issue
- No returned candidate = likely new issue (find-similar's own default threshold is {{config.issues.duplicate_detection.similar_threshold}}, so anything below that never appears in the results)

A match against a `done`/`cancelled` issue is a candidate for reopening rather than a fresh capture.

#### Search History DB for Near-Duplicates

Independently of word-overlap scoring, query the session history for recently closed or deferred issues matching the new issue's title keywords. This FTS5 query answers a different question (was something like this recently closed/deferred in conversation history?) against a different corpus (`.ll/history.db`, not `.issues/`), so it stays a separate call:

```bash
# NOTE: this stop-word regex is an FTS-query-keyword shim for ll-session --fts,
# not the similarity stop-word list — that list is text_utils._COMMON_WORDS,
# consumed via `ll-issues find-similar` above.
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](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

1. **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"
   ```

2. **Infer `testable: false`** — scan the issue title and description for doc-only signal keywords before creating the file (ENH-2966: this mirrors `check_format_gaps`'s title + `## Summary` scan surface, word-boundary matched — not the pre-ENH-2966 whole-body substring scan):
   - **Signal keywords**: "doc", "docs", "documentation", "broken link"/"broken links", "broken anchor"/"broken anchors", "readme"/"readmes", "changelog"/"changelogs", "spelling", "typo"/"typos", "guide", "fix link"/"fix links" — word-boundary matches only (e.g. `doc` does not match inside `documentation` or `docs`)
   - **Threshold**: 2+ distinct keyword matches (case-insensitive) in the combined title + description text
   - If threshold met: after `ll-issues create` writes the file (step 4), use `Edit` to add `testable: false` to its frontmatter and log `ℹ️ Set testable: false (inferred: documentation-only issue)`
   - If threshold not met: leave `testable` unset (absence means testable)

3. **Verify quoted evidence against the cited artifact** (ENH-3283 — the write-time half of
   BUG-3282's verify-time gate). Before step 4's write, identify every quoted span in the drafted
   body (fenced block or inline backticks) that is attributed to a named file or issue ID — minus
   the non-trigger shapes below — and confirm each against that artifact.

   **Non-triggers — skip these without verifying:**
   - **Command output** and run-log excerpts (a pasted traceback, pytest summary, FSM state
     transition) — not a quotation *of* the file it mentions
   - **Reproduction steps** that name an artifact as an argument (e.g. `` `ll-issues show
     ENH-3277` ``) — cites the artifact as an input, not as a source of quoted text
   - **Proposed text** — a snippet the issue suggests be *written* (a new config key, a
     replacement line) — by definition absent from the artifact today
   - **Symbol and path names** in backticks (`` `create_issue` ``, `` `create.py:406` ``) —
     references, not spans
   - When it is ambiguous whether a span is a quotation at all, **leave it alone** — a missed
     fabrication is tolerable; deleting a true quote is not.

   **For each remaining triggered span**, confirm it appears in the cited artifact using the
   **`Grep` tool** — not shell `grep`, which this skill has no `Bash(grep:*)` grant for. If the
   span is attributed to an issue ID rather than a path, resolve the path first with `ll-issues
   show <ID> --json`. Check the current working-tree file only.

   **On a miss**: drop the quote and describe the evidence in prose, or re-read the artifact and
   quote it correctly — never write the unverified span. Evidence that genuinely came from
   uncommitted or transient state (a working-tree edit, a loop run directory) must be labeled as
   such explicitly, rather than attributed to the file.

4. **Create the issue file atomically** (FEAT-2947 — this single call replaces the old next-id/sections/slugify/Write dance, and wires `parent:` both directions when `PARENT_ID` is set):

   ```bash
   ll-issues create --type "$ISSUE_TYPE" --title "$ISSUE_TITLE" \
     --priority "$PRIORITY" --variant "$TEMPLATE_STYLE" \
     --body-file - --json \
     ${PARENT_ID:+--parent "$PARENT_ID"} <<< "$ISSUE_SUMMARY"
   ```

   Parse the JSON result for `{"id": ..., "path": ...}`. Note `--stage` is deliberately **not** passed here: later steps still mutate the file (the `testable: false` edit of step 2, the session-log append of step 5), so staging happens once at step 6 after those edits.

**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](templates.md) for the complete issue file template structure.

5. **Append session log entry** to the newly created issue file. Use the Bash tool:

```bash
ll-issues append-log <path-to-issue-file> /ll:capture-issue
```

If `ll-issues` is not available, fall back to manually appending with **exactly** this format (backticks required):

```
- `/ll:capture-issue` - YYYY-MM-DDTHH:MM:SS - `<absolute path to session JSONL>`
```

Append it under the existing `## Session Log` heading if one exists; create the
heading only when none does, immediately above the `---` / `## Status` footer.
Never add a second `## Session Log` heading (BUG-3424).

   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):

   ```bash
   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
   ```

6. **Stage the new file** (last, so every edit above is included in the staged content):

```bash
git add "{{config.issues.base_dir}}/[category]/[filename]"
```

   If `PARENT_ID` was set, also stage the parent EPIC — `ll-issues create --parent` appended its
   `## Children` bullet but, without `--stage`, left it unstaged:

```bash
git add "PARENT_EPIC_PATH"
```

### Phase 4b: Link Relevant Documents (if documents.enabled)

See [templates.md](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 for the Create New Issue action** — `ll-issues create --parent` (Phase 4,
step 4) already writes `parent:` on the child and appends its `## Children` bullet on the EPIC,
both staged together (FEAT-2947). This phase applies only when reopening/updating an existing
issue under `PARENT_ID` outside the `create` path.

**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:

```markdown
- **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):

```markdown
## Children

- **CHILD_ID** — [one-sentence child title]
```

Use `Edit` to apply the change. Do not rewrite the whole file.

#### 3. Stage the EPIC file

```bash
git add "PARENT_EPIC_PATH"
```

#### Action: Update Existing Issue

Append an "Additional Context" section to the existing issue:

```bash
cat >> "[path-to-existing-issue]" << 'EOF'

---

## Additional Context

- **Date**: [YYYY-MM-DD]
- **Source**: capture-issue

[New context/findings from the description or conversation]

EOF
```

Stage the updated file:
```bash
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.

1. **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:

   ```markdown
   ---

   ## 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]
   ```

2. **Stage the changes:**
```bash
git add "[path-to-issue]"
```

### Phase 5: Output Report

See [templates.md](templates.md) for complete output report templates including:
- Single issue report format
- Multiple issues summary table
- Next steps recommendations

---

## Examples

```bash
# Capture issue from explicit description (bug)
/ll:capture-issue "The login button doesn't respond on mobile Safari"

# Capture issue from explicit description (feature)
/ll:capture-issue "We should add dark mode support to the settings page"

# Capture issue from explicit description (enhancement)
/ll:capture-issue "The API response time could be improved with caching"

# Analyze current conversation for issues to capture
/ll:capture-issue

# Capture with minimal template (quick mode)
/ll:capture-issue "Quick note: cache is slow" --quick

# Analyze conversation and use minimal templates
/ll:capture-issue --quick

# Capture a child issue and link it to an existing EPIC
/ll:capture-issue "Add retry logic to sprint runner" --parent EPIC-1663

# Child with minimal template
/ll:capture-issue "Fix log output truncation" --parent EPIC-1626 --quick
```

---

## Integration

After capturing issues:
1. **Review**: `cat [issue-path]` to verify content
2. **Validate**: `/ll:ready-issue [ID]` to check accuracy
3. **Prioritize**: `/ll:prioritize-issues` if priority needs adjustment
4. **Link**: `/ll:link-epics` to assign parentless issues to open epics
5. **Commit**: `/ll:commit` to save new issues
6. **Process**: `/ll:manage-issue [type] [action] [ID]` to implement

Files in this skill

  • SKILL.md20 KB
  • agents/openai.yaml147 B
  • templates.md7.7 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…