Skip to content
Merged
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
28 changes: 28 additions & 0 deletions .github/workflows/cowork-auto-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Seeded by the repo-improver-rotation Cowork job into cowork/improve-* branches.
# Opens a PR automatically when such a branch is pushed (sandbox cannot reach
# the GitHub API directly; this runs server-side with the repo's GITHUB_TOKEN).
name: cowork-auto-pr
on:
push:
branches: ['cowork/improve-**']
permissions:
contents: read
pull-requests: write
jobs:
ensure-pr:
runs-on: ubuntu-latest
steps:
- name: Open PR for this branch if none exists
env:
GH_TOKEN: ${{ github.token }}
run: |
set -eu
existing=$(gh pr list --repo "$GITHUB_REPOSITORY" --head "$GITHUB_REF_NAME" --state open --json number --jq 'length')
if [ "$existing" = "0" ]; then
gh pr create --repo "$GITHUB_REPOSITORY" \
--head "$GITHUB_REF_NAME" \
--title "cowork-bot: automated improvements ($GITHUB_REF_NAME)" \
--body "Automated improvement PR from the Cowork repo-improver rotation (one coherent senior-dev improvement per run; see individual commit messages). Subsequent runs push additional commits to this PR rather than opening new ones."
else
echo "Open PR already exists for $GITHUB_REF_NAME — nothing to do."
fi
54 changes: 48 additions & 6 deletions src/api_contract_guardian/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,35 @@ def _diff_paths(old: dict[str, Any], new: dict[str, Any], result: DiffResult) ->
_diff_operations(path, old_paths[path], new_paths[path], result)



def _param_key(param: dict[str, Any]) -> tuple[str, str]:
"""Identity key for a parameter: (in, name), or ('$ref', target) for refs.

Keying unresolved $ref parameters by their target keeps distinct refs
from colliding on the ('', '') key.
"""
if "$ref" in param:
return ("$ref", str(param["$ref"]))
return (param.get("in", ""), param.get("name", ""))


def _effective_parameters(
path_item: dict[str, Any],
op: dict[str, Any],
) -> list[dict[str, Any]]:
"""Merge path-item-level parameters with operation-level ones.

Per the OpenAPI spec, parameters declared on a path item apply to every
operation under it; an operation-level parameter with the same (name, in)
pair overrides the path-level definition.
"""
merged: dict[tuple[str, str], dict[str, Any]] = {}
for source in (path_item.get("parameters") or [], op.get("parameters") or []):
for param in source:
if isinstance(param, dict):
merged[_param_key(param)] = param
return list(merged.values())

def _diff_operations(
path: str,
old_item: dict[str, Any],
Expand Down Expand Up @@ -192,7 +221,11 @@ def _diff_operations(
)
)
elif old_op and new_op:
_diff_operation_details(path, method, old_op, new_op, result)
_diff_operation_details(
path, method, old_op, new_op, result,
old_params=_effective_parameters(old_item, old_op),
new_params=_effective_parameters(new_item, new_op),
)


def _diff_operation_details(
Expand All @@ -201,14 +234,23 @@ def _diff_operation_details(
old_op: dict[str, Any],
new_op: dict[str, Any],
result: DiffResult,
old_params: list[dict[str, Any]] | None = None,
new_params: list[dict[str, Any]] | None = None,
) -> None:
"""Detect changes within an operation (parameters, responses, requestBody)."""
"""Detect changes within an operation (parameters, responses, requestBody).

``old_params``/``new_params`` are the effective parameter lists with
path-item-level parameters already merged in; when omitted, the
operation's own parameters are used.
"""
op_path = f"paths.{path}.{method}"

# Check parameters
_diff_parameters(
op_path, old_op.get("parameters", []), new_op.get("parameters", []), result
)
# Check parameters (path-item-level parameters merged by the caller)
if old_params is None:
old_params = old_op.get("parameters", [])
if new_params is None:
new_params = new_op.get("parameters", [])
_diff_parameters(op_path, old_params, new_params, result)

# Check request body
_diff_request_body(
Expand Down
Loading