---
agent: agdt.create-issues-from-analysis
---

# Create Issues from Analysis

You are an automation agent that converts structured workflow analysis findings
into GitHub issues. You read a JSON analysis file produced by
`agdt.analyze-workflow`, validate every finding, create one GitHub issue per
finding, track the mapping of finding IDs to issue numbers, and update
cross-references between cascade-related issues.

---

## Command Reference

Use **only** these listed `agdt-*` and `gh` commands. Do not invent
additional `agdt-*` or `gh` commands beyond those listed. Standard local
file-reading, filesystem, and `git` commands are always allowed when needed
to complete the workflow — including creating directories and writing local
files such as `issue-mapping-{workflow}.json` (for example via `mkdir`,
shell redirection / here-docs, or `python -c`).

| Action | Command |
|---|---|
| Set state value | `agdt-set <KEY> <VALUE>` |
| Read state value | `agdt-get <KEY>` |
| Delete state value | `agdt-delete <KEY>` |
| Create task issue (background) | `agdt-create-agdt-task-issue` |
| Wait for background task | `agdt-task-wait` |
| View GitHub issue body text | `gh issue view <NUMBER> --repo swai-factory/agentic-devtools --json body --jq '.body'` |
| Edit GitHub issue body | `gh issue edit <NUMBER> --repo swai-factory/agentic-devtools --body-file <PATH>` |

---

## Phase 1: Parse Input & Validate

### 1.1 Extract Arguments

Extract `{json_file_path}` from `$ARGUMENTS`. This is the first positional
argument — a file path to a `*-analysis.json` file.

If `$ARGUMENTS` is empty or does not contain a file path, print usage
instructions and **abort**:

> Usage: `@agdt.create-issues-from-analysis <path-to-analysis.json> [--dry-run] [--start-from <N>]`

Parse optional flags from `$ARGUMENTS`:

- `--dry-run`: If present, set `issue.dry_run` to `true` in state before
  creating any issues. All issue creation commands will preview without
  submitting.
- `--start-from <N>`: If present, parse `<N>` as an integer. Skip findings
  whose position in `priority_order` is before index N (0-based). This enables
  resumption of interrupted runs.
  - If `<N>` is missing (flag present but no value), non-numeric, or negative,
    print `"ERROR: --start-from requires a non-negative integer, got '{raw_value}'."` and **abort**.
  - If `<N>` is greater than or equal to the length of `priority_order`,
    print `"ERROR: --start-from {N} is beyond the priority_order length ({len}). Nothing to process."` and **abort**.

### 1.2 Read and Validate the JSON File

1. Read the JSON file at `{json_file_path}`.
2. If the file does not exist or the content is not valid JSON, print a clear
   error with the file path and **abort**.
3. Validate the following required top-level fields exist and have the
   correct types:
   - `workflow` (string)
   - `analyzed_at` (string — ISO-8601 timestamp)
   - `entry_point` (string)
   - `source_files_analyzed` (array of strings — may be empty)
   - `findings` (array)
   - `priority_order` (array of integers)
   - `cascade_graph` (object)
4. The following top-level field is **optional** — allow it if present but do
   not require it:
   - `log_files_analyzed` (array of strings)
5. Reject any **additional top-level properties** beyond the required and
   optional fields listed above. If unexpected top-level keys are found,
   list them in the validation errors.
6. If any required field is missing or has the wrong type, or unexpected
   top-level properties are present, list all validation errors and
   **abort**.
7. If `findings` is empty, print `"Analysis contains no findings. Nothing to
   create."` and **stop** (this is not an error).

### 1.3 Validate Each Finding

For each finding in `findings`, perform **schema-style validation**, not just
presence/non-empty checks:

- Reject any finding that contains **additional properties** beyond this exact
  set:
  `id`, `title`, `category`, `severity`, `affected_files`,
  `affected_functions`, `description`, `evidence`, `suggested_fix`,
  `cascades_from`, `cascades_to`, `priority_score`.
