diff --git a/CLAUDE.md b/CLAUDE.md index 0eaf76bb..f19f9201 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -144,7 +144,8 @@ Opt-in per project via Jira project property. When configured, Forge opens a PR |----------|---------|-------------| | `forge.prd_proposals_repo` | `org/enhancement-proposals` | Enables PR-based PRD approval for this project | -Set via: `jira project-property set forge.prd_proposals_repo "owner/repo"` +Set via: `forge project-setup --prd-proposals-repo owner/repo` +Remove via: `forge project-setup --prd-proposals-repo ""` **Global fallbacks (`.env`, used when `FORGE_REQUIRE_PROJECT_CONFIG=false`):** diff --git a/src/forge/cli.py b/src/forge/cli.py index 4ea03860..3b7497d2 100644 --- a/src/forge/cli.py +++ b/src/forge/cli.py @@ -523,6 +523,23 @@ async def cmd_project_setup(args: argparse.Namespace) -> int: await jira.set_project_property(project_key, "forge.default_repo", args.default_repo) print(f"[OK] forge.default_repo = {args.default_repo!r}") + # forge.prd_proposals_repo — opt-in / opt-out for PRD approval via GitHub PR + if args.prd_proposals_repo is not None: + if args.prd_proposals_repo == "": + await jira.delete_project_property(project_key, "forge.prd_proposals_repo") + print("[OK] forge.prd_proposals_repo removed (PRD approval via Jira labels)") + else: + if "/" not in args.prd_proposals_repo: + print( + f"Error: --prd-proposals-repo must be owner/repo, got: {args.prd_proposals_repo!r}", + file=sys.stderr, + ) + return 1 + await jira.set_project_property( + project_key, "forge.prd_proposals_repo", args.prd_proposals_repo + ) + print(f"[OK] forge.prd_proposals_repo = {args.prd_proposals_repo!r}") + # forge.skills — built from --add-skill flags and/or --skills-config JSON skill_entries: list[dict] = [] @@ -873,6 +890,15 @@ def main() -> int: metavar="OWNER/REPO", help="Primary GitHub repo (sets forge.default_repo)", ) + setup_parser.add_argument( + "--prd-proposals-repo", + metavar="OWNER/REPO", + default=None, + help=( + "Enhancement proposals repo for PR-based PRD approval " + "(sets forge.prd_proposals_repo). Pass empty string to disable." + ), + ) setup_parser.add_argument( "--add-skill", action="append", diff --git a/src/forge/integrations/jira/client.py b/src/forge/integrations/jira/client.py index 0545e914..f35be56f 100644 --- a/src/forge/integrations/jira/client.py +++ b/src/forge/integrations/jira/client.py @@ -897,6 +897,21 @@ async def set_project_property(self, project_key: str, property_key: str, value: response.raise_for_status() _project_property_cache.pop((project_key, property_key), None) + async def delete_project_property(self, project_key: str, property_key: str) -> None: + """Delete a Jira project property. + + Args: + project_key: The Jira project key (e.g., "MYPROJ"). + property_key: The property key (e.g., "forge.prd_proposals_repo"). + """ + client = await self._get_client() + response = await client.delete( + f"/project/{project_key}/properties/{property_key}", + ) + if response.status_code != 404: + response.raise_for_status() + _project_property_cache.pop((project_key, property_key), None) + async def get_project_repos(self, project_key: str) -> list[str]: """Fetch the forge.repos project property.