diff --git a/README.md b/README.md index 2f123e58..dc77db87 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ Each invocation creates a deterministic thread ID, so follow-up messages on the **Trigger tags (Sea Haven fork):** a mention is a case-insensitive substring match on the comment body — `@openswe`, `@open-swe`, `@openswe-dev`, or `@seahaven-openswe` (the deployed App slug). GitHub won't linkify `@seahaven-openswe` (App `[bot]` accounts aren't user-mentionable), but the text still fires a run. -**Engineering conventions & attribution (Sea Haven fork):** the main agent's system prompt is tuned to the Sea Haven engineering handbook — branch names are `feature|bug|hotfix/` (optional resolvable `-` prefix), PR titles are imperative with no conventional-commit `type:` prefix, PR bodies use `## Summary / Validation / Tests / Notes`, and commit messages follow the handbook format (≤50-char imperative subject, *why* over *what*). **No agent/AI attribution is added to any artifact** — no `Co-authored-by` bot trailer, no `Made by [Open SWE]` footer, no "generated by an agent" notes. Commits are currently authored as the **triggering user** (the upstream behavior, which keeps Vercel preview deploys resolvable); flipping authorship to the bot account is tracked separately in issue #11 pending the Vercel-resolvability decision. +**Engineering conventions & attribution (Sea Haven fork):** the main agent's system prompt is tuned to the Sea Haven engineering handbook — branch names are `feature|bug|hotfix/` (optional resolvable `-` prefix), PR bodies use `## Summary / Validation / Tests / Notes`, and commit messages follow the handbook format (≤50-char imperative subject, *why* over *what*). The **PR title rule is repo-aware**: when the target repo enforces a conventional-commit title (an `amannn/action-semantic-pull-request` workflow, a `commitlint` config, or a documented requirement in `AGENTS.md` / `CONTRIBUTING.md`), the agent emits a conforming `type(scope): …` title that reads the action's allowed types/scopes — this lets it pass gates like this repo's own `PR Title Lint` and upstream `langchain-ai/open-swe` without manual retitling; otherwise it falls back to the Sea Haven imperative style with no `type:` prefix. PRs that resolve a GitHub issue **auto-link it** in the body (`Closes #` for full fixes, `Refs #`/`Part of #` for partial work, `Closes owner/repo#` cross-repo); because the Sea Haven flow targets `dev` rather than the default branch, the issue closes when `dev` is promoted, not at dev-merge. **No agent/AI attribution is added to any artifact** — no `Co-authored-by` bot trailer, no `Made by [Open SWE]` footer, no "generated by an agent" notes. Commits are currently authored as the **triggering user** (the upstream behavior, which keeps Vercel preview deploys resolvable); flipping authorship to the bot account is tracked separately in issue #11 pending the Vercel-resolvability decision. ### 7. Validation — Prompt-Driven diff --git a/agent/prompt.py b/agent/prompt.py index ffe9c03d..d918a27f 100644 --- a/agent/prompt.py +++ b/agent/prompt.py @@ -394,7 +394,16 @@ When you have completed your implementation, follow these steps in order: - **Open a new PR** with the `open_pull_request` tool (pass `owner`, `repo`, `head` = your branch, `base`, `title`, `body`). This attributes the PR to the triggering user. Push the branch BEFORE calling it. - **Update an existing PR** (edit the body, mark ready for review, etc.) with `GH_TOKEN=dummy gh pr edit`. If a PR already exists for the branch (including one the user pasted in), do NOT open a duplicate — `open_pull_request` returns the existing PR's URL, so switch to `gh pr edit`. For follow-up changes, add a new commit on top of the existing branch history. - **PR Title** (under 70 characters): imperative mood, capitalized, describing the change — not the ticket. Do NOT use a conventional-commit `type:` prefix (no `feat:`/`fix:`/`chore:`). When a ticket key is resolvable from the run context, prefix it in square brackets; otherwise omit it entirely: + **PR Title** (under 70 characters): the title rule is **repo-aware** — first detect whether the target repo enforces a conventional-commit PR title, then pick the matching style. The repo is already cloned, so this check is cheap. + + *Detect a conventional-commit title gate* — the repo enforces one if ANY of these hold: + - a workflow under `.github/workflows/` references `amannn/action-semantic-pull-request` (or any `semantic-pull-request` action); + - a `commitlint` config wired to PR titles (`commitlint.config.*`, `.commitlintrc*`, or a `commitlint` key in `package.json`); + - `AGENTS.md` / `CONTRIBUTING.md` states a conventional-commit title requirement. + + *If a gate is enforced* → emit a conventional-commit title `type(scope): description` and conform to the action's configuration. This **overrides** the Sea Haven no-`type:`-prefix default. Open the workflow (e.g. `.github/workflows/pr_lint.yml`) and read the allowed `types`/`scopes` so you stay inside them; if `requireScope` is false, a scope is optional. Map the work to a type: new functionality → `feat`, defect fix → `fix`, infra/CI → `ci`/`build`/`chore`, docs → `docs`, tests → `test`, refactor → `refactor`, perf → `perf`. Examples: `feat: add retry logic for transient upstream failures` or `fix(deps): pin langgraph-cli`. Do NOT rely on an escape-hatch label (e.g. `ignore-lint-pr-title`) to dodge the check — conform to the title instead. (Note: this repo's own `PR Title Lint` and upstream `langchain-ai/open-swe` both enforce this — emit a conforming `type:` title for them.) + + *If no gate is enforced* → use the Sea Haven imperative style: imperative mood, capitalized, describing the change — not the ticket. Do NOT use a conventional-commit `type:` prefix (no `feat:`/`fix:`/`chore:`). When a ticket key is resolvable from the run context, prefix it in square brackets; otherwise omit it entirely: ``` [] Add retry logic for transient upstream failures ``` @@ -415,6 +424,13 @@ When you have completed your implementation, follow these steps in order: ``` + **Link the GitHub issue the PR resolves** — when the run originates from (or fully fixes) a GitHub issue, add a closing keyword to the PR body so merging auto-closes the issue. The issue number is usually in-context: issue-triggered runs receive a `## GitHub Issue: #` line; for Slack/Linear-triggered runs that fix a GitHub issue, pick `#` up from the task text. + - When the PR **fully resolves** a GitHub issue in the **same repo**, add a dedicated trailing line in `## Summary` (or its own line at the end of the body): `Closes #`. GitHub recognizes `Closes`/`Fixes`/`Resolves #` anywhere in the body. + - When the PR only **partially** addresses an issue (more work remains), use a **non-closing** reference so the issue stays open: `Refs #` or `Part of #`. + - **Cross-repo**: if the issue lives in a different repo, use the fully-qualified form: `Closes owner/repo#` (or `Refs owner/repo#` for partial). + - This is the GitHub-issue analog of the Linear `Refs: ` commit trailer — placed in the PR body where GitHub's auto-close looks. + - **Default-branch caveat (don't mistake this for a bug):** GitHub only auto-closes the linked issue when the PR merges into the repo's **default branch**. In the Sea Haven flow the agent targets `dev`, not the default branch, so `Closes #` will **not** close the issue at dev-merge time — it closes when `dev` is promoted to the default branch. The link still renders, and the issue closes on promotion; this is the correct, expected outcome. On repos where the agent targets the default branch directly, it closes on merge as usual. + You don't need to add links back to the originating Slack thread or Linear ticket — for private repos, `open_pull_request` appends a `## References` section automatically. When the target repo is public, don't reference private repos or private PR/issue numbers in the description. @@ -426,6 +442,8 @@ When you have completed your implementation, follow these steps in order: - No generic subjects ("Fix stuff", "Update code", "WIP", "Address review comments") and no self-referential phrasing ("This commit…", "This PR…", "I refactored…"). - When a ticket key is resolvable, add a `Refs: ` trailer (combine with `#` when both apply); otherwise omit the trailer. + This per-commit convention is independent of the repo-aware **PR title** rule above. On a repo that requires conventional PR titles **and** squash-merges, the squash commit subject becomes the PR title (e.g. `feat: …`) and so diverges from this imperative-no-prefix commit style — that's an acceptable tradeoff (the target repo's title lint wins), not a contradiction. Your own per-commit subjects still follow the Sea Haven format here. + **IMPORTANT: For code-change tasks, never ask the user for permission or confirmation before pushing commits or opening/updating a draft PR. Do not say "if you want, I can proceed" or "shall I open the PR?". When implementation is done and checks pass, push autonomously, and open/update a draft PR autonomously when requested, necessary, or required by the Always Create PRs dashboard setting.** **IMPORTANT: If you made commits directly via `git commit` or `git revert` in the sandbox, you MUST push those commits to GitHub. Never report the work as done without pushing.** diff --git a/tests/test_github_comment_prompts.py b/tests/test_github_comment_prompts.py index b5ebeeeb..2d22717b 100644 --- a/tests/test_github_comment_prompts.py +++ b/tests/test_github_comment_prompts.py @@ -195,6 +195,29 @@ def test_construct_system_prompt_uses_sea_haven_conventions() -> None: assert "## Release Note" not in prompt +def test_construct_system_prompt_links_resolved_github_issue() -> None: + prompt = construct_system_prompt(working_dir="/workspace") + + # Part A: PRs that resolve a GitHub issue auto-link it for auto-close. + assert "Closes #" in prompt + assert "Refs #" in prompt or "Part of #" in prompt + assert "Closes owner/repo#" in prompt + # The default-branch auto-close caveat must be stated so it isn't read as a bug. + assert "default branch" in prompt + assert "promoted" in prompt or "promotion" in prompt + + +def test_construct_system_prompt_pr_title_rule_is_repo_aware() -> None: + prompt = construct_system_prompt(working_dir="/workspace") + + # Part B: detect a conventional-commit title gate and conform to it... + assert "amannn/action-semantic-pull-request" in prompt + assert "repo-aware" in prompt + assert "type(scope): description" in prompt or "type(scope): …" in prompt + # ...without dropping the no-prefix default for repos that don't enforce one. + assert "Do NOT use a conventional-commit `type:` prefix" in prompt + + def test_construct_system_prompt_shell_escapes_user_name() -> None: import shlex