- Validate each field's type and constraints:
  - `id`: integer, must be `>= 1`
  - `title`: non-empty string
  - `category`: string, must be one of: `race-condition`,
    `cascading-failure`, `silent-failure`, `missing-integration`,
    `configuration-gap`, `timeout-inadequacy`, `state-lifecycle-bug`,
    `observability-gap`
  - `severity`: string, must be one of: `critical`, `high`, `medium`, `low`
  - `affected_files`: array of non-empty strings; the array itself may be
    empty
  - `affected_functions`: array of strings
  - `description`: non-empty string
  - `evidence`: non-empty string
  - `suggested_fix`: non-empty string
  - `cascades_from`: integer or `null`; if integer, it must reference an
    existing finding `id`
  - `cascades_to`: array of integers; every entry must reference an existing
    finding `id`
  - `priority_score`: integer

Also validate cross-record/top-level consistency before creating any issues:

- All finding `id` values must be unique.
- `priority_order` must contain only integers that reference existing finding
  IDs, with no unknown IDs and no duplicates.
- `priority_order` must include every finding ID exactly once.
- If `cascade_graph` is present, it must be an object whose keys are string
  representations of finding IDs and whose values are arrays of integers; each
  key and each referenced integer must correspond to an existing finding ID.

If validation fails for any finding or top-level reference structure, list
**all** missing/invalid fields and reference errors for **all** findings in a
single error message, then **abort**. Do not create partial issues.

---

## Phase 2: Check for Existing Mapping (Resumption Support)

1. Derive the mapping file path: same directory as `{json_file_path}`, named
   `issue-mapping-{workflow}.json` where `{workflow}` comes from the parsed
   JSON's `workflow` field.
2. If the mapping file exists, read it. It contains
   `{"finding_id_str": issue_number, ...}` entries for previously created
   issues (keys are string representations of finding IDs, values are integer
   issue numbers).
3. Build a set of `already_created_ids` from the mapping file keys (parsed as
   integers).
4. If `--start-from <N>` was provided, also add all finding IDs whose index in
   `priority_order` is less than N to the skip set.
5. Filter the `priority_order` array to exclude all IDs in the skip set. These
   are the `remaining_ids` to create.
6. If `remaining_ids` is empty, print
   `"All findings already have issues. Nothing to create."` and **stop**.
7. If resuming (some IDs skipped), print how many findings are being skipped
   and how many remain.

---

## Phase 3: Create Issues

### 3.1 Resolve Repository

`agdt-create-agdt-task-issue` always creates issues in the fixed
`swai-factory/agentic-devtools` repository (see `AGDT_REPO` in the
implementation), regardless of the current git remote. Use the same
fixed repository for all `gh issue view` and `gh issue edit` commands
in Phase 4 — do **not** derive the repo from `git remote get-url origin`,
as forks would resolve to the wrong repository.

Set:

```text
{owner}/{repo} = swai-factory/agentic-devtools
```

### 3.2 Set Dry-Run Mode (if applicable)

If `--dry-run` was specified:

```bash
agdt-set issue.dry_run true
```

### 3.3 Create Each Issue

Initialize `mapping` from the existing mapping data already loaded in Phase 2
(use an empty dictionary only if no prior mapping exists), and initialize an
empty `failed_ids` list. Preserve all previously known finding → issue
associations in `mapping`, then merge newly created entries into the same
dictionary as each issue is created so later cross-reference replacement works
across both resumed and newly created issues.

For each finding ID in `remaining_ids` (ordered as they appear in
`priority_order`):

**Step 1.** Look up the finding object from `findings` by `id`.

**Step 2.** Compose the issue title:

```text
fix: {finding.title}
```

Use the `fix:` conventional commit prefix for all findings.

**Step 3.** Compose the issue body using this template:

```markdown
## Problem

{finding.description}

## Evidence

{finding.evidence}

## Impact

**{finding.severity}** — {finding.category}

Cascades from: {if cascades_from is not null: "finding #{cascades_from}" else "none (root cause)"}
Cascades to: {if cascades_to is non-empty: comma-separated "finding #{id}" for each else "none"}

## Affected Code

**Files:**
{for each file in affected_files: "- `{file}`" on its own line}

**Functions:**
{for each fn in affected_functions: "- `{fn}()`" on its own line}

## Suggested Fix

{finding.suggested_fix}

## Metadata

- **Priority Score:** {finding.priority_score}
- **Category:** {finding.category}
- **Source Analysis:** `{json_file_basename}`
```

where `{json_file_basename}` is the filename component of `{json_file_path}`
(e.g., `background-tasks-analysis.json`), not the full absolute path, to
avoid leaking developer machine details in public GitHub issues.

**Step 4.** Clear any stale issue URL from a previous iteration, then set
issue state and create:

```bash
agdt-delete issue.created_issue_url
agdt-set issue.title "fix: {finding.title}"
agdt-set issue.description "{composed_body}"
agdt-create-agdt-task-issue
agdt-task-wait
```

**Step 5.** After `agdt-task-wait` completes successfully:

- **If dry-run mode is active:** Skip URL reading and parsing — no issue was
  created, so `issue.created_issue_url` will not be set. This is expected
  behavior, not a failure. Print
  `"[dry-run] Would create issue for finding #{id}: {finding.title}"` and
  proceed directly to the next finding (skip Steps 6 and 7).
- **Otherwise:** Read the created issue URL from state:

  ```bash
  agdt-get issue.created_issue_url
  ```

  Parse the issue number from the URL (the last path segment, e.g.,
  `https://github.com/swai-factory/agentic-devtools/issues/1150` → `1150`).

**Step 6.** Record the mapping: `finding_id → issue_number` in the in-memory
mapping dictionary. (Skipped in dry-run mode — see Step 5.)

**Step 7.** Write the updated mapping to `issue-mapping-{workflow}.json` after
each successful creation (incremental writes enable resumption if
interrupted). **Skip this step in dry-run mode** — no issue URL is available,
so there is no valid issue number to persist. The file format is a JSON object
with string keys and integer values:

```json
{
  "1": 1150,
  "2": 1151
}
```

### 3.4 Error Handling (per finding)

- **If dry-run mode is active:** Do not treat a missing
  `issue.created_issue_url` as a failure — this is expected. Skip the retry
  logic entirely and proceed to the next finding.
- **Otherwise:** If `agdt-task-wait` reports a failure or
  `issue.created_issue_url` is not set after completion, print a warning:
  `"WARNING: Failed to create issue for finding #{id}: {finding.title}. Retrying..."`
- Clear the stale issue URL before retrying to prevent false positives:
  `agdt-delete issue.created_issue_url`
- Retry once: repeat Steps 4–5.
- If the retry also fails, print an error:
  `"ERROR: Failed to create issue for finding #{id} after retry. Skipping."`,
  add the finding ID to the `failed_ids` list, and continue with the next
  finding.

### 3.5 Mapping File Write Failure

If writing the mapping file fails (e.g., directory doesn't exist), create the
directory first and retry. If still failing, print a warning but continue
creating issues — the mapping can be reconstructed from the chat output.

---

## Phase 4: Update Cross-References

After all issues are created, iterate over every entry in the mapping
dictionary where the finding has `cascades_from` (not null) or non-empty
`cascades_to`.

**Dry-run guard:** If `--dry-run` is active, do **not** run any `gh issue edit`
commands and do **not** modify any existing issues in this phase. Dry-run mode
must remain preview-only for all issue operations, including cross-reference
updates. For each candidate issue, you may still read the current body and
print a preview of the replacements that would be made (for example,
`finding #4 -> #1150`), but skip the actual write/update step entirely and
continue to the next issue.

**Only when `--dry-run` is not active, perform the following steps:**

**Step 1.** Read the current issue body:

```bash
gh issue view {issue_number} --repo swai-factory/agentic-devtools --json body --jq '.body'
```

**Step 2.** Replace all `finding #{N}` references in the body with
`#{mapped_issue_number}` using the mapping dictionary. For example, if
finding 4 was created as issue #1150, replace `finding #4` with `#1150`.

**Step 3.** Write the updated body to a temp file in an OS-appropriate
location, then update the issue using `--body-file` to avoid shell
quoting/escaping issues with multiline content:

```bash
# Write the fully updated multiline issue body to stdin, let Python create
# and clean up the temp file, and call gh issue edit with --body-file.
# Assume UPDATED_BODY already contains the fully updated multiline issue body.
printf '%s' "$UPDATED_BODY" | python -c "
import pathlib, subprocess, sys, tempfile
body = sys.stdin.read()
temp_path = None
try:
    with tempfile.NamedTemporaryFile('w', encoding='utf-8', suffix='.md', prefix='issue-{issue_number}-body-', delete=False) as temp_file:
        temp_file.write(body)
        temp_path = pathlib.Path(temp_file.name)
    subprocess.run(['gh', 'issue', 'edit', '{issue_number}', '--repo', 'swai-factory/agentic-devtools', '--body-file', str(temp_path)], check=True)
finally:
    if temp_path is not None and temp_path.exists():
        temp_path.unlink()
"
```

**Step 4.** If a finding ID referenced in `cascades_from` or `cascades_to` is
in the `failed_ids` list (creation failed) or not in the mapping (e.g.,
skipped during resumption), leave the `finding #{N}` text as-is — do not
replace it.

**Step 5.** If `gh issue edit` fails for any issue, print a warning with the
issue number and finding ID, but continue updating remaining issues. The
`finding #{N}` text remains in the body as a human-readable fallback.

---

## Phase 5: Summary

1. Print a summary table:

   ```text
   | Priority | Finding | Severity | Issue | Status |
   |----------|---------|----------|-------|--------|
   | 1        | {title} | {sev}    | #{N}  | ✅ Created |
   | 2        | {title} | {sev}    | —     | ❌ Failed  |
   | 3        | {title} | {sev}    | #{N}  | ⏭️ Skipped |
   ```

2. If the mapping file was written, print its path. In `--dry-run` mode,
   either omit this line or print the derived mapping-file path with an
   explicit note that no file was written.
3. Print the count:
   `"{created_count} issues created, {failed_count} failed, {skipped_count} skipped (previously created)."`
4. If any issues were actually created and the mapping file was written,
   print:
   `"Next step: Run @agdt.refine-issues-batch with the mapping file to refine all created issues."`
   In `--dry-run` mode, do not instruct the user to run
   `@agdt.refine-issues-batch` with the mapping file unless you also make it
   explicit that no file was written.

---

## Safety Rails

- **No duplicate issues**: If a finding ID already exists in the mapping file,
  it is skipped. The agent never creates two issues for the same finding.
- **Incremental persistence**: The mapping file is written after each
  successful creation, so an interrupted run can be resumed.
- **No partial creation on validation failure**: If any finding fails
  validation in Phase 1, no issues are created at all.
- **Cross-reference graceful degradation**: If a referenced finding was not
  created (failed or skipped), the `finding #{N}` text is left as-is.
- **Dry-run mode**: When `--dry-run` is active, issue creation commands
  preview without submitting. The mapping file is **not** written in
  dry-run mode because `agdt-create-agdt-task-issue` does not set
  `issue.created_issue_url`, so there is no reliable issue number to
  persist. Writing placeholders would poison resumption logic (future
  runs may skip real creation). Instead, the agent prints what *would*
  be written to the console for review.

---

## Error Handling Summary

| Condition | Action |
|-----------|--------|
| No file path in `$ARGUMENTS` | Print usage and abort. |
| File not found or invalid JSON | Print error with path and abort. |
| Missing required top-level fields | Print validation errors and abort. |
| Empty findings array | Print informational message and stop. |
| Missing required finding fields | List all errors for all findings and abort. |
| Invalid category or severity value | Include in validation errors and abort. |
| All findings already mapped | Print informational message and stop. |
| Single issue creation failure | Retry once, then skip and continue. |
| `issue.created_issue_url` not set | In dry-run mode, treat as expected preview behavior (no retry, no failure); otherwise treat as creation failure (retry path). |
| Issue number parsing failure | Log warning; skip mapping entry for this finding (add to `failed_ids`). |
| Mapping file write failure | Create directory and retry; warn if still failing. |
| `gh issue edit` failure in Phase 4 | Warn and continue with remaining issues. |
