mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-07 16:19:09 +00:00
Merge branch 'dev' into deps/pyjwt-2.13.0-hs256-fix
This commit is contained in:
commit
2fea80a814
23 changed files with 202 additions and 9629 deletions
59
.github/dependabot.yml
vendored
59
.github/dependabot.yml
vendored
|
|
@ -1,52 +1,45 @@
|
||||||
version: 2
|
version: 2
|
||||||
updates:
|
updates:
|
||||||
|
# Python — uv (open-swe-specific; siblings use pip)
|
||||||
- package-ecosystem: "uv"
|
- package-ecosystem: "uv"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "monthly"
|
interval: "weekly"
|
||||||
|
assignees: ["amoussa1229"]
|
||||||
groups:
|
groups:
|
||||||
minor-and-patch:
|
minor-and-patch:
|
||||||
patterns:
|
update-types: ["minor", "patch"]
|
||||||
- "*"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
major:
|
|
||||||
patterns:
|
|
||||||
- "*"
|
|
||||||
update-types:
|
|
||||||
- "major"
|
|
||||||
|
|
||||||
|
# JavaScript/TypeScript — CDK (/infra), Playwright (/tests/e2e), dashboard (/ui), root tooling
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directories:
|
||||||
|
- "/"
|
||||||
|
- "/infra"
|
||||||
|
- "/tests/e2e"
|
||||||
|
- "/ui"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
assignees: ["amoussa1229"]
|
||||||
|
groups:
|
||||||
|
minor-and-patch:
|
||||||
|
update-types: ["minor", "patch"]
|
||||||
|
|
||||||
|
# Docker — root Dockerfile
|
||||||
- package-ecosystem: "docker"
|
- package-ecosystem: "docker"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "monthly"
|
interval: "weekly"
|
||||||
|
assignees: ["amoussa1229"]
|
||||||
groups:
|
groups:
|
||||||
minor-and-patch:
|
minor-and-patch:
|
||||||
patterns:
|
update-types: ["minor", "patch"]
|
||||||
- "*"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
major:
|
|
||||||
patterns:
|
|
||||||
- "*"
|
|
||||||
update-types:
|
|
||||||
- "major"
|
|
||||||
|
|
||||||
|
# GitHub Actions
|
||||||
- package-ecosystem: "github-actions"
|
- package-ecosystem: "github-actions"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "monthly"
|
interval: "weekly"
|
||||||
|
assignees: ["amoussa1229"]
|
||||||
groups:
|
groups:
|
||||||
minor-and-patch:
|
minor-and-patch:
|
||||||
patterns:
|
update-types: ["minor", "patch"]
|
||||||
- "*"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
major:
|
|
||||||
patterns:
|
|
||||||
- "*"
|
|
||||||
update-types:
|
|
||||||
- "major"
|
|
||||||
|
|
|
||||||
10
.github/scripts/check-dev-green.sh
vendored
10
.github/scripts/check-dev-green.sh
vendored
|
|
@ -4,7 +4,7 @@
|
||||||
# Reads check-runs on stdin — one
|
# Reads check-runs on stdin — one
|
||||||
# name<US>status<US>conclusion<US>details_url
|
# name<US>status<US>conclusion<US>details_url
|
||||||
# per line, fields separated by ASCII Unit Separator (0x1F) — so it is unit-testable
|
# per line, fields separated by ASCII Unit Separator (0x1F) — so it is unit-testable
|
||||||
# WITHOUT GitHub. promote_dev_to_prod.yml pipes the live `gh api .../check-runs`
|
# WITHOUT GitHub. promote-dev-to-prod.yml pipes the live `gh api .../check-runs`
|
||||||
# output in. 0x1F (not TAB) is used deliberately: TAB is IFS-whitespace, so an empty
|
# output in. 0x1F (not TAB) is used deliberately: TAB is IFS-whitespace, so an empty
|
||||||
# conclusion (every in_progress check has a null conclusion) would collapse and shift
|
# conclusion (every in_progress check has a null conclusion) would collapse and shift
|
||||||
# the columns — which would make the promote run fail to exclude itself. 0x1F is
|
# the columns — which would make the promote run fail to exclude itself. 0x1F is
|
||||||
|
|
@ -26,12 +26,12 @@
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
EXCLUDE_RUN_ID="${EXCLUDE_RUN_ID:-}"
|
EXCLUDE_RUN_ID="${EXCLUDE_RUN_ID:-}"
|
||||||
# Mandatory checks (one per line). Defaults to the Agent CI suite, which runs on
|
# Mandatory checks (one per line). Defaults to the CI suite, which runs on
|
||||||
# every push to dev (see ci.yml). Keep in sync with those job names; if a name
|
# every push to dev (see ci.yml). Keep in sync with those job names; if a name
|
||||||
# drifts the gate blocks (fails safe) until the list is updated.
|
# drifts the gate blocks (fails safe) until the list is updated.
|
||||||
REQUIRED_CHECKS="${REQUIRED_CHECKS:-Agent lint
|
REQUIRED_CHECKS="${REQUIRED_CHECKS:-Lint
|
||||||
Agent format check
|
Format check
|
||||||
Agent unit tests
|
Unit tests
|
||||||
Playwright E2E}"
|
Playwright E2E}"
|
||||||
|
|
||||||
declare -A GREEN
|
declare -A GREEN
|
||||||
|
|
|
||||||
4
.github/workflows/build-artifacts.yml
vendored
4
.github/workflows/build-artifacts.yml
vendored
|
|
@ -62,7 +62,7 @@ jobs:
|
||||||
BUCKET: open-swe-dev-assets
|
BUCKET: open-swe-dev-assets
|
||||||
DEPLOY_DOC: open-swe-dev-deploy
|
DEPLOY_DOC: open-swe-dev-deploy
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- uses: oven-sh/setup-bun@v2
|
- uses: oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
bun-version: latest
|
bun-version: latest
|
||||||
|
|
@ -100,7 +100,7 @@ jobs:
|
||||||
BUCKET: open-swe-prod-assets
|
BUCKET: open-swe-prod-assets
|
||||||
DEPLOY_DOC: open-swe-prod-deploy
|
DEPLOY_DOC: open-swe-prod-deploy
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- uses: oven-sh/setup-bun@v2
|
- uses: oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
bun-version: latest
|
bun-version: latest
|
||||||
|
|
|
||||||
4
.github/workflows/cd-infra.yml
vendored
4
.github/workflows/cd-infra.yml
vendored
|
|
@ -66,7 +66,7 @@ jobs:
|
||||||
id-token: write
|
id-token: write
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "24"
|
node-version: "24"
|
||||||
|
|
@ -102,7 +102,7 @@ jobs:
|
||||||
id-token: write
|
id-token: write
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "24"
|
node-version: "24"
|
||||||
|
|
|
||||||
2
.github/workflows/ci-infra.yml
vendored
2
.github/workflows/ci-infra.yml
vendored
|
|
@ -1,6 +1,6 @@
|
||||||
name: Infra CI
|
name: Infra CI
|
||||||
|
|
||||||
# Path-filtered CI for the /infra CDK app (TypeScript). The existing "Agent CI"
|
# Path-filtered CI for the /infra CDK app (TypeScript). The existing "CI"
|
||||||
# (ci.yml) covers the Python agent; this adds tsc + jest + cdk synth for /infra so
|
# (ci.yml) covers the Python agent; this adds tsc + jest + cdk synth for /infra so
|
||||||
# infra changes are gated on a PR the same way. Runs only when /infra changes.
|
# infra changes are gated on a PR the same way. Runs only when /infra changes.
|
||||||
|
|
||||||
|
|
|
||||||
26
.github/workflows/ci.yml
vendored
26
.github/workflows/ci.yml
vendored
|
|
@ -1,11 +1,11 @@
|
||||||
name: Agent CI
|
name: CI
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
# dev as well as main so every dev HEAD carries the full Agent CI signal that
|
# dev as well as main so every dev HEAD carries the full CI signal that
|
||||||
# the dev->main promotion gate (check-dev-green.sh) reads. PR checks alone are
|
# the dev->main promotion gate (check-dev-green.sh) reads. PR checks alone are
|
||||||
# not enough: an admin-merge can land a red PR onto dev.
|
# not enough: an admin-merge can land a red PR onto dev.
|
||||||
branches: ["main", "dev"]
|
branches: ["main", "dev"]
|
||||||
|
|
@ -18,33 +18,33 @@ concurrency:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
name: Agent lint
|
name: Lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: uv sync --locked --extra dev
|
run: uv sync --locked --extra dev
|
||||||
- name: Run lint
|
- name: Run lint
|
||||||
run: make lint
|
run: make lint
|
||||||
|
|
||||||
format:
|
format:
|
||||||
name: Agent format check
|
name: Format check
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: uv sync --locked --extra dev
|
run: uv sync --locked --extra dev
|
||||||
- name: Run format check
|
- name: Run format check
|
||||||
run: make format-check
|
run: make format-check
|
||||||
|
|
||||||
unit-tests:
|
unit-tests:
|
||||||
name: Agent unit tests
|
name: Unit tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: uv sync --locked --extra dev
|
run: uv sync --locked --extra dev
|
||||||
- name: Run unit tests
|
- name: Run unit tests
|
||||||
|
|
@ -55,8 +55,8 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
|
|
|
||||||
15
.github/workflows/dependency-review.yml
vendored
Normal file
15
.github/workflows/dependency-review.yml
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
name: Dependency Review
|
||||||
|
|
||||||
|
on: pull_request
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
dependency-review:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
- uses: actions/dependency-review-action@v4
|
||||||
|
with:
|
||||||
|
fail-on-severity: high
|
||||||
13
.github/workflows/labeler.yml
vendored
Normal file
13
.github/workflows/labeler.yml
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
name: Labeler
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
issues: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
labeler:
|
||||||
|
uses: Sea-Haven-Industries/.github/.github/workflows/callable-labeler.yaml@main
|
||||||
|
|
@ -19,7 +19,7 @@ jobs:
|
||||||
contents: write
|
contents: write
|
||||||
checks: read
|
checks: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
ref: dev
|
ref: dev
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
@ -78,8 +78,8 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 360
|
timeout-minutes: 360
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: uv sync --locked
|
run: uv sync --locked
|
||||||
- name: Run reviewer eval
|
- name: Run reviewer eval
|
||||||
4
.github/workflows/rollback.yml
vendored
4
.github/workflows/rollback.yml
vendored
|
|
@ -50,7 +50,7 @@ jobs:
|
||||||
DEPLOY_DOC: open-swe-dev-deploy
|
DEPLOY_DOC: open-swe-dev-deploy
|
||||||
TARGET_SHA: ${{ inputs.sha }}
|
TARGET_SHA: ${{ inputs.sha }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- uses: aws-actions/configure-aws-credentials@v6
|
- uses: aws-actions/configure-aws-credentials@v6
|
||||||
with:
|
with:
|
||||||
role-to-assume: ${{ vars.AWS_DEPLOY_ROLE_APP_DEV }}
|
role-to-assume: ${{ vars.AWS_DEPLOY_ROLE_APP_DEV }}
|
||||||
|
|
@ -75,7 +75,7 @@ jobs:
|
||||||
DEPLOY_DOC: open-swe-prod-deploy
|
DEPLOY_DOC: open-swe-prod-deploy
|
||||||
TARGET_SHA: ${{ inputs.sha }}
|
TARGET_SHA: ${{ inputs.sha }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- uses: aws-actions/configure-aws-credentials@v6
|
- uses: aws-actions/configure-aws-credentials@v6
|
||||||
with:
|
with:
|
||||||
role-to-assume: ${{ vars.AWS_DEPLOY_ROLE_APP_PROD }}
|
role-to-assume: ${{ vars.AWS_DEPLOY_ROLE_APP_PROD }}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
FROM python:3.14.5-slim-trixie
|
FROM python:3.14.6-slim-trixie
|
||||||
|
|
||||||
ARG DOCKER_CLI_VERSION=5:29.1.5-1~debian.13~trixie
|
ARG DOCKER_CLI_VERSION=5:29.1.5-1~debian.13~trixie
|
||||||
ARG NODEJS_VERSION=22.22.0-1nodesource1
|
ARG NODEJS_VERSION=22.22.0-1nodesource1
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,10 @@ All three companies in the article converge on **Slack as the primary invocation
|
||||||
|
|
||||||
Each invocation creates a deterministic thread ID, so follow-up messages on the same issue or thread route to the same running agent.
|
Each invocation creates a deterministic thread ID, so follow-up messages on the same issue or thread route to the same running agent.
|
||||||
|
|
||||||
|
**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/<kebab-desc>` (optional resolvable `<KEY>-` 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 #<n>` for full fixes, `Refs #<n>`/`Part of #<n>` for partial work, `Closes owner/repo#<n>` 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
|
### 7. Validation — Prompt-Driven
|
||||||
|
|
||||||
The agent is instructed to run linters, formatters, and tests before committing, and is responsible end-to-end for committing, pushing, opening/updating the draft PR, and replying in the source channel.
|
The agent is instructed to run linters, formatters, and tests before committing, and is responsible end-to-end for committing, pushing, opening/updating the draft PR, and replying in the source channel.
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ from .utils.authorship import (
|
||||||
OPEN_SWE_BOT_EMAIL,
|
OPEN_SWE_BOT_EMAIL,
|
||||||
OPEN_SWE_BOT_NAME,
|
OPEN_SWE_BOT_NAME,
|
||||||
CollaboratorIdentity,
|
CollaboratorIdentity,
|
||||||
build_pr_attribution_footer,
|
|
||||||
)
|
)
|
||||||
from .utils.github_comments import UNTRUSTED_GITHUB_COMMENT_OPEN_TAG
|
from .utils.github_comments import UNTRUSTED_GITHUB_COMMENT_OPEN_TAG
|
||||||
|
|
||||||
|
|
@ -159,7 +158,12 @@ Before starting any task that requires code changes, set up the repository in yo
|
||||||
|
|
||||||
This sets the author of every commit you make. This is required for CI: third-party integrations (e.g. Vercel preview deploys) reject commits whose author email cannot be resolved to a GitHub account, and this email resolves. Do NOT set any other identity, do NOT pass `--author` to `git commit`, and do NOT export `GIT_AUTHOR_*` / `GIT_COMMITTER_*` env vars.
|
This sets the author of every commit you make. This is required for CI: third-party integrations (e.g. Vercel preview deploys) reject commits whose author email cannot be resolved to a GitHub account, and this email resolves. Do NOT set any other identity, do NOT pass `--author` to `git commit`, and do NOT export `GIT_AUTHOR_*` / `GIT_COMMITTER_*` env vars.
|
||||||
|
|
||||||
4. **Choose your branch** — Use a thread-stable branch name such as `open-swe/<short-task-slug>`. If a branch already exists for this thread/task, fetch and check it out instead of creating a new one.
|
4. **Choose your branch** — Use a Sea Haven branch name: `<prefix>/<description>`, all kebab-case. Pick the prefix by the kind of work:
|
||||||
|
- `feature/` — new functionality or an enhancement
|
||||||
|
- `bug/` — a defect caught before it reaches production
|
||||||
|
- `hotfix/` — a fix for a production-impacting issue
|
||||||
|
|
||||||
|
Keep `<description>` short and kebab-case (e.g. `feature/add-receipt-parser`). When a ticket key is resolvable from the run context, put it first: `feature/<KEY>-add-receipt-parser`; if no key is resolvable, omit it. Never commit directly to `main`. Keep the branch thread-stable: if a branch already exists for this thread/task, fetch and check it out instead of creating a new one.
|
||||||
|
|
||||||
5. **Checkout your branch** — Always fetch and checkout your branch before making any changes. When reusing an existing remote branch, start from `origin/<branch>` rather than recreating the branch from the base branch; this preserves prior commits for review.
|
5. **Checkout your branch** — Always fetch and checkout your branch before making any changes. When reusing an existing remote branch, start from `origin/<branch>` rather than recreating the branch from the base branch; this preserves prior commits for review.
|
||||||
|
|
||||||
|
|
@ -390,31 +394,55 @@ 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.
|
- **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.
|
- **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):
|
**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.
|
||||||
```
|
|
||||||
<type>: <concise description> [closes <TICKET>]
|
|
||||||
```
|
|
||||||
Where type is one of: `fix` (bug fix), `feat` (new feature), `chore` (maintenance), `ci` (CI/CD).
|
|
||||||
Always append the resolvable ticket number in square brackets at the end of the title (e.g. `fix: handle null session [closes AB-000]`). Resolve the ticket from the Linear-triggered run when present (`{linear_project_id}-{linear_issue_number}`), or from a Linear ticket referenced in the Slack thread / task context. If no ticket number is resolvable, omit the bracketed suffix entirely.
|
|
||||||
|
|
||||||
**PR Body** (keep under 10 lines total. the more concise the better):
|
*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);
|
||||||
## Description
|
- a `commitlint` config wired to PR titles (`commitlint.config.*`, `.commitlintrc*`, or a `commitlint` key in `package.json`);
|
||||||
<1-3 sentences on WHY and the approach.
|
- `AGENTS.md` / `CONTRIBUTING.md` states a conventional-commit title requirement.
|
||||||
NO "Changes:" section — file changes are already in the commit history.>
|
|
||||||
|
|
||||||
## Release Note
|
*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.)
|
||||||
<One-line changelog summary for self-hosted customers, or "none" for internal/CI/test/refactor changes.>
|
|
||||||
|
|
||||||
## Test Plan
|
*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:
|
||||||
- [ ] <new/novel verification steps only — NOT "run existing tests" or "verify existing behavior">
|
|
||||||
```
|
```
|
||||||
|
[<KEY>] Add retry logic for transient upstream failures
|
||||||
|
```
|
||||||
|
With no resolvable key, use just the imperative description: `Add retry logic for transient upstream failures`. Resolve the key from the Linear-triggered run when present (`{linear_project_id}-{linear_issue_number}`), or from a Linear ticket referenced in the Slack thread / task context.
|
||||||
|
|
||||||
|
**PR Body** — use this structure. Omit a section only when it would be empty:
|
||||||
|
```
|
||||||
|
## Summary
|
||||||
|
<What changed and why — 1-3 sentences. Explain the motivation, not just the diff.>
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
<How you verified it works — commands run, steps taken, screenshots if UI.>
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
<What tests were added, updated, or run. If no automated tests, explain manual testing.>
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
<Anything reviewers should know — migration steps, deploy order, follow-ups, breaking changes. Omit this section if empty.>
|
||||||
|
```
|
||||||
|
|
||||||
|
**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: #<n>` line; for Slack/Linear-triggered runs that fix a GitHub issue, pick `#<n>` 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 #<n>`. GitHub recognizes `Closes`/`Fixes`/`Resolves #<n>` 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 #<n>` or `Part of #<n>`.
|
||||||
|
- **Cross-repo**: if the issue lives in a different repo, use the fully-qualified form: `Closes owner/repo#<n>` (or `Refs owner/repo#<n>` for partial).
|
||||||
|
- This is the GitHub-issue analog of the Linear `Refs: <KEY>` 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 #<n>` 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.
|
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.
|
When the target repo is public, don't reference private repos or private PR/issue numbers in the description.
|
||||||
|
|
||||||
**Commit message**: Concise, focusing on the "why" rather than the "what". If not provided, the PR title is used.
|
**Commit message** — follow the Sea Haven format:
|
||||||
|
- Imperative mood, capitalized first letter (e.g. "Add retry logic", not "Added retry logic" or "adds retry logic").
|
||||||
|
- Subject line ≤50 characters. If you need more, add a blank line and a body wrapped at 72 characters.
|
||||||
|
- Explain *why*, not *what* — the diff already shows what changed.
|
||||||
|
- 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: <KEY>` trailer (combine with `#<issue>` 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: 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.**
|
||||||
|
|
||||||
|
|
@ -448,23 +476,17 @@ For code-change tasks, push the branch and notify the appropriate source once im
|
||||||
|
|
||||||
COLLABORATION_TEMPLATE = """---
|
COLLABORATION_TEMPLATE = """---
|
||||||
|
|
||||||
### Collaborative Attribution
|
### Authorship & Attribution
|
||||||
|
|
||||||
This run was triggered by **{display_name}**. You author the work **as them** — their git identity is already configured in the Repository Setup step, so every commit and the PR are attributed to them. Credit open-swe as the collaborator:
|
This run was triggered by **{display_name}**. You author the work as them — their git identity is already configured in the Repository Setup step, so every commit and the PR are attributed to them.
|
||||||
|
|
||||||
- **Commits**: append this trailer (verbatim, on its own line, separated from the message body by a blank line) to every commit message you author. Add it to both the first commit and any follow-up commits in this run:
|
**Add NO agent or AI attribution to any artifact.** Sea Haven artifacts carry no agent attribution — write commits, PR descriptions, and comments exactly as a human engineer would. Specifically, never add:
|
||||||
|
|
||||||
```
|
- A `Co-authored-by:` trailer for any bot or agent (no `open-swe[bot]`, no `Claude`, etc.).
|
||||||
{bot_coauthor_trailer}
|
- A PR-body footer or tagline such as `Made by [Open SWE]`, `Generated with …`, a 🤖 emoji line, or `_Opened collaboratively by … and open-swe._`.
|
||||||
```
|
- Any "created/opened by an agent" note in commits, PR bodies, or issue comments.
|
||||||
|
|
||||||
- **PR body**: append this line to the bottom of the PR description (separated from the body by a blank line) when you open or update the draft PR. Do not duplicate it if it is already present. If the PR body already contains a `Made by [Open SWE]` footer pointing at a different link, or a legacy footer like `_Opened collaboratively by {display_name} and open-swe._`, replace that existing footer with this line instead of appending a second footer:
|
If a template or a prior artifact already contains such attribution, strip it rather than carrying it forward."""
|
||||||
|
|
||||||
```
|
|
||||||
{pr_attribution_footer}
|
|
||||||
```
|
|
||||||
|
|
||||||
If you forget the trailer on a local commit that has not been pushed, fix it with `git commit --amend` before pushing — do not push without it. If the commit has already been pushed, leave it as-is and add the trailer to your next commit; never rewrite remote history to fix it."""
|
|
||||||
|
|
||||||
|
|
||||||
def _render_collaboration_section(
|
def _render_collaboration_section(
|
||||||
|
|
@ -473,11 +495,7 @@ def _render_collaboration_section(
|
||||||
) -> str:
|
) -> str:
|
||||||
if identity is None:
|
if identity is None:
|
||||||
return ""
|
return ""
|
||||||
return COLLABORATION_TEMPLATE.format(
|
return COLLABORATION_TEMPLATE.format(display_name=identity.display_name)
|
||||||
display_name=identity.display_name,
|
|
||||||
pr_attribution_footer=build_pr_attribution_footer(thread_url),
|
|
||||||
bot_coauthor_trailer=f"Co-authored-by: {OPEN_SWE_BOT_NAME} <{OPEN_SWE_BOT_EMAIL}>",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
ALWAYS_CREATE_PR_SECTION = """---
|
ALWAYS_CREATE_PR_SECTION = """---
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
"""Helpers for collaborative commit and PR attribution."""
|
"""Helpers for resolving the triggering user's git identity."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
@ -16,16 +16,6 @@ OPEN_SWE_BOT_NAME = "open-swe[bot]"
|
||||||
# accepts, which broke preview deploys on commits carrying this co-author.
|
# accepts, which broke preview deploys on commits carrying this co-author.
|
||||||
OPEN_SWE_BOT_EMAIL = "open-swe@users.noreply.github.com"
|
OPEN_SWE_BOT_EMAIL = "open-swe@users.noreply.github.com"
|
||||||
|
|
||||||
PR_ATTRIBUTION_TEXT = "Made by [Open SWE]"
|
|
||||||
PR_ATTRIBUTION_DEFAULT_URL = "https://openswe.vercel.app"
|
|
||||||
PR_ATTRIBUTION_FOOTER = f"{PR_ATTRIBUTION_TEXT}({PR_ATTRIBUTION_DEFAULT_URL})"
|
|
||||||
|
|
||||||
|
|
||||||
def build_pr_attribution_footer(thread_url: str | None = None) -> str:
|
|
||||||
"""Build the Open SWE PR footer, linking the run's thread when available."""
|
|
||||||
url = thread_url.strip() if isinstance(thread_url, str) and thread_url.strip() else ""
|
|
||||||
return f"{PR_ATTRIBUTION_TEXT}({url or PR_ATTRIBUTION_DEFAULT_URL})"
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class CollaboratorIdentity:
|
class CollaboratorIdentity:
|
||||||
|
|
@ -149,50 +139,3 @@ def resolve_triggering_user_identity(
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return _identity_from_github_token(github_token) or _identity_from_config(config)
|
return _identity_from_github_token(github_token) or _identity_from_config(config)
|
||||||
|
|
||||||
|
|
||||||
def add_bot_coauthor_trailer(commit_message: str) -> str:
|
|
||||||
"""Append the open-swe[bot] Co-authored-by trailer.
|
|
||||||
|
|
||||||
Commits are authored by the triggering user (via the repo-local git
|
|
||||||
identity); open-swe[bot] is credited as the collaborator.
|
|
||||||
"""
|
|
||||||
normalized_message = commit_message.rstrip()
|
|
||||||
trailer = f"Co-authored-by: {OPEN_SWE_BOT_NAME} <{OPEN_SWE_BOT_EMAIL}>"
|
|
||||||
if trailer in normalized_message:
|
|
||||||
return normalized_message
|
|
||||||
return f"{normalized_message}\n\n{trailer}"
|
|
||||||
|
|
||||||
|
|
||||||
def add_pr_collaboration_note(
|
|
||||||
pr_body: str,
|
|
||||||
identity: CollaboratorIdentity | None = None,
|
|
||||||
thread_url: str | None = None,
|
|
||||||
) -> str:
|
|
||||||
"""Append the Open SWE attribution footer to a PR body.
|
|
||||||
|
|
||||||
The PR is opened as the triggering user, so the body only credits Open SWE
|
|
||||||
as the collaborator. The footer links the run's thread when available. Any
|
|
||||||
legacy double-attribution footer is replaced.
|
|
||||||
"""
|
|
||||||
|
|
||||||
normalized_body = pr_body.rstrip()
|
|
||||||
note = build_pr_attribution_footer(thread_url)
|
|
||||||
if note in normalized_body:
|
|
||||||
return normalized_body
|
|
||||||
if PR_ATTRIBUTION_TEXT in normalized_body:
|
|
||||||
return normalized_body
|
|
||||||
|
|
||||||
legacy_footers: list[str] = []
|
|
||||||
if identity is not None:
|
|
||||||
legacy_footers.append(
|
|
||||||
f"_Opened collaboratively by {identity.pr_attribution_name} and open-swe._"
|
|
||||||
)
|
|
||||||
legacy_footers.append(f"_Opened collaboratively by {identity.display_name} and open-swe._")
|
|
||||||
for legacy in legacy_footers:
|
|
||||||
if legacy in normalized_body:
|
|
||||||
return normalized_body.replace(legacy, note)
|
|
||||||
|
|
||||||
if not normalized_body:
|
|
||||||
return note
|
|
||||||
return f"{normalized_body}\n\n{note}"
|
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ __all__ = [
|
||||||
"verify_github_signature",
|
"verify_github_signature",
|
||||||
]
|
]
|
||||||
|
|
||||||
OPEN_SWE_TAGS = ("@openswe", "@open-swe", "@openswe-dev")
|
OPEN_SWE_TAGS = ("@openswe", "@open-swe", "@openswe-dev", "@seahaven-openswe")
|
||||||
UNTRUSTED_GITHUB_COMMENT_OPEN_TAG = "<dangerous-external-untrusted-users-comment>"
|
UNTRUSTED_GITHUB_COMMENT_OPEN_TAG = "<dangerous-external-untrusted-users-comment>"
|
||||||
UNTRUSTED_GITHUB_COMMENT_CLOSE_TAG = "</dangerous-external-untrusted-users-comment>"
|
UNTRUSTED_GITHUB_COMMENT_CLOSE_TAG = "</dangerous-external-untrusted-users-comment>"
|
||||||
_SANITIZED_UNTRUSTED_GITHUB_COMMENT_OPEN_TAG = "[blocked-untrusted-comment-tag-open]"
|
_SANITIZED_UNTRUSTED_GITHUB_COMMENT_OPEN_TAG = "[blocked-untrusted-comment-tag-open]"
|
||||||
|
|
|
||||||
|
|
@ -57,8 +57,8 @@ uv run python -m evals.reviewer.run_eval --limit 3
|
||||||
|
|
||||||
### From the GitHub Action (recommended for full runs)
|
### From the GitHub Action (recommended for full runs)
|
||||||
|
|
||||||
Trigger the **Reviewer eval** workflow (`.github/workflows/reviewer_eval.yml`)
|
Trigger the **Reviewer eval** workflow (`.github/workflows/reviewer-eval.yml`)
|
||||||
from the Actions UI or `gh workflow run reviewer_eval.yml --ref prod -f limit=3`.
|
from the Actions UI or `gh workflow run reviewer-eval.yml --ref prod -f limit=3`.
|
||||||
Run it on the **prod** branch so the harness/judge match the deployed reviewer it
|
Run it on the **prod** branch so the harness/judge match the deployed reviewer it
|
||||||
scores. Running it on a durable runner (instead of inside the serving deployment)
|
scores. Running it on a durable runner (instead of inside the serving deployment)
|
||||||
means a deploy or container recycle can't kill a long run.
|
means a deploy or container recycle can't kill a long run.
|
||||||
|
|
|
||||||
|
|
@ -245,7 +245,7 @@ npm test # jest — naming Aspect
|
||||||
## CI/CD (T18 — `.github/workflows/ci-infra.yml` + `cd-infra.yml`)
|
## CI/CD (T18 — `.github/workflows/ci-infra.yml` + `cd-infra.yml`)
|
||||||
|
|
||||||
Path-filtered, OIDC-only (no static keys). The Python agent keeps its own
|
Path-filtered, OIDC-only (no static keys). The Python agent keeps its own
|
||||||
`ci.yml` ("Agent CI"); these two add the `/infra` half.
|
`ci.yml` ("CI"); these two add the `/infra` half.
|
||||||
|
|
||||||
| Workflow | Trigger | Does |
|
| Workflow | Trigger | Does |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ dependencies = [
|
||||||
"langgraph>=1.1.10",
|
"langgraph>=1.1.10",
|
||||||
"markdownify>=1.2.2",
|
"markdownify>=1.2.2",
|
||||||
"langchain-anthropic>=1.4.6",
|
"langchain-anthropic>=1.4.6",
|
||||||
"langgraph-cli[inmem]>=0.4.27",
|
"langgraph-cli[inmem]>=0.4.30",
|
||||||
"langsmith==0.8.18",
|
"langsmith==0.8.18",
|
||||||
"langchain-openai>=1.2.2",
|
"langchain-openai>=1.2.2",
|
||||||
"langchain-fireworks>=1.4.2",
|
"langchain-fireworks>=1.4.2",
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from agent.utils.authorship import (
|
from agent.utils.authorship import resolve_triggering_user_identity
|
||||||
OPEN_SWE_BOT_EMAIL,
|
|
||||||
OPEN_SWE_BOT_NAME,
|
|
||||||
add_bot_coauthor_trailer,
|
|
||||||
resolve_triggering_user_identity,
|
|
||||||
)
|
|
||||||
|
|
||||||
_BOT_TRAILER = f"Co-authored-by: {OPEN_SWE_BOT_NAME} <{OPEN_SWE_BOT_EMAIL}>"
|
|
||||||
|
|
||||||
|
|
||||||
def test_add_bot_coauthor_trailer_appends_bot() -> None:
|
|
||||||
result = add_bot_coauthor_trailer("fix: thing")
|
|
||||||
assert result == f"fix: thing\n\n{_BOT_TRAILER}"
|
|
||||||
|
|
||||||
|
|
||||||
def test_add_bot_coauthor_trailer_is_idempotent() -> None:
|
|
||||||
once = add_bot_coauthor_trailer("fix: thing")
|
|
||||||
assert add_bot_coauthor_trailer(once) == once
|
|
||||||
|
|
||||||
|
|
||||||
def test_resolve_identity_from_config_uses_user_noreply_email() -> None:
|
def test_resolve_identity_from_config_uses_user_noreply_email() -> None:
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ from agent.utils.authorship import (
|
||||||
OPEN_SWE_BOT_EMAIL,
|
OPEN_SWE_BOT_EMAIL,
|
||||||
OPEN_SWE_BOT_NAME,
|
OPEN_SWE_BOT_NAME,
|
||||||
CollaboratorIdentity,
|
CollaboratorIdentity,
|
||||||
add_pr_collaboration_note,
|
|
||||||
resolve_triggering_user_identity,
|
resolve_triggering_user_identity,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -100,7 +99,7 @@ def test_construct_system_prompt_includes_corridor_prompt_when_enabled() -> None
|
||||||
def test_construct_system_prompt_omits_collaboration_section_without_identity() -> None:
|
def test_construct_system_prompt_omits_collaboration_section_without_identity() -> None:
|
||||||
prompt = construct_system_prompt(working_dir="/workspace")
|
prompt = construct_system_prompt(working_dir="/workspace")
|
||||||
|
|
||||||
assert "Collaborative Attribution" not in prompt
|
assert "Authorship & Attribution" not in prompt
|
||||||
assert "Co-authored-by:" not in prompt
|
assert "Co-authored-by:" not in prompt
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -136,7 +135,7 @@ def test_construct_system_prompt_forbids_force_push() -> None:
|
||||||
assert "git pull --rebase origin <branch>" in prompt
|
assert "git pull --rebase origin <branch>" in prompt
|
||||||
|
|
||||||
|
|
||||||
def test_construct_system_prompt_includes_coauthor_trailer_when_identity_present() -> None:
|
def test_construct_system_prompt_emits_no_attribution_when_identity_present() -> None:
|
||||||
identity = CollaboratorIdentity(
|
identity = CollaboratorIdentity(
|
||||||
display_name="octocat",
|
display_name="octocat",
|
||||||
commit_name="octocat",
|
commit_name="octocat",
|
||||||
|
|
@ -148,16 +147,24 @@ def test_construct_system_prompt_includes_coauthor_trailer_when_identity_present
|
||||||
triggering_user_identity=identity,
|
triggering_user_identity=identity,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert "Collaborative Attribution" in prompt
|
# The Sea Haven Authorship section renders, the commits are still authored as
|
||||||
# The user authors the commits; open-swe[bot] is the co-author/collaborator.
|
# the triggering user (identity flip to the bot is deferred — see issue #11),
|
||||||
|
# and NO agent/AI attribution leaks into the prompt.
|
||||||
|
assert "Authorship & Attribution" in prompt
|
||||||
|
assert "Add NO agent or AI attribution" in prompt
|
||||||
# Values are shell-escaped via shlex.quote; safe tokens need no quoting.
|
# Values are shell-escaped via shlex.quote; safe tokens need no quoting.
|
||||||
assert "git config user.name octocat" in prompt
|
assert "git config user.name octocat" in prompt
|
||||||
assert "git config user.email 1234+octocat@users.noreply.github.com" in prompt
|
assert "git config user.email 1234+octocat@users.noreply.github.com" in prompt
|
||||||
assert _BOT_TRAILER in prompt
|
# The concrete attribution artifacts the old template INSTRUCTED are gone (the
|
||||||
assert "Made by [Open SWE](https://openswe.vercel.app)" in prompt
|
# prohibition still names them as examples, so assert the instructional forms:
|
||||||
|
# the full co-author trailer, the URL-bearing footer, and the old mandate text).
|
||||||
|
assert _BOT_TRAILER not in prompt
|
||||||
|
assert "Made by [Open SWE](https://openswe.vercel.app)" not in prompt
|
||||||
|
assert "Credit open-swe as the collaborator" not in prompt
|
||||||
|
assert "append this trailer" not in prompt
|
||||||
|
|
||||||
|
|
||||||
def test_construct_system_prompt_includes_github_login_in_pr_footer() -> None:
|
def test_construct_system_prompt_no_attribution_with_github_login() -> None:
|
||||||
identity = CollaboratorIdentity(
|
identity = CollaboratorIdentity(
|
||||||
display_name="Mona Lisa",
|
display_name="Mona Lisa",
|
||||||
commit_name="Mona Lisa",
|
commit_name="Mona Lisa",
|
||||||
|
|
@ -173,27 +180,42 @@ def test_construct_system_prompt_includes_github_login_in_pr_footer() -> None:
|
||||||
# A name with a space is shlex-quoted; the safe email is left bare.
|
# A name with a space is shlex-quoted; the safe email is left bare.
|
||||||
assert "git config user.name 'Mona Lisa'" in prompt
|
assert "git config user.name 'Mona Lisa'" in prompt
|
||||||
assert "git config user.email 1234+octocat@users.noreply.github.com" in prompt
|
assert "git config user.email 1234+octocat@users.noreply.github.com" in prompt
|
||||||
assert _BOT_TRAILER in prompt
|
assert _BOT_TRAILER not in prompt
|
||||||
assert "Made by [Open SWE](https://openswe.vercel.app)" in prompt
|
|
||||||
assert "replace that existing footer with this line" in prompt
|
|
||||||
assert "`_Opened collaboratively by Mona Lisa and open-swe._`" in prompt
|
|
||||||
|
|
||||||
|
|
||||||
def test_construct_system_prompt_footer_links_thread_when_provided() -> None:
|
|
||||||
identity = CollaboratorIdentity(
|
|
||||||
display_name="octocat",
|
|
||||||
commit_name="octocat",
|
|
||||||
commit_email="1234+octocat@users.noreply.github.com",
|
|
||||||
)
|
|
||||||
|
|
||||||
prompt = construct_system_prompt(
|
|
||||||
working_dir="/workspace",
|
|
||||||
triggering_user_identity=identity,
|
|
||||||
thread_url="https://openswe.vercel.app/agents/abc-123",
|
|
||||||
)
|
|
||||||
|
|
||||||
assert "Made by [Open SWE](https://openswe.vercel.app/agents/abc-123)" in prompt
|
|
||||||
assert "Made by [Open SWE](https://openswe.vercel.app)" not in prompt
|
assert "Made by [Open SWE](https://openswe.vercel.app)" not in prompt
|
||||||
|
assert "replace that existing footer with this line" not in prompt
|
||||||
|
|
||||||
|
|
||||||
|
def test_construct_system_prompt_uses_sea_haven_conventions() -> None:
|
||||||
|
prompt = construct_system_prompt(working_dir="/workspace")
|
||||||
|
|
||||||
|
# Branch naming, PR structure, and commit format follow the handbook.
|
||||||
|
assert "feature/" in prompt and "hotfix/" in prompt
|
||||||
|
assert "Do NOT use a conventional-commit `type:` prefix" in prompt
|
||||||
|
assert "## Summary" in prompt and "## Validation" in prompt
|
||||||
|
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 #<n>" in prompt
|
||||||
|
assert "Refs #<n>" in prompt or "Part of #<n>" in prompt
|
||||||
|
assert "Closes owner/repo#<n>" 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:
|
def test_construct_system_prompt_shell_escapes_user_name() -> None:
|
||||||
|
|
@ -217,38 +239,6 @@ def test_construct_system_prompt_shell_escapes_user_name() -> None:
|
||||||
assert f"git config user.name {hostile}" not in prompt
|
assert f"git config user.name {hostile}" not in prompt
|
||||||
|
|
||||||
|
|
||||||
def test_add_pr_collaboration_note_replaces_legacy_footer() -> None:
|
|
||||||
identity = CollaboratorIdentity(
|
|
||||||
display_name="Mona Lisa",
|
|
||||||
commit_name="Mona Lisa",
|
|
||||||
commit_email="1234+octocat@users.noreply.github.com",
|
|
||||||
github_login="octocat",
|
|
||||||
)
|
|
||||||
|
|
||||||
body = "## Description\nDone.\n\n_Opened collaboratively by Mona Lisa and open-swe._"
|
|
||||||
|
|
||||||
assert add_pr_collaboration_note(body, identity) == (
|
|
||||||
"## Description\nDone.\n\nMade by [Open SWE](https://openswe.vercel.app)"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_add_pr_collaboration_note_links_thread() -> None:
|
|
||||||
body = "## Description\nDone."
|
|
||||||
|
|
||||||
assert add_pr_collaboration_note(
|
|
||||||
body, thread_url="https://openswe.vercel.app/agents/abc-123"
|
|
||||||
) == ("## Description\nDone.\n\nMade by [Open SWE](https://openswe.vercel.app/agents/abc-123)")
|
|
||||||
|
|
||||||
|
|
||||||
def test_add_pr_collaboration_note_skips_when_footer_present_with_other_link() -> None:
|
|
||||||
body = "## Description\nDone.\n\nMade by [Open SWE](https://openswe.vercel.app)"
|
|
||||||
|
|
||||||
assert (
|
|
||||||
add_pr_collaboration_note(body, thread_url="https://openswe.vercel.app/agents/abc-123")
|
|
||||||
== body
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_resolve_triggering_user_identity_combines_slack_name_with_github_login() -> None:
|
def test_resolve_triggering_user_identity_combines_slack_name_with_github_login() -> None:
|
||||||
identity = resolve_triggering_user_identity(
|
identity = resolve_triggering_user_identity(
|
||||||
{
|
{
|
||||||
|
|
|
||||||
9386
ui/yarn.lock
9386
ui/yarn.lock
File diff suppressed because it is too large
Load diff
8
uv.lock
generated
8
uv.lock
generated
|
|
@ -1627,7 +1627,7 @@ wheels = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "langgraph-cli"
|
name = "langgraph-cli"
|
||||||
version = "0.4.27"
|
version = "0.4.30"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "click" },
|
{ name = "click" },
|
||||||
|
|
@ -1636,9 +1636,9 @@ dependencies = [
|
||||||
{ name = "pathspec" },
|
{ name = "pathspec" },
|
||||||
{ name = "python-dotenv" },
|
{ name = "python-dotenv" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/02/21/046b2345d83427a59ebdac797fbe343bb8d1e575560667846f7346733774/langgraph_cli-0.4.27.tar.gz", hash = "sha256:67a64b67dddc8c670d77cc4c9663a7f8e924eaeb8857a926e87f239b699ef8f6", size = 1045499, upload-time = "2026-05-28T14:25:35.13Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/f0/27/4b6a0f00c804f0b0831f741c0607b46a4cbddff14d1eab6bbd4ce5820837/langgraph_cli-0.4.30.tar.gz", hash = "sha256:4948fdc77ff45fc5ef3d8330d17bbecfcb26cd9c4d3a4f00da84a41a0226cd72", size = 1046771, upload-time = "2026-06-16T19:46:27.949Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/d5/89/4bda72ff33172ad15ddb2c57c5b998bf9309e7c4890d3d84a8c8ed6333d4/langgraph_cli-0.4.27-py3-none-any.whl", hash = "sha256:d53bddfc4e7b6e47871bedb34ccc476fd5e4e97f8678dd453ecac597c5f11ff1", size = 78002, upload-time = "2026-05-28T14:25:34.127Z" },
|
{ url = "https://files.pythonhosted.org/packages/3e/b6/94cbd2ba0820caae203a915272394c576a21ab4a56dfbc93724dc8cd8e2b/langgraph_cli-0.4.30-py3-none-any.whl", hash = "sha256:9c577750c57da1a0e3407e8b83e5a0d7eaa80685fe99d95aa7f9bf0e1e73ca92", size = 82061, upload-time = "2026-06-16T19:46:26.873Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.optional-dependencies]
|
[package.optional-dependencies]
|
||||||
|
|
@ -2040,7 +2040,7 @@ requires-dist = [
|
||||||
{ name = "langchain-openai", specifier = ">=1.2.2" },
|
{ name = "langchain-openai", specifier = ">=1.2.2" },
|
||||||
{ name = "langchain-runloop", specifier = ">=0.0.4" },
|
{ name = "langchain-runloop", specifier = ">=0.0.4" },
|
||||||
{ name = "langgraph", specifier = ">=1.1.10" },
|
{ name = "langgraph", specifier = ">=1.1.10" },
|
||||||
{ name = "langgraph-cli", extras = ["inmem"], specifier = ">=0.4.27" },
|
{ name = "langgraph-cli", extras = ["inmem"], specifier = ">=0.4.30" },
|
||||||
{ name = "langgraph-sdk", specifier = ">=0.4.2" },
|
{ name = "langgraph-sdk", specifier = ">=0.4.2" },
|
||||||
{ name = "langsmith", specifier = "==0.8.18" },
|
{ name = "langsmith", specifier = "==0.8.18" },
|
||||||
{ name = "markdownify", specifier = ">=1.2.2" },
|
{ name = "markdownify", specifier = ">=1.2.2" },
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue