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
19 changes: 19 additions & 0 deletions .agents/pattern-agents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"schema_version": 1,
"github": {
"repo": "patterninc/blurb"
},
"slack_channel": "#sre-comm",
"datadog": {
"service": "blurb",
"env": "production"
},
"skills": {
"plugins": [
"ai-sdlc",
"terraform",
".claude/skills/change-github-workflows",
".claude/skills/change-and-verify"
]
}
}
23 changes: 23 additions & 0 deletions .claude/skills/change-and-verify/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
name: change-and-verify
description: Use when changing Ruby code in blurb (lib/blurb/**) and deciding how to verify the change before a PR, or when cutting a gem release.
---

# Changing code in blurb

1. Read `AGENTS.md` (layout, what must not change) and `docs/adr/` before touching `lib/blurb/request.rb`, `lib/blurb/base_class.rb` or `lib/blurb/request_collection*.rb`.
2. Make the change. Every HTTP call goes through `Blurb::Request` (ADR 0002); don't call `RestClient` elsewhere.
3. Add or update a hermetic spec in `spec/unit/` for the behaviour you changed. Stub HTTP with WebMock (`stub_request`) and keep payloads in `spec/fixtures/`. Never put real credentials, profile IDs or refresh tokens in fixtures.
4. `make all` must pass: RuboCop, every pre-commit hook, unit specs and the SimpleCov gate — exactly what CI runs. If coverage rises, raise `minimum_coverage` in `spec/spec_helper.rb`.
5. If the change alters the public API (method names, key casing, exception classes), it is breaking: say so in the PR and in `CHANGELOG.md`.
6. Open the PR with a Conventional Commit title (`feat: …`, `fix: …`) and add a line under `## [Unreleased]` in `CHANGELOG.md`.

## Live API check (optional, before a release)

`make test-live` runs `spec/blurb/` against the real Amazon Advertising API. It needs a `.env` with `BLURB_CLIENT_ID`, `BLURB_CLIENT_SECRET`, `BLURB_REFRESH_TOKEN`, `BLURB_REGION` and `BLURB_PROFILE_ID`, and it creates campaigns, keywords and reports. Only run it when the user asks, against a test/sandbox profile.

## Releasing a version (only when asked)

1. Bump `spec.version` in `blurb.gemspec` (semver; breaking → minor while < 1.0).
2. Move the `[Unreleased]` entries in `CHANGELOG.md` under the new version and date; update the compare links.
3. Merge via PR. Then, from an up-to-date default branch, `bundle exec rake release` tags `vX.Y.Z`, pushes the tag and publishes to RubyGems. It needs the owner's RubyGems credentials; don't run it without the user.
13 changes: 13 additions & 0 deletions .claude/skills/change-github-workflows/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
name: change-github-workflows
description: Use when adding or changing a GitHub Actions workflow in blurb (.github/workflows/*.yml), for example CI, terraform plan/apply, Docker build, ECS or Lambda deploy.
---

# Changing GitHub workflows in blurb

1. Load the `terraform:github-reusable-workflows` skill before you edit anything under `.github/workflows/`.
2. Check whether a reusable workflow in `patterninc/pattern-reusable-workflow` already covers the change: `terraform-ci-cd`, `ecs-deployment`, `docker-build-and-push`, `lambda-deploy`, `db-migrate`, `amplify-deployment` or `frontend-ci-checks`. If one does, call it rather than writing the steps inline.
3. Pin `uses:` to the latest release tag (`gh api repos/patterninc/pattern-reusable-workflow/tags --jq '.[0].name'`), never `@main`. Check the inputs and secrets at that tag, and pass secrets in an explicit `secrets:` block, never `secrets: inherit`.
4. AWS access goes through OIDC (`use-oidc: true` and the account's `aws-account-id`), not static keys.
5. If no reusable workflow fits, say so in the PR body and name the one you checked.
6. `make all` must pass. Its `check-yaml` hook validates the YAML.
10 changes: 10 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "blurb",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04",
"features": {
"ghcr.io/devcontainers/features/github-cli:1": {},
"ghcr.io/devcontainers/features/python:1": { "version": "3.12" },
"ghcr.io/devcontainers/features/ruby:1": { "version": "2.7.8" }
},
"postCreateCommand": "make bootstrap"
}
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Review routing for every path (item 9). Owner comes from backstage.yaml.
* @patterninc/sre
1 change: 0 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,3 @@ jobs:
env:
BUNDLE_GITHUB__COM: ${{ secrets.RUBY_GEM_BUNDLE_TOKEN }}
BUNDLE_ENTERPRISE__CONTRIBSYS__COM: ${{ secrets.SIDEKIQ_ENTERPRISE_TOKEN }}

20 changes: 20 additions & 0 deletions .github/workflows/pr-hygiene.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# PR title = squash-commit message (item 14). Added by closing-ai-readiness-gaps.
# Make "PR Hygiene" a required status check (item 16).
name: 'PR Hygiene'

on:
pull_request:
types: [opened, edited, synchronize, reopened]

permissions:
pull-requests: read

jobs:
hygiene:
name: 'PR Hygiene'
runs-on: ubuntu-latest
steps:
- name: Conventional Commit PR title
uses: amannn/action-semantic-pull-request@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
56 changes: 56 additions & 0 deletions .github/workflows/static-checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Credential-free PR checks (items 10, 11, 13, 23): the same hooks and tests as `make all`.
# Added by closing-ai-readiness-gaps. Make "Static Checks" a required status check (item 16).
name: 'Static Checks'

on:
pull_request:
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
static:
name: 'Static Checks'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

- uses: actions/setup-python@v6
with:
python-version: '3.12'

# Ruby version from .ruby-version; installs Gemfile.lock into vendor/bundle.
- uses: ruby/setup-ruby@v1
with:
bundler-cache: true

- name: Pre-commit hooks (format, lint, validate)
run: |
pip install pre-commit
pre-commit run --all-files --show-diff-on-failure

- name: Unit tests
run: make test

- name: License scan of installed gems (item 18)
uses: aquasecurity/trivy-action@v0.36.0
with:
scan-type: rootfs
scan-ref: vendor/bundle
scanners: license
severity: HIGH,CRITICAL
exit-code: '1'
trivyignores: .trivyignore.yaml

- name: Upload test results (item 33)
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: '**/*-junit.xml'
if-no-files-found: ignore
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/.bundle/
/.yardoc
/Gemfile.lock
/_yardoc/
/coverage/
/doc/
Expand All @@ -12,3 +11,7 @@
.rspec_status
.env
.byebug_history

# test output (Makefile `test` target)
rspec-junit.xml
/vendor/
40 changes: 40 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Added by closing-ai-readiness-gaps (items 10, 11, 12, 13, 14, 17). `make lint` runs every hook; CI runs the same.
default_install_hook_types: [pre-commit, commit-msg]

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: check-merge-conflict
- id: check-yaml
args: [--allow-multiple-documents]
- id: check-added-large-files
- id: detect-private-key
- id: end-of-file-fixer
exclude: \.md$
- id: trailing-whitespace
exclude: \.md$

# RuboCop: lint, layout and complexity cops (items 10, 11, 21). Uses the Gemfile.lock version.
- repo: local
hooks:
- id: rubocop
name: rubocop
entry: bundle exec rubocop --force-exclusion
language: system
types: [ruby]
# Type check lib/ against sig/*.rbs (item 12). Same as `make typecheck`.
- id: steep
name: steep check
entry: bundle exec steep check
language: system
pass_filenames: false
files: ^(lib/.*\.rb|sig/.*\.rbs|Steepfile|Gemfile\.lock)$

# Conventional Commits (item 14), enforced on commit-msg
- repo: https://github.com/compilerla/conventional-pre-commit
rev: v4.4.0
hooks:
- id: conventional-pre-commit
stages: [commit-msg]
args: [--strict, feat, fix, docs, chore, refactor, test, ci, build, perf, revert, style]
27 changes: 27 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Lint, layout (formatter) and complexity cops (audit items 10, 11, 21).
# Offenses that predate RuboCop are frozen in .rubocop_todo.yml; fix them in their
# own PRs and delete the matching todo entry. Don't regenerate the todo to hide new ones.
inherit_from: .rubocop_todo.yml
inherit_mode:
merge:
- Exclude

AllCops:
TargetRubyVersion: 2.7
NewCops: enable
SuggestExtensions: false
Exclude:
- 'vendor/**/*'
- 'coverage/**/*'

# Complexity limits (item 21): RuboCop defaults, enforced on new code.
Metrics/CyclomaticComplexity:
Enabled: true
Metrics/PerceivedComplexity:
Enabled: true
Metrics/MethodLength:
Enabled: true
Metrics/BlockLength:
Exclude:
- 'spec/**/*'
- '*.gemspec'
Loading
Loading