Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ See [Bug Workflow Guide](docs/guide/bug-workflow.md) for the full stage referenc
| **Implementation** | Fix implemented in ephemeral container with TDD + bidirectional test validation; qualitative review (7-item checklist, up to 2 retries) | (Automatic) |
| **PR → CI → Review** | Same as Feature workflow; PR includes release note section | Merge or request changes |
| **Post-merge Summary** | Fix summary + release note posted to Jira ticket | (Automatic) |
| **Docs Repo Update** | If `forge.docs_repo` is configured, updates separate docs repository (non-blocking) | (Automatic) |

## Architecture

Expand Down
2 changes: 2 additions & 0 deletions docs/guide/bug-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ flowchart TD
K -->|needs work, retry < 2| J
L --> M[CI/CD + Review → Merge]
M --> N[Post-merge Summary\nJira comment]
N --> O[Update Docs Repo\nif configured]
```

## Triggering a Bug Workflow
Expand Down Expand Up @@ -105,6 +106,7 @@ After plan approval, Forge:
7. **CI validation** with automatic fix loop.
8. **Human review** gate.
9. **Post-merge summary:** After merge, Forge posts a fix summary and release note to the Jira ticket.
10. **Docs repo update:** If `forge.docs_repo` is configured, Forge updates the separate documentation repository and creates a PR (non-blocking).

---

Expand Down
9 changes: 9 additions & 0 deletions docs/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,17 @@ curl -X PUT \
-H "Content-Type: application/json" \
-u "you@example.com:YOUR_API_TOKEN" \
-d '"org/repo1"'

# Optional: separate docs repo for post-merge documentation updates
curl -X PUT \
"https://your-org.atlassian.net/rest/api/3/project/MYPROJ/properties/forge.docs_repo" \
-H "Content-Type: application/json" \
-u "you@example.com:YOUR_API_TOKEN" \
-d '"org/docs-repo"'
```

When `forge.docs_repo` is set, Forge updates the separate documentation repository after a code PR is merged. The docs repo is cloned, the update-docs skill runs with the code repo mounted read-only, and a fork-based PR is created for any documentation changes. This is optional and non-blocking — if not configured or if the update fails, the workflow proceeds normally.

## Local Development Overrides

Use these to skip the Jira project property requirement during local development:
Expand Down
8 changes: 6 additions & 2 deletions docs/workflow-graph.mmd
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ graph TD;
escalate_blocked(escalate_blocked)
ai_review(ai_review)
human_review_gate(human_review_gate)
update_documentation(update_documentation)
update_docs_repo(update_docs_repo)
complete_tasks(complete_tasks)
aggregate_epic_status(aggregate_epic_status)
aggregate_feature_status(aggregate_feature_status)
Expand Down Expand Up @@ -65,13 +67,15 @@ graph TD;
generate_tasks -.-> __end__;
generate_tasks -.-> task_approval_gate;
human_review_gate -.-> __end__;
human_review_gate -.-> complete_tasks;
human_review_gate -.-> update_docs_repo;
human_review_gate -.-> implement_task;
update_docs_repo --> complete_tasks;
implement_bug_fix --> create_pr;
implement_task -.-> local_review;
implement_task -.-> escalate_blocked;
local_review -.-> local_review;
local_review -.-> create_pr;
local_review -.-> update_documentation;
update_documentation --> create_pr;
plan_approval_gate -.-> __end__;
plan_approval_gate -.-> generate_tasks;
plan_approval_gate -.-> regenerate_all_epics;
Expand Down
84 changes: 52 additions & 32 deletions skills/default/update-docs/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
name: update-docs
description: >-
Detects documentation files that have become stale due to code changes
and applies minimal targeted updates. Builds an identifier checklist
from the diff, greps documentation for matches, evaluates candidates
in two passes, and edits confirmed stale docs in-place.
and applies targeted updates.
---

# Update Docs
Expand Down Expand Up @@ -38,15 +36,18 @@ If the diff is empty, output `NO_DOCS_UPDATED` and stop.

Explore the repository structure to identify where documentation lives.
Different repos organize docs differently — look for dedicated doc
directories (`docs/`, `doc/`, `documentation/`), standalone files like
`README.md` at any level, and any other files whose primary purpose is
documentation.
directories, standalone files like README.md at any level, and any
other files whose primary purpose is documentation.

Search broadly:
Search broadly — include diagram and config files that live alongside
docs:

```bash
find . -type f \( -name "*.md" -o -name "*.rst" -o -name "*.adoc" -o -name "*.txt" \) \
! -path "./.git/*" ! -path "./.forge/*" ! -path "./vendor/*" ! -path "./node_modules/*" \
find . -type f \( -name "*.md" -o -name "*.rst" -o -name "*.adoc" \
-o -name "*.txt" -o -name "*.mmd" -o -name "*.puml" \
-o -name "*.yaml" -o -name "*.yml" \) \
! -path "./.git/*" ! -path "./.forge/*" ! -path "./vendor/*" \
! -path "./node_modules/*" \
| head -500
```

Expand All @@ -58,6 +59,9 @@ Then filter the results:
swagger output)
- **Exclude** changelog and release note entries that describe past
releases
- **Exclude** YAML/YML files that are not documentation. Only keep
YAML files that live in doc directories or are clearly documentation
examples.

If no documentation files exist in the repo, output `NO_DOCS_FOUND`
and stop.
Expand Down Expand Up @@ -88,7 +92,7 @@ Run the script in a single Bash call:

```bash
for id in "identifier1" "identifier2" "identifier3"; do
matches=$(grep -rl "$id" <doc_files> 2>/dev/null)
matches=$(grep -rlFi "$id" <doc_files> 2>/dev/null)
if [ -n "$matches" ]; then
echo "MATCH: $id -> $matches"
fi
Expand All @@ -102,25 +106,24 @@ are skipped.
From the script output, collect all matched doc files into a
candidate list.

### 5. Evaluate every candidate (two passes)
### 5. Evaluate candidates (two passes)

**Pass 1 — Quick scan.** For each candidate doc file from step 4,
view only the lines that matched the grep (use `grep -n` to see them
in context). Based on the matching lines alone, decide whether the
doc might be stale. Record a verdict for every candidate:
**Pass 1 — Quick scan.** For each candidate, view only the lines
that matched the grep (use `grep -n` to see them in context). Based
on the matching lines alone, give a quick verdict:

```
- path/to/doc.md -> possibly stale (describes behavior that changed)
- path/to/other.md -> not stale (mentions identifier in passing)
- path/to/another.md -> not stale (changelog entry)
...
```

Every candidate must have a verdict. Do not skip candidates.

**Pass 2 — Deep read.** For each candidate marked "possibly stale"
in pass 1, read the full file alongside the relevant section of the
diff. Confirm whether the doc is actually stale.
**Pass 2 — Deep read.** Only for candidates marked "possibly stale"
in pass 1. Read the full file alongside the relevant section of the
diff. Confirm whether the doc is actually stale. Check the file
header for auto-generation markers — if the file is generated from
source, skip it.

When evaluating:

Expand All @@ -131,19 +134,32 @@ When evaluating:
- **Do not flag changelog entries or release notes that describe past
releases.** Historical entries are not stale because the code evolved.

### 6. Update confirmed stale docs
### 6. Review beyond grep results

For each doc confirmed stale in pass 2:
This step catches stale references that grep cannot find because
documentation often uses prose names that differ from code identifiers,
and new code introduces identifiers that don't exist in any doc yet.

1. Read the full file
2. Make minimal targeted edits — fix only what the diff invalidated
3. Do NOT restructure, rewrite, or add content beyond what the code
change requires
4. Preserve the file's existing format, style, and structure
Always read the repository's README. Also read any main index or
overview files at the root of doc directories. Read each file fully
and compare it against the diff. Determine whether it describes any
behavior, flow, or feature that the code changes affected. If it
does, add it to the list of confirmed stale docs.

Also scan the discovered documentation files for any that may cover
the same area as the changed code, based on your understanding of
what the diff does. You don't need to read every file — use file
names and paths to decide which ones are worth checking.

### 7. Update confirmed stale docs

Update the documentation so it accurately reflects the new code.
For each doc confirmed stale:

1. Read the full file
2. Update the documentation so it accurately reflects the new code
3. Preserve the file's existing format, style, and structure

### 7. Commit changes
### 8. Commit changes

If any documentation files were updated:

Expand All @@ -154,7 +170,7 @@ git commit -m "[TICKET_KEY] docs: update documentation for code changes"

Replace TICKET_KEY with the actual ticket key from the task context.

### 8. Output
### 9. Output

If docs were updated:
```
Expand All @@ -177,11 +193,15 @@ NO_DOCS_FOUND

## Constraints

- **Only change what the diff affects.** Do not improve, restructure,
or reformat documentation that is unrelated to the code change.
- **Only change what the diff affects.** Fix stale content and add
new information that the diff introduced to existing docs. Do not
improve, restructure, or reformat documentation that is unrelated
to the code change.
- **Do not update historical entries.** Changelog and release note
entries for past releases are not stale — they describe what happened
at that point in time.
- **Skip auto-generated files.** Do not edit files that are generated
from source. They should be regenerated, not manually patched.
- **Update existing files only.** Do not create new doc files from
scratch.
- **Preserve format.** Match the existing file's formatting conventions
Expand Down
20 changes: 20 additions & 0 deletions src/forge/integrations/jira/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -943,6 +943,26 @@ async def get_project_default_repo(self, project_key: str) -> str:
logger.info(f"Project {project_key}: default repo: {value}")
return value

async def get_project_docs_repo(self, project_key: str) -> str | None:
"""Fetch the optional forge.docs_repo project property.

Args:
project_key: The Jira project key.

Returns:
Repo string in "owner/repo" format, or None if not set.
"""
value = await self.get_project_property(project_key, "forge.docs_repo")
if value is None:
return None
if not isinstance(value, str) or "/" not in value:
logger.warning(
f"forge.docs_repo for project {project_key} is malformed: {value!r}, ignoring"
)
return None
logger.info(f"Project {project_key}: docs repo: {value}")
return value

async def get_skills_config(self, project_key: str) -> list[SkillEntry] | None:
"""Fetch and parse the forge.skills project property.

Expand Down
15 changes: 15 additions & 0 deletions src/forge/prompts/v1/update-docs-separate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Update documentation files that have become stale due to code changes in a separate repository.

## Workspace

{workspace_path}

The documentation repository is mounted at `/workspace` (read-write). The code repository is mounted at `/code-repo` (read-only).

Run `cd /code-repo && git diff origin/main...HEAD` to see the code changes, and check `/code-repo/.forge/handoff.md` for context on what was implemented and why.

Then follow the update-docs skill process to find and update any documentation files in `/workspace` whose content has become incorrect due to the code changes.

## Project Guidelines

{guardrails}
15 changes: 14 additions & 1 deletion src/forge/sandbox/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ def _build_podman_command(
config: ContainerConfig,
container_name: str,
ticket_key: str | None = None,
extra_mounts: list[tuple[Path, str]] | None = None,
) -> list[str]:
"""Build the podman run command."""

Expand Down Expand Up @@ -272,6 +273,11 @@ def _build_podman_command(
"/workspace",
]

# Mount extra volumes (e.g., code repo alongside docs workspace)
if extra_mounts:
for host_path, container_path in extra_mounts:
cmd.extend(["-v", f"{host_path}:{container_path}:ro,Z"])

# Mount gcloud credentials for Vertex AI authentication
if self.settings.use_vertex_ai:
gcloud_creds = self._get_gcloud_credentials_path()
Expand Down Expand Up @@ -324,6 +330,7 @@ async def run(
task_key: str | None = None,
repo_name: str | None = None,
previous_task_keys: list[str] | None = None,
extra_mounts: list[tuple[Path, str]] | None = None,
) -> ContainerResult:
"""Run a task in a container sandbox.

Expand All @@ -336,6 +343,7 @@ async def run(
task_key: Jira task key being implemented.
repo_name: Repository name (e.g., "owner/repo") for container naming.
previous_task_keys: List of previously implemented task keys for handoff context.
extra_mounts: Additional read-only volume mounts as (host_path, container_path) tuples.

Returns:
ContainerResult with execution status and logs.
Expand All @@ -358,7 +366,12 @@ async def run(
# Build container name and command
container_name = self._build_container_name(ticket_key, repo_name)
cmd = self._build_podman_command(
workspace_path, task_file, config, container_name, ticket_key
workspace_path,
task_file,
config,
container_name,
ticket_key,
extra_mounts=extra_mounts,
)

logger.info(f"Starting container {container_name} for task: {task_summary}")
Expand Down
8 changes: 7 additions & 1 deletion src/forge/workflow/bug/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
)
from forge.workflow.nodes.rebase import rebase_pr
from forge.workflow.nodes.triage import route_triage_gate, triage_check, triage_gate
from forge.workflow.nodes.update_docs_repo import update_docs_repo
from forge.workflow.nodes.workspace_setup import setup_workspace
from forge.workflow.utils import resolve_shared_resume_node

Expand Down Expand Up @@ -98,6 +99,8 @@ def route_entry(state: BugState) -> str:
return "decompose_plan"
elif current_node == "post_merge_summary":
return "post_merge_summary"
elif current_node == "update_docs_repo":
return "update_docs_repo"
elif current_node == "setup_workspace":
return "setup_workspace"
elif current_node == "implement_bug_fix":
Expand Down Expand Up @@ -362,6 +365,7 @@ def build_bug_graph() -> StateGraph:

# ── Post-merge ──
graph.add_node("post_merge_summary", post_merge_summary)
graph.add_node("update_docs_repo", update_docs_repo)

# ── Q&A ──
graph.add_node("answer_question", answer_question)
Expand Down Expand Up @@ -405,6 +409,7 @@ def build_bug_graph() -> StateGraph:
"regenerate_plan": "regenerate_plan",
"decompose_plan": "decompose_plan",
"post_merge_summary": "post_merge_summary",
"update_docs_repo": "update_docs_repo",
"setup_workspace": "setup_workspace",
"implement_bug_fix": "implement_bug_fix",
"local_review": "local_review",
Expand Down Expand Up @@ -626,6 +631,7 @@ def build_bug_graph() -> StateGraph:
)

# ── Post-merge terminal ──
graph.add_edge("post_merge_summary", END)
graph.add_edge("post_merge_summary", "update_docs_repo")
graph.add_edge("update_docs_repo", END)

return graph
3 changes: 3 additions & 0 deletions src/forge/workflow/bug/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ class BugState(
bug_fix_implemented: bool
tdd_approach: bool

# Documentation
docs_pr_url: str | None

# Q&A mode
qa_history: list[dict[str, str]] # List of {question, answer, artifact_type, timestamp}
generation_context: dict[str, Any] # Stored context from generation
Expand Down
Loading
Loading