Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
c67050c
[AISOS-2298] Define Draft and Item Pydantic Models in src/forge/model…
ekuris-redhat Jul 30, 2026
832506b
[AISOS-2299] Implement Jira API Attachment Helpers in JiraClient
ekuris-redhat Jul 30, 2026
05a4a3f
[AISOS-2299] Fix concurrent request race condition in JiraClient atta…
ekuris-redhat Jul 30, 2026
5318c06
[AISOS-2300] Develop DraftManager in src/forge/workflow/utils/draft_m…
ekuris-redhat Jul 30, 2026
e20fc1a
[AISOS-2301] Add unit tests for DraftManager
ekuris-redhat Jul 30, 2026
c7af9e8
[AISOS-2301] Add Unit Tests for DraftManager and simplify implementation
ekuris-redhat Jul 30, 2026
0462511
[AISOS-2302] Implement Command Classification & Parameter Parsing in …
ekuris-redhat Jul 30, 2026
acc0fb7
[AISOS-2302] Extract shared key-value pair parsing logic and update J…
ekuris-redhat Jul 30, 2026
16054c4
[AISOS-2303] Implement Draft Mutation Logic in DraftManager
ekuris-redhat Jul 30, 2026
812f26d
[AISOS-2303] Implement Draft Mutation Logic and Refactor DraftManager…
ekuris-redhat Jul 30, 2026
bed8279
[AISOS-2304] Implement Natural Language Revision LLM Chain in ForgeAgent
ekuris-redhat Jul 30, 2026
946b077
[AISOS-2305] Integrate Comment Commands and Rollback in Orchestrator …
ekuris-redhat Jul 30, 2026
6c5a52c
[AISOS-2306] Integrate Draft Review Gate in Epic Decomposition Node
ekuris-redhat Jul 30, 2026
2a2cc5e
[AISOS-2307] Integrate Draft Review Gate in Task Generation Node
ekuris-redhat Jul 30, 2026
e493749
[AISOS-2308] Implement Draft-Based Ticket Provisioning on Approval
ekuris-redhat Jul 30, 2026
cd3b4ba
[AISOS-2309] Integrate Comment Commands and Label Events in Orchestra…
ekuris-redhat Jul 30, 2026
2eeae11
[AISOS-2310] Implement integration tests for draft review flow
ekuris-redhat Jul 30, 2026
879583d
[AISOS-2294-review] Local code review — fix breaking issues
ekuris-redhat Jul 30, 2026
2c973fc
[AISOS-2294-docs] Update stale documentation for draft-based planning…
ekuris-redhat Jul 30, 2026
c41e7d0
[AISOS-2294] review: address PR feedback
ekuris-redhat Jul 30, 2026
42c4372
[AISOS-2294-review-fix] Implement PR review plan for AISOS-2294
ekuris-redhat Jul 30, 2026
6a2347d
[AISOS-2294-review-review-impl] Post-review-impl code review
ekuris-redhat Jul 30, 2026
815692c
[AISOS-2294] review: address PR feedback
ekuris-redhat Jul 30, 2026
38f0150
[AISOS-2294-review-fix] Implement PR review plan for AISOS-2294
ekuris-redhat Jul 30, 2026
a7875ae
[AISOS-2294-review-review-impl] Post-review-impl code review
ekuris-redhat Jul 30, 2026
61e8644
[AISOS-2294] review: address PR feedback
ekuris-redhat Jul 30, 2026
b8594d5
[AISOS-2294-review-fix] Implement PR review plan for AISOS-2294
ekuris-redhat Jul 30, 2026
b1bf560
[AISOS-2294-review-review-impl] Post-review-impl code review
ekuris-redhat Jul 30, 2026
fd35a10
[AISOS-2294-review-review-impl] Post-review-impl code review
ekuris-redhat Jul 30, 2026
c27f73a
chore: remove temporary commit_msg.txt
ekuris-redhat Jul 30, 2026
8c420a8
[AISOS-2294-review-analyze] Analyze PR review feedback for AISOS-2294
ekuris-redhat Jul 30, 2026
06ae37e
[AISOS-2294] review: address PR feedback
ekuris-redhat Jul 30, 2026
fb1ed97
[AISOS-2294-review-review-impl] Fix draft review flow integration tes…
ekuris-redhat Jul 30, 2026
ac8d0b1
[AISOS-2294-review-analyze] Analyze PR review feedback for AISOS-2294
ekuris-redhat Jul 30, 2026
a1e964c
[AISOS-2294] review: address PR feedback
ekuris-redhat Jul 30, 2026
f2c0500
[AISOS-2294-review-review-impl] Refine typing signature of check_yolo…
ekuris-redhat Jul 30, 2026
eb2986a
[AISOS-2294-review-review-impl] Post-review-impl code review
ekuris-redhat Jul 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ specs/
docs/github-app-setup.md

# Python
.mypy_cache/
__pycache__/
*.py[cod]
*$py.class
Expand Down
11 changes: 8 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,16 @@ podman rm $(podman ps -a --filter name=forge- -q)

## Jira Comment Syntax

| Prefix | Effect |
|--------|--------|
| `!` | Revision request — triggers regeneration with feedback |
| Prefix / Command | Effect |
|------------------|--------|
| `!` | Revision request — triggers regeneration/revision with feedback |
| `?` or `@forge ask` | Question — triggers Q&A answer |
| `>option N` | RCA option selection (RCA Option Gate only) |
| `/forge approve` | Approve draft (Epic plan or Tasks) to provision tickets |
| `/forge remove ID` | Remove proposed draft item by ID and re-sequence |
| `/forge exclude ID` | Toggle exclusion of proposed draft item by ID |
| `/forge update ID key=val` | Update fields (`summary`, `description`, `repo`) of proposed draft item |
| `/forge add key=val` | Add a new proposed item to draft |
| _(no prefix)_ | Informational — workflow ignores it |

## GitHub PR Comment Commands
Expand Down
12 changes: 12 additions & 0 deletions docs/developer-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,18 @@ curl -X POST http://localhost:8000/api/v1/webhooks/github \
| `/forge skip-gate <name>` | Skip named CI check | CI stages |
| `/forge unskip-gate <name>` | Remove a skip | CI stages |

### Jira comment commands

These commands are used on the parent Jira ticket during the draft review stages (Epic Plan and Tasks).

| Command | Effect | Active at |
|---------|--------|-----------|
| `/forge approve` | Approve draft, provision sub-tickets, and delete draft attachment | `plan_approval_gate`, `task_approval_gate` |
| `/forge remove <ID>` | Remove a draft item by local sequential ID | `plan_approval_gate`, `task_approval_gate` |
| `/forge exclude <ID>` | Toggle the exclusion flag of a draft item | `plan_approval_gate`, `task_approval_gate` |
| `/forge update <ID> key=val` | Update fields (`summary`, `description`, `repo`) of a draft item | `plan_approval_gate`, `task_approval_gate` |
| `/forge add key=val` | Add a new proposed item to the draft | `plan_approval_gate`, `task_approval_gate` |

### Jira labels

| Label | Meaning |
Expand Down
50 changes: 32 additions & 18 deletions docs/guide/feature-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,22 +60,29 @@ Forge generates a behavioral specification from the approved PRD, typically usin

Forge breaks the feature into logical epics — high-level areas of work that map to implementation phases.

**Human action:** Review the epic plan. You have four options at this stage:
By default, Forge uses an interactive **Draft Review Flow** at this stage (unless YOLO mode is active):
1. Instead of creating Jira tickets immediately, Forge serializes the proposed epics into `forge-stories-draft.json` and uploads it as an attachment on the Feature ticket.
2. Forge posts a markdown table comment on the Feature ticket outlining the proposed Epics.
3. The workflow pauses at `plan_approval_gate`.

| Action | How |
|--------|-----|
| Approve | Change label to `forge:plan-approved` |
| Ask a question | Comment with `?` prefix — Forge answers without re-decomposing |
| Revise one epic | `!` comment on the **specific epic sub-ticket** — Forge updates only that epic |
| Redo the full decomposition | `!` comment on the **feature ticket** — Forge regenerates all epics with your feedback |
**Human action:** Review the epic plan draft. You have several options at this stage:

| Action | How | Description |
|--------|-----|-------------|
| **Approve** | Comment `/forge approve` OR set label to `forge:plan-approved` | Forge provisions the Epic sub-tickets on Jira from the draft, deletes the draft attachment, and advances to Task Generation. |
| **Direct Edit** | Use `/forge` commands (e.g. `/forge update`, `/forge remove`, etc.) | Directly modify the draft attachment and regenerate the proposal comment. See [Jira Labels & Comments](labels.md) for a list of commands. |
| **Ask a question** | Comment with `?` prefix or `@forge ask` | Forge answers your question without regenerating the draft. |
| **Request revisions** | Comment with `!` prefix followed by your feedback | Forge uses LLM assistance to revise the entire draft JSON and update the proposal comment with your feedback. |

If `forge:yolo` mode is active, the draft review is bypassed. Epics are created in Jira immediately, and the workflow automatically proceeds to Task Generation.

```mermaid
flowchart TD
Gate([plan_approval_gate])
Gate -->|forge:plan-approved| Next[Generate Tasks]
Gate -->|forge:plan-approved or /forge approve| Next[Generate Tasks]
Gate -->|"? on feature ticket"| QA[Answer Question]
Gate -->|"! on feature ticket"| Regen[Regenerate All Epics]
Gate -->|"! on epic sub-ticket"| Update[Update Single Epic]
Gate -->|"/forge update/remove/exclude/add"| Update[Modify Draft]
QA --> Gate
Regen --> Gate
Update --> Gate
Expand All @@ -87,22 +94,29 @@ flowchart TD

Forge generates granular implementation tasks scoped to individual repositories. Each task is sized to fit in a single container execution pass.

**Human action:** Review the tasks. You have four options at this stage:
By default, Forge uses an interactive **Draft Review Flow** at this stage (unless YOLO mode is active):
1. Instead of creating Jira tickets immediately, Forge serializes the proposed tasks into `forge-tasks-draft.json` and uploads it as an attachment on the Feature ticket.
2. Forge posts a markdown table comment on the Feature ticket outlining the proposed Tasks.
3. The workflow pauses at `task_approval_gate`.

**Human action:** Review the task draft. You have several options at this stage:

| Action | How | Description |
|--------|-----|-------------|
| **Approve** | Comment `/forge approve` OR set label to `forge:task-approved` | Forge provisions the Task sub-tickets on Jira from the draft, deletes the draft attachment, and advances to Implementation. |
| **Direct Edit** | Use `/forge` commands (e.g. `/forge update`, `/forge remove`, etc.) | Directly modify the draft attachment and regenerate the proposal comment. See [Jira Labels & Comments](labels.md) for a list of commands. |
| **Ask a question** | Comment with `?` prefix or `@forge ask` | Forge answers your question without regenerating the draft. |
| **Request revisions** | Comment with `!` prefix followed by your feedback | Forge uses LLM assistance to revise the entire draft JSON and update the proposal comment with your feedback. |

| Action | How |
|--------|-----|
| Approve | Change label to `forge:task-approved` |
| Ask a question | Comment with `?` prefix — Forge answers without regenerating |
| Revise one task | `!` comment on the **specific task sub-ticket** — Forge updates only that task |
| Regenerate all tasks | `!` comment on the **feature or epic ticket** — Forge regenerates the full task list with your feedback |
If `forge:yolo` mode is active, the draft review is bypassed. Tasks are created in Jira immediately, and the workflow automatically proceeds to Implementation.

```mermaid
flowchart TD
Gate([task_approval_gate])
Gate -->|forge:task-approved| Next[Implement Tasks]
Gate -->|forge:task-approved or /forge approve| Next[Implement Tasks]
Gate -->|"? on ticket"| QA[Answer Question]
Gate -->|"! on feature/epic"| Regen[Regenerate All Tasks]
Gate -->|"! on task sub-ticket"| Update[Update Single Task]
Gate -->|"/forge update/remove/exclude/add"| Update[Modify Draft]
QA --> Gate
Regen --> Gate
Update --> Gate
Expand Down
28 changes: 24 additions & 4 deletions docs/guide/labels.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,33 @@ Standalone Tasks and Epics can be processed with the standard `forge:managed` la

**Starting a workflow:** Create a Jira issue and add `forge:managed`. Forge detects the issue type and begins the appropriate pipeline: Feature/Story, Bug, or standalone Task/Epic takeover.

**Approving a stage:** When Forge posts a PRD, spec, or other artifact, it sets the `forge:*-pending` label. Change it to `forge:*-approved` to advance the workflow. Do not add the approved label manually before Forge posts — it won't be recognized until the pending state is set.
**Approving a stage:** When Forge posts an artifact (such as a PRD or Spec), it sets the `forge:*-pending` label. You can approve it by changing the label to `forge:*-approved` to advance the workflow. For draft-based stages (Epic Plan and Tasks), you can also approve by commenting `/forge approve` on the ticket.

**Requesting revisions:** Start a comment with `!` followed by your feedback. Forge regenerates the artifact and resets the pending label.
**Interactive Draft Review:** For Epic Decomposition and Task Generation stages, Forge uses a draft-based review flow by default (unless `forge:yolo` mode is active).
1. Instead of creating sub-tickets immediately, Forge serializes the proposed items into a JSON draft file (`forge-stories-draft.json` or `forge-tasks-draft.json`) and uploads it as a Jira attachment.
2. Forge posts a formatted markdown table comment on the ticket detailing the proposed plan.
3. While the stage is pending, you can modify the draft directly using **Jira comment commands** (see below) or request a natural language revision.
4. Once you approve (via `/forge approve` or setting the approved label), Forge downloads the draft, provisions the actual Jira tickets from it, and deletes the draft attachment.

**Asking questions:** Start a comment with `?` or `@forge ask`. Forge answers without advancing or regenerating.
### Jira Comment Commands

**Informational comments:** Comments without a recognized prefix (`!`, `?`, `@forge ask`, `>option`) are ignored by the workflow — use them for team discussion without triggering Forge.
For stages using the draft-based review flow (Epic Plan and Tasks), you can post comments on the parent ticket with the following commands:

| Command | Description | Example |
|---------|-------------|---------|
| `/forge approve` | Approve the draft, provision all non-excluded items as Jira tickets, and delete the draft attachment. | `/forge approve` |
| `/forge remove <ID>` | Remove a draft item by its local sequential ID. Remaining items are automatically re-sequenced. | `/forge remove 3` |
| `/forge exclude <ID>` | Toggle the exclusion flag of a draft item. Excluded items are skipped during ticket provisioning. | `/forge exclude 2` |
| `/forge update <ID> key=val` | Update fields of a draft item (supported keys: `summary`, `description`, `repo`). | `/forge update 1 repo="my-org/custom-repo"` |
| `/forge add key=val` | Add a new proposed item to the draft. | `/forge add summary="New Story" repo="my-org/repo"` |

*Note: Successful command/revision comments are automatically edited by Forge to prepend `✅`. If a command or revision fails, Forge posts a comment detailing the error with a leading `❌`.*

**Requesting revisions:** Start a comment with `!` followed by your feedback (e.g., `! update the repositories to use the new service`). For standard artifacts, Forge regenerates them. For drafts, Forge uses LLM assistance to revise the draft JSON attachment and update the proposed plan table.

**Asking questions:** Start a comment with `?` or `@forge ask`. Forge answers without advancing or regenerating/modifying the drafts.

**Informational comments:** Comments without a recognized prefix (such as `!`, `?`, `@forge ask`, `>option`, or `/forge`) are ignored by the workflow — use them for team discussion without triggering Forge.

**Handling failures:** When `forge:blocked` appears, read the Forge comment for the error. Fix the underlying issue if needed, then add `forge:retry`.

Expand Down
5 changes: 5 additions & 0 deletions src/forge/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,11 @@ def ignored_ci_checks(self) -> list[str]:
default=0.5, description="Webhook acknowledgment timeout in seconds"
)

yolo_mode: bool = Field(
default=False,
description="Autonomous mode - skip all artifact approval gates",
)

# Container Configuration
container_image: str = Field(
default="localhost/forge-dev:latest",
Expand Down
83 changes: 83 additions & 0 deletions src/forge/integrations/agents/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -1272,6 +1272,89 @@ async def answer_question(
logger.info(f"Generated answer ({len(result)} chars)")
return result.strip() if result else ""

async def revise_draft_with_feedback(
self,
draft_content: str,
feedback: str,
context: dict[str, Any] | None = None,
) -> str:
"""Revise draft content based on user feedback.

Uses the 'revision-draft' prompt template to guide the LLM to output
the revised draft JSON.

Args:
draft_content: The current draft JSON content.
feedback: Natural language feedback.
context: Optional context from the workflow state.

Returns:
The updated draft JSON string.
"""
from langchain_core.output_parsers import StrOutputParser

# Format context into a readable string/JSON
context_str = json.dumps(context, indent=2) if context else "None provided"

# Load the prompt template using project's load_prompt
prompt_text = load_prompt(
"revision-draft",
draft_content=draft_content,
feedback=feedback,
context=context_str,
)

model = self._create_model()
chain = model | StrOutputParser()

logger.info("Revising draft using direct LangChain model chain")
response = await chain.ainvoke(prompt_text)

# Strip preamble/narration and validate as JSON
cleaned_text = response.strip()

# Check markdown code blocks first
pattern = r"```(?:json)?\s*([\s\S]*?)\s*```"
match = re.search(pattern, cleaned_text)
if match:
cleaned_text = match.group(1).strip()
else:
# If no code block, look for the JSON object/list boundary
# Find the first occurrence of '{' or '[' and the last of '}' or ']'
start_brace = cleaned_text.find("{")
start_bracket = cleaned_text.find("[")

# Determine which starts first
start_idx = -1
if start_brace != -1 and start_bracket != -1:
start_idx = min(start_brace, start_bracket)
elif start_brace != -1:
start_idx = start_brace
elif start_bracket != -1:
start_idx = start_bracket

if start_idx != -1:
# Find the last brace or bracket
end_brace = cleaned_text.rfind("}")
end_bracket = cleaned_text.rfind("]")
end_idx = max(end_brace, end_bracket)

if end_idx > start_idx:
cleaned_text = cleaned_text[start_idx : end_idx + 1].strip()

try:
parsed_json = json.loads(cleaned_text)
validated_json_str = json.dumps(parsed_json, indent=2)
logger.info(
f"Successfully revised draft and validated JSON ({len(validated_json_str)} chars)"
)
return validated_json_str
except json.JSONDecodeError as e:
logger.error(f"Failed to parse LLM response as valid JSON: {e}\nResponse: {response}")
raise ValueError(
f"Failed to parse LLM response as valid JSON: {e}\nResponse: {response}"
)

async def close(self) -> None:
"""Close the agent and cleanup resources."""
pass
Loading
Loading