Release Manager Agent Specification
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.
Table of Contents
- Introduction
- Architecture Overview
- Signal Collectors
- Analysis Engine
- Decision Engine
- Changelog Generation
- Release Workflows
- Integrations
- Configuration
- Security Considerations
- See Also
Introduction
Purpose
The Release Manager Agent is an AI-native tool that provides intelligent release management, quality gate enforcement, and automated changelog generation. Unlike traditional release automation tools that operate on rigid rules, this agent uses LLM-powered analysis to understand context, assess readiness, and provide actionable recommendations.
Why
Existing release management tools fall into two categories:
- Fully Automated (semantic-release, release-please): Execute mechanical workflows without understanding context or quality
- Fully Manual (changesets): Require human decisions at every step without intelligent guidance
Neither approach provides what engineering teams actually need: intelligent decision support with automation capabilities.
The Release Manager Agent bridges this gap by:
- Aggregating signals from tests, security scans, code reviews, and dependencies
- Providing confidence scores for release readiness
- Generating human-quality changelogs using LLM understanding
- Recommending actions while keeping humans in control
Release Authority
Only a named person MAY approve a release. Agents MAY assess readiness, collect evidence, draft versions, changelogs, and notes, and open private draft change proposals. They MUST then stop. A separate step that only a person can trigger tags and publishes the approved commit and artifacts.
Agents MUST NOT hold credentials that can tag, publish releases or packages, push to public branches, or post announcements. An agent’s recommendation, confidence score, or session confirmation is not release approval.
Why Human Approval
Publication commits the project to a public artifact. A named person owns that decision, and separate credentials keep preparation from becoming publication.
Scope
This specification covers:
- Agent architecture and components
- Signal collection and analysis
- Release decision logic
- Changelog generation patterns
- Integration requirements
- Configuration options
Audience
- Engineering teams implementing release automation
- DevOps engineers managing CI/CD pipelines
- Release managers overseeing software delivery
- Platform engineers building developer tooling
Architecture Overview
System Design
┌─────────────────────────────────────────────────────────────────────┐
│ RELEASE MANAGER AGENT │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Signal Collectors │ │
│ ├────────────┬────────────┬────────────┬────────────┬───────────┤ │
│ │ Git │ CI/CD │ Security │ Code │ Deps │ │
│ │ Commits │ Status │ Scans │ Reviews │ Graph │ │
│ └────────────┴────────────┴────────────┴────────────┴───────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Analysis Engine │ │
│ ├────────────────────────────────────────────────────────────────┤ │
│ │ • LLM Core (Claude/GPT) • Pattern Matching │ │
│ │ • Embeddings & RAG • Historical Learning │ │
│ │ • Semantic Understanding • Impact Analysis │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Decision Engine │ │
│ ├────────────────────────────────────────────────────────────────┤ │
│ │ • Confidence Scoring • Version Calculation │ │
│ │ • Blocker Detection • Changelog Generation │ │
│ │ • Risk Assessment • Recommendations │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Output Handlers │ │
│ ├────────────┬────────────┬────────────┬────────────┬───────────┤ │
│ │ Private │ Changelog │ Notes │ Evidence │ Metrics │ │
│ │ Proposal │ Draft │ Draft │ Register │ Export │ │
│ └────────────┴────────────┴────────────┴────────────┴───────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
Core Principles
The agent MUST adhere to these principles:
- Intelligence Over Rules: Use LLM understanding, not regex patterns
- Signals Over Guesses: Base decisions on collected data
- Recommendations Over Mandates: Suggest, don’t force
- Transparency Over Magic: Explain every decision
- Platform Agnostic: Work with any tech stack
Signal Collectors
Signal collectors gather information from various sources to inform release
decisions. Each collector MUST implement the SignalCollector interface.
Evidence Binding
Every signal MUST be bound to the exact artifact being released. A signal that cannot be bound MUST be treated as missing, never as passing.
Why
Release readiness is a claim about one specific commit and one specific artifact. “Recent workflow runs are green” is not that claim: the green run may have tested an earlier tree, an approval may predate the last force-push, and the scanned container may not be the one about to be published. GitHub’s own protected-branch controls encode the same invariant — required status checks are evaluated against the head commit, and stale approvals are dismissed when new commits change the diff. A collector that reads “the latest run” reintroduces exactly the gap those controls close.
Required Fields
Every collected signal record MUST carry:
| Field | Type | Purpose |
|---|---|---|
target_sha | string | Full 40-character SHA of the commit being released |
evidence_sha | string | Full SHA the evidence was actually produced from |
source | string | Concrete producer, e.g. github_actions, trivy, codeowners |
run_id | string | Workflow run, job, or scan ID that produced the evidence |
event | string | Trigger that produced it, e.g. push, pull_request |
lockfile_digest | string | SHA-256 of the resolved dependency lockfile |
artifact_digest | string | SHA-256 digest of the artifact the evidence covers |
collected_at | timestamp | When the collector read the evidence |
expires_at | timestamp | When the evidence stops counting |
Validation Rules
Collectors MUST:
- Reject any signal whose
evidence_shadiffers fromtarget_sha. - Reject any signal read after its
expires_at. - Reject any signal whose
artifact_digestdiffers from the digest of the artifact that will be promoted and published. - Reject any dependency signal whose
lockfile_digestdiffers from the lockfile attarget_sha. - Record a rejection as a missing signal with its reason, so the confidence score and the report show the gap rather than silently omitting it.
Collectors MUST NOT substitute evidence from a parent commit, a sibling branch, a rebuilt artifact, or a re-run whose inputs were not the target tree.
evidence_binding:
enabled: true
# Resolved once, then passed to every collector
target_sha: ${RELEASE_SHA}
require_exact_sha: true
require_artifact_digest: true
# Evidence older than this is treated as missing, per signal class
max_age:
tests: 24h
security_scans: 24h
reviews: 7d
artifacts: 24h
on_mismatch: fail # fail | warn — `warn` is for dry runs only
Don’t — take the newest run for the branch:
cicd:
workflow_lookup: latest_on_branch
Do — look up runs by the release commit and reject anything else:
cicd:
workflow_lookup: by_head_sha
head_sha: ${RELEASE_SHA}
require_conclusion: success
Git Collector
The Git Collector analyzes repository history since the last release.
Collected Signals
| Signal | Type | Description |
|---|---|---|
commits | Commit[] | All commits since last release |
authors | Author[] | Contributors in this release |
files_changed | FileChange[] | Modified files with change type |
breaking_changes | Commit[] | Commits with breaking change indicators |
conventional_commits | ParsedCommit[] | Commits parsed per Conventional Commits spec |
Configuration
signal_collectors:
git:
enabled: true
# Branch to analyze
branch: main
# Tag pattern for releases
tag_pattern: "v*"
# Conventional commit types to recognize
commit_types:
- feat
- fix
- docs
- style
- refactor
- perf
- test
- build
- ci
- chore
# Breaking change indicators
breaking_indicators:
- "BREAKING CHANGE:"
- "BREAKING-CHANGE:"
- "!:"
Example Output
{
"collector": "git",
"signals": {
"commits_count": 47,
"authors_count": 8,
"files_changed_count": 156,
"breaking_changes_count": 2,
"commit_types": {
"feat": 12,
"fix": 23,
"docs": 5,
"refactor": 4,
"test": 3
},
"has_breaking_changes": true,
"breaking_commits": [
{
"sha": "abc123",
"message": "feat!: redesign authentication API",
"author": "jane@example.com"
}
]
}
}
CI/CD Collector
The CI/CD Collector gathers build and test status from CI systems.
Collected Signals
| Signal | Type | Description |
|---|---|---|
workflow_runs | WorkflowRun[] | Workflow executions whose head_sha equals the release SHA |
test_results | TestSuite[] | Test pass/fail/skip counts from those runs |
coverage | CoverageReport | Code coverage metrics from those runs |
build_artifacts | Artifact[] | Build outputs, recorded with their SHA-256 digests |
duration_trends | DurationTrend[] | Build time analysis |
Supported Platforms
- GitHub Actions (native)
- GitLab CI (native)
- CircleCI (API)
- Jenkins (API)
- Azure Pipelines (API)
Configuration
signal_collectors:
cicd:
enabled: true
platform: github_actions
# Look runs up by commit, never "latest on branch" (see Evidence Binding)
workflow_lookup: by_head_sha
head_sha: ${RELEASE_SHA}
# Required workflows for release
required_workflows:
- "CI"
- "Security Scan"
- "Integration Tests"
# A required workflow with no run at this SHA is missing, not passing
treat_absent_run_as: missing
# Reject evidence produced from a different tree or too long ago
require_exact_sha: true
max_signal_age: 24h
# Record the digest of every artifact the runs produced
record_artifact_digests: true
# Minimum coverage threshold
coverage_threshold: 80
# Max allowed test failures
max_test_failures: 0
# Max allowed flaky tests
max_flaky_tests: 5
Security Collector
The Security Collector aggregates vulnerability and security scan data.
Collected Signals
| Signal | Type | Description |
|---|---|---|
vulnerabilities | Vulnerability[] | Known security issues |
dependency_audit | AuditResult | Dependency vulnerability scan |
sast_results | SASTFinding[] | Static analysis findings |
secrets_detected | SecretFinding[] | Exposed secrets/credentials |
license_issues | LicenseIssue[] | License compliance problems |
Severity Levels
CRITICAL → Blocks release, requires immediate fix
HIGH → Blocks release by default, configurable
MEDIUM → Warning, does not block
LOW → Informational only
Configuration
signal_collectors:
security:
enabled: true
# Scans MUST have been produced from the release commit and artifact
scanned_sha: ${RELEASE_SHA}
require_exact_sha: true
require_artifact_digest: true
max_scan_age: 24h
# Block release on these severities
blocking_severities:
- critical
- high
# Vulnerability sources
sources:
- dependabot
- snyk
- trivy
- semgrep
# Ignore specific CVEs
ignore_cves:
- CVE-2023-XXXXX # False positive, documented
# License blocklist
blocked_licenses:
- GPL-3.0
- AGPL-3.0
Code Review Collector
The Code Review Collector analyzes pull request review status.
Collected Signals
| Signal | Type | Description |
|---|---|---|
prs_merged | PullRequest[] | PRs included in release |
review_coverage | number | Percentage of PRs reviewed |
approval_rate | number | Average approvals per PR |
outstanding_comments | Comment[] | Unresolved review comments |
review_latency | Duration | Average time to review |
Configuration
signal_collectors:
code_review:
enabled: true
# Only count approvals of the diff that is actually being released
require_approval_of_merged_sha: true
ignore_dismissed_reviews: true
# An approval that predates the last change to the diff does not count
ignore_approvals_before_last_push: true
max_approval_age: 7d
# Minimum approvals required
min_approvals: 1
# Require all comments resolved
require_resolved_comments: true
# Require review from code owners
require_codeowner_review: true
# Exempt paths from review requirement
exempt_paths:
- "docs/**"
- "*.md"
Dependency Collector
The Dependency Collector analyzes dependency changes and impacts.
Collected Signals
| Signal | Type | Description |
|---|---|---|
added_deps | Dependency[] | Newly added dependencies |
removed_deps | Dependency[] | Removed dependencies |
updated_deps | DependencyUpdate[] | Version changes |
breaking_updates | DependencyUpdate[] | Incompatible updates identified from API checks and release notes |
downstream_impact | Package[] | Packages affected by changes |
Major-version signals apply to dependencies whose upstream uses compatibility-based versioning. A calendar-year change alone does not classify a dependency update as breaking.
Configuration
signal_collectors:
dependencies:
enabled: true
# Resolve from the lockfile at the release commit, and record its digest
lockfile_sha: ${RELEASE_SHA}
record_lockfile_digest: true
require_exact_sha: true
# Package managers to analyze
package_managers:
- npm
- pip
- cargo
- go
# Warn on major version bumps
warn_major_bumps: true
# Block on unreviewed new dependencies
require_new_dep_review: true
Analysis Engine
The Analysis Engine processes collected signals using LLM-powered understanding to derive insights.
LLM Core
The agent MUST use an LLM for semantic understanding tasks.
Model Selection
| Task | Recommended Model | Rationale |
|---|---|---|
| Changelog generation | Claude Sonnet / GPT-4o | Balance of quality and cost |
| Breaking change analysis | Claude Opus / GPT-4 | High-stakes decision |
| Commit classification | Claude Haiku / GPT-4o-mini | High volume, simpler task |
| Risk assessment | Claude Sonnet / GPT-4o | Requires reasoning |
System Prompt Template
You are a Release Manager Agent analyzing software changes for release readiness.
Your responsibilities:
1. Analyze commits and changes for semantic meaning
2. Identify breaking changes even when not explicitly marked
3. Assess risk level of changes
4. Generate changelog entries following the project's release policy
5. Provide release recommendations with rationale
Context about this project:
{project_context}
Current release analysis:
{collected_signals}
Respond with structured analysis following the output schema.
Semantic Understanding
The engine MUST perform semantic analysis on:
Commit Message Analysis
def analyze_commit(commit: Commit) -> CommitAnalysis:
"""
Analyze commit beyond conventional commit parsing.
Identifies:
- True intent (even if message is vague)
- User-facing impact
- Breaking change risk
- Related components
"""
pass
Code Change Analysis
def analyze_changes(diff: Diff) -> ChangeAnalysis:
"""
Analyze actual code changes for impact.
Identifies:
- API surface changes
- Behavioral changes
- Performance implications
- Security considerations
"""
pass
Pattern Matching
The engine SHOULD use pattern matching for:
- Known vulnerability patterns
- Common breaking change indicators
- Security anti-patterns
- Performance regression patterns
Historical Learning
The engine MAY learn from historical releases:
historical_learning:
enabled: true
# Learn from past release patterns
learn_from_releases: true
# Learn from rollback incidents
learn_from_rollbacks: true
# Minimum data points before learning
min_data_points: 10
Outcome Tracking
The engine SHOULD systematically track what happens after each release to learn what actually matters.
Why Outcome Tracking
Traditional release tools only know if the release happened. They don’t know:
- Did it cause production incidents?
- Was it rolled back?
- Did error rates increase?
- Did performance degrade?
- Were hotfixes needed?
Outcome tracking closes this loop, enabling the agent to learn which pre-release signals actually predict post-release problems.
Tracked Outcomes
| Outcome | Source | Tracked Metrics |
|---|---|---|
| Incidents | PagerDuty, Opsgenie, Incident.io | Count, severity, MTTR, related services |
| Rollbacks | Git, Deploy system | Time to rollback, reason, affected users |
| Error Rates | Sentry, Datadog, New Relic | Error count delta, new error types |
| Performance | APM, Prometheus | Latency p50/p95/p99, throughput |
| Hotfixes | Git, PRs | Count within 24h/72h/7d of release |
| User Impact | Analytics, Support | Ticket volume, NPS delta |
Outcome Collection
outcome_tracking:
enabled: true
# Time windows for outcome collection
windows:
immediate: 1h # Critical issues
short_term: 24h # Most issues surface here
medium_term: 7d # Slower-developing issues
long_term: 30d # Cumulative impact
# Data sources
sources:
incidents:
provider: pagerduty # pagerduty | opsgenie | incident_io
api_key: ${PAGERDUTY_API_KEY}
service_filter: ["my-service", "my-service-*"]
errors:
provider: sentry
# Read APIs are authenticated with a bearer auth token, never a DSN
base_url: https://sentry.io/api/0
auth_token: ${SENTRY_AUTH_TOKEN}
organization: my-org # organization slug or ID
projects: [my-project] # project slugs read for this release
scopes: [org:read, project:read, event:read]
pagination: link_header # follow rel="next" while results="true"
rate_limit:
respect_headers: true # X-Sentry-Rate-Limit-*
max_requests_per_second: 20
metrics:
provider: datadog
api_key: ${DATADOG_API_KEY}
app_key: ${DATADOG_APP_KEY}
deployments:
provider: github # or: argocd, spinnaker
Why Sentry Needs an Auth Token, Not a DSN
A DSN authenticates event ingestion. It encodes a public key and project
ID and is used to build submission endpoints such as
{BASE_URI}/api/{PROJECT_ID}/envelope/; the Sentry SDK specification defines
it purely as a
transport credential for submitting events.
Outcome tracking does the opposite: it reads organization and project
data. Those endpoints — for example
GET /api/0/organizations/{organization_id_or_slug}/stats_v2/, which returns
event counts by outcome — authenticate with
auth tokens
passed as Authorization: Bearer <token>, and require an organization
identifier that a DSN does not carry. Configuring only a DSN produces 401
responses, so the entire outcome-tracking loop silently yields no data.
Collectors MUST:
- Use a least-privilege token from a Sentry internal integration, not a personal token tied to one engineer’s account.
- Request only the read
scopes
the queries need —
org:readfor organization statistics,project:readfor project metadata,event:readfor issues and events. - Send the organization slug or ID and the project slugs explicitly.
- Follow Link-header pagination
until the
rel="next"link reportsresults="false", rather than reading only the first page. - Honor the
X-Sentry-Rate-Limit-*response headers and back off on 429.stats_v2is rate limited to 20 requests per second per IP, user, and organization.
Collectors MUST NOT put a DSN in a read configuration. A DSN belongs in a configuration block only where the agent itself emits telemetry to Sentry.
Outcome Report
Generated for each release after outcome windows close:
## Release Outcome Report: v1.2.0
### Release Context
- **Released**: 2025-01-01 10:30 UTC
- **Confidence Score**: 82/100
- **Changes**: 47 commits, 156 files
### Outcomes
#### Immediate (0-1h)
| Metric | Before | After | Delta | Status |
|--------|--------|-------|-------|--------|
| Error rate | 0.12% | 0.15% | +25% | ⚠️ Elevated |
| p95 latency | 145ms | 152ms | +5% | ✅ Normal |
| Incidents | 0 | 0 | - | ✅ Clear |
#### Short-term (0-24h)
| Metric | Before | After | Delta | Status |
|--------|--------|-------|-------|--------|
| Error rate | 0.12% | 0.13% | +8% | ✅ Stabilized |
| p95 latency | 145ms | 148ms | +2% | ✅ Normal |
| Incidents | 0 | 1 (P3) | +1 | ⚠️ Minor incident |
| Hotfixes | - | 0 | - | ✅ None needed |
#### Medium-term (0-7d)
| Metric | Before | After | Delta | Status |
|--------|--------|-------|-------|--------|
| Error rate | 0.12% | 0.11% | -8% | ✅ Improved |
| Support tickets | 12/day | 10/day | -17% | ✅ Improved |
| Rollback | No | No | - | ✅ Stable |
### Incident Analysis
#### INC-1234: Slow dashboard loading (P3)
- **Time to detect**: 4h 23m
- **Time to resolve**: 1h 12m
- **Root cause**: N+1 query in new analytics endpoint
- **Related commit**: `abc123` - "feat: add user analytics"
- **Learning**: Analytics features need query review
### Correlation Analysis
| Pre-Release Signal | Outcome Correlation |
|-------------------|---------------------|
| Test coverage: 78% | Incident occurred in uncovered code path |
| No perf tests | Performance regression detected |
| Security scan: clean | No security incidents |
| Review coverage: 100% | Logic error in reviewed code (false negative) |
### Learnings
1. **Query patterns in analytics code need explicit review**
- This release: N+1 query caused P3 incident
- Action: Add query count check to verification
2. **Coverage threshold should be enforced**
- This release: 78% vs 80% target, incident in gap
- Action: Raise coverage weight in confidence score
3. **Performance testing missing**
- This release: Latency increase not caught pre-release
- Action: Add performance benchmark to pipeline
### Updated Risk Factors
Based on this release, updating risk model:
| Factor | Previous Weight | New Weight | Reason |
|--------|----------------|------------|--------|
| Analytics changes | 1.0x | 1.5x | Higher incident correlation |
| Coverage below threshold | 1.2x | 1.5x | Confirmed predictor |
| Missing perf tests | 1.0x | 1.3x | New predictor |
Outcome Database Schema
-- Releases table
CREATE TABLE releases (
id UUID PRIMARY KEY,
version VARCHAR NOT NULL,
released_at TIMESTAMP NOT NULL,
confidence_score INTEGER,
commit_count INTEGER,
file_count INTEGER,
breaking_changes BOOLEAN,
signals JSONB
);
-- Outcomes table
CREATE TABLE outcomes (
id UUID PRIMARY KEY,
release_id UUID REFERENCES releases(id),
outcome_window VARCHAR NOT NULL, -- immediate, short_term, etc.
collected_at TIMESTAMP NOT NULL,
metrics JSONB,
incidents JSONB,
rollback BOOLEAN DEFAULT FALSE,
hotfix_count INTEGER DEFAULT 0
);
-- Learnings table
CREATE TABLE learnings (
id UUID PRIMARY KEY,
release_id UUID REFERENCES releases(id),
signal_type VARCHAR NOT NULL,
signal_value JSONB,
outcome_type VARCHAR NOT NULL,
outcome_value JSONB,
correlation_strength FLOAT,
created_at TIMESTAMP DEFAULT NOW()
);
Why outcome_window and not window
Column names MUST NOT collide with PostgreSQL reserved keywords.
WINDOW is reserved,
so window VARCHAR NOT NULL is rejected with syntax error at or near "window" unless the identifier is double-quoted at every use site.
outcome_window needs no quoting and keeps consuming queries readable.
Schema examples MUST be validated with a PostgreSQL parser rather than a generic YAML or Markdown check, which cannot detect reserved-word collisions.
Predictive Intelligence
The engine SHOULD use historical outcomes to predict release risk before release.
Why Prediction
Instead of reactive checks, predict problems before they occur:
- “PRs like this have 73% incident rate”
- “Friday releases have 2.1x rollback rate”
- “This author’s changes to payment/ need extra review”
Prediction enables proactive intervention, not just reactive detection.
Prediction Model
┌─────────────────────────────────────────────────────────────┐
│ PREDICTION ENGINE │
├─────────────────────────────────────────────────────────────┤
│ │
│ Input Features Output Predictions │
│ ───────────── ────────────────── │
│ • Files changed • Incident probability │
│ • Lines of code • Rollback probability │
│ • Test coverage delta • Error rate delta │
│ • Authors involved • Hotfix probability │
│ • Time of day/week • Confidence adjustment │
│ • Commit patterns │
│ • Dependency changes │
│ • Historical outcomes │
│ │
└─────────────────────────────────────────────────────────────┘
Prediction Features
| Feature | Type | Predictive Signal |
|---|---|---|
files_changed_count | Numeric | More files → higher risk |
files_changed_paths | Categorical | payment/* → higher risk |
test_coverage_delta | Numeric | Decrease → higher risk |
author_history | Derived | Author’s past incident rate |
time_of_release | Temporal | Friday PM → higher risk |
dependency_changes | Boolean | Major bumps → higher risk |
breaking_changes | Boolean | Breaking → higher risk |
review_depth | Numeric | More reviewers → lower risk |
ci_flakiness | Numeric | Flaky tests → uncertainty |
Prediction Algorithm
def predict_release_risk(
release: PendingRelease,
history: ReleaseHistory
) -> RiskPrediction:
"""
Predict release risk based on historical patterns.
Uses gradient boosting model trained on past releases
with their outcomes.
"""
# Extract features from pending release
features = extract_features(release)
# Get historical baseline for similar releases
similar_releases = history.find_similar(
files=release.files_changed,
authors=release.authors,
size=release.lines_changed
)
# Calculate base rates from history
base_incident_rate = history.overall_incident_rate()
base_rollback_rate = history.overall_rollback_rate()
# Apply learned adjustments
adjustments = []
# Time-based adjustment
if release.is_friday_afternoon():
adjustments.append(("friday_release", 1.8)) # 80% higher risk
# Path-based adjustment
for path_pattern, multiplier in history.risky_paths():
if release.touches_path(path_pattern):
adjustments.append((path_pattern, multiplier))
# Author-based adjustment
for author in release.authors:
author_history = history.author_stats(author)
if author_history.incident_rate > base_incident_rate * 1.5:
adjustments.append((f"author:{author}", 1.3))
# Coverage-based adjustment
if release.coverage_delta < 0:
adjustments.append(("coverage_decrease", 1.4))
# Calculate final predictions
incident_prob = base_incident_rate
for factor, multiplier in adjustments:
incident_prob *= multiplier
return RiskPrediction(
incident_probability=min(incident_prob, 0.95),
rollback_probability=calculate_rollback_prob(similar_releases),
confidence_adjustment=calculate_confidence_adj(adjustments),
risk_factors=adjustments,
similar_releases=similar_releases[:5],
recommendation=generate_recommendation(incident_prob)
)
Prediction Output
## Predictive Risk Assessment
### Overall Risk: ELEVATED (67th percentile)
This release is riskier than 67% of past releases based on:
### Risk Factors
| Factor | Impact | Historical Basis |
|--------|--------|------------------|
| 🔴 Changes to `payment/` | +50% risk | 73% incident rate for payment changes |
| 🟡 Friday 4pm release | +30% risk | 2.1x rollback rate for Fri PM releases |
| 🟡 Coverage decreased 3% | +20% risk | Uncovered code correlates with incidents |
| 🟢 All tests passing | -10% risk | Strong negative predictor |
| 🟢 3 reviewers approved | -15% risk | Review depth reduces incidents |
### Predicted Outcomes
| Outcome | Probability | Confidence |
|---------|-------------|------------|
| Incident within 24h | 23% | High (n=147 similar) |
| Rollback needed | 8% | Medium (n=52 similar) |
| Hotfix within 72h | 31% | High (n=147 similar) |
| Error rate increase >10% | 15% | Medium (n=89 similar) |
### Similar Past Releases
| Version | Similarity | Outcome |
|---------|------------|---------|
| v1.1.3 | 89% | ✅ Clean release |
| v1.0.8 | 85% | ⚠️ P3 incident, no rollback |
| v0.9.2 | 82% | ❌ Rolled back after 2h |
| v1.1.1 | 78% | ✅ Clean release |
| v0.8.5 | 75% | ⚠️ Hotfix needed day 2 |
### Recommendations
1. **Delay to Monday**: Friday PM releases have 2.1x rollback rate
- Alternative: Deploy to staging only, release Monday AM
2. **Add payment integration tests**: `payment/` changes without integration tests have 73% incident rate
- Specific: Add test for refund edge case in changed code
3. **Increase monitoring**: Set up dashboards before release
- Watch: Error rates, payment success rate, latency p99
### Confidence Score Adjustment
Based on predictions, adjusting confidence score:
| Original Score | Adjustment | Final Score |
|----------------|------------|-------------|
| 82 | -12 (elevated risk) | 70 |
**New recommendation**: Owner review with monitoring plan (was: ready for owner review)
Prediction Configuration
prediction:
enabled: true
# Minimum history before predictions
min_releases: 20
min_incidents: 5
# Feature weights (learned, can override)
feature_weights:
path_risk: 0.25
time_risk: 0.15
author_risk: 0.10
coverage_risk: 0.20
dependency_risk: 0.15
review_depth: 0.15
# Risky paths (learned + configured)
risky_paths:
- pattern: "payment/**"
base_multiplier: 1.5
- pattern: "auth/**"
base_multiplier: 1.4
- pattern: "database/migrations/**"
base_multiplier: 1.6
# Risky times
risky_times:
- day: friday
after: "14:00"
multiplier: 1.8
- day: "*"
after: "17:00"
multiplier: 1.3
# Thresholds for recommendations
thresholds:
delay_release: 0.40 # >40% incident probability
require_review: 0.25 # >25% incident probability
enhanced_monitoring: 0.15 # >15% incident probability
# Model retraining
retrain:
schedule: weekly
min_new_outcomes: 10
Learning Loop
┌─────────────────────────────────────────────────────────────┐
│ LEARNING LOOP │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Release │ ──▶ │ Predict │ ──▶ │ Deploy │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ ▲ │ │
│ │ ▼ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Retrain │ ◀── │ Compare │ ◀── │ Observe │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ 1. Make prediction before release │
│ 2. Deploy and observe outcomes │
│ 3. Compare predictions vs actual outcomes │
│ 4. Retrain model on new data │
│ 5. Repeat with improved predictions │
│ │
└─────────────────────────────────────────────────────────────┘
Decision Engine
The Decision Engine synthesizes analysis into actionable outputs.
Confidence Score
Every release assessment MUST include a confidence score.
Score Calculation
Confidence Score = weighted_average(
test_score × 0.30,
security_score × 0.25,
review_score × 0.20,
commit_score × 0.15,
deps_score × 0.10
)
Score Interpretation
| Score | Interpretation | Recommended Action |
|---|---|---|
| 90-100 | High confidence | Ready for owner review |
| 70-89 | Good confidence | Owner review with monitoring plan |
| 50-69 | Moderate confidence | Review recommended |
| 30-49 | Low confidence | Address issues first |
| 0-29 | Critical issues | Release blocked |
Readiness also requires no blockers and passing mandatory gates; a score MUST NOT override either requirement.
Why: averaging strong signals with a failed required check can still produce a high score.
Score Output
{
"confidence_score": 82,
"interpretation": "good",
"breakdown": {
"tests": { "score": 95, "weight": 0.30, "contribution": 28.5 },
"security": { "score": 78, "weight": 0.25, "contribution": 19.5 },
"reviews": { "score": 85, "weight": 0.20, "contribution": 17.0 },
"commits": { "score": 70, "weight": 0.15, "contribution": 10.5 },
"dependencies": { "score": 65, "weight": 0.10, "contribution": 6.5 }
},
"blockers": [],
"warnings": [
"2 dependencies have major version updates",
"Code coverage decreased by 3%"
],
"recommendation": "Owner review with enhanced monitoring plan"
}
Blocker Detection
The engine MUST identify release blockers. Any non-empty blockers array
or failed mandatory gate MUST stop the release and changelog mutation,
regardless of decision or confidence. Missing, stale, or unverifiable
mandatory gate results MUST count as failures. This rule applies to every
workflow, including manual releases and runs that skip non-critical checks.
Why: a recommendation or weighted average cannot negate a failed required
check. The executable CI guard in the
release-manager definition
requires ci, tests, security, reviews, and evidence gate results to
be explicitly true before exposing successful readiness outputs.
Do: stop on a failed security gate, even with decision=release and confidence=100
Don't: publish because the decision says release while blockers is non-empty
Blocker Categories
blockers:
critical:
- failing_required_workflow
- critical_vulnerability
- unresolved_security_finding
- missing_required_approval
high:
- high_vulnerability
- failing_tests
- coverage_below_threshold
- unresolved_review_comments
configurable:
- major_dependency_update
- new_unreviewed_dependency
- missing_changelog_entry
Blocker Output
{
"blockers": [
{
"id": "SEC-001",
"category": "security",
"severity": "critical",
"title": "Critical vulnerability in lodash",
"description": "CVE-2021-XXXXX allows prototype pollution",
"remediation": "Update lodash to 4.17.21 or later",
"affected_files": ["package.json", "package-lock.json"],
"auto_fixable": true
}
],
"blocker_count": 1,
"release_blocked": true
}
Version Calculation
The engine MUST classify the project using the Versioning Guide. A library is a package published for other code to depend on through a package manager or module system. A project with both a command and importable packages is a library. Libraries in every ecosystem MUST use Semantic Versioning 2.0.0: MAJOR for breaking changes, MINOR for compatible features, and PATCH for fixes. Breaking changes take precedence over features, then fixes.
Anything else MUST use the next calendar version (YYYY.M.N) from
the release date and release history. Go libraries use SemVer tags such
as v0.x.y or v1.x.y, with /vN module paths at major version 2 or
higher. Command-only Go tools use calendar tags and MUST be built from
checkouts or shipped as release artifacts, not installed with
go install with module@vYYYY.M.N.
Why
Dependency resolvers assume SemVer compatibility ranges for libraries.
Commit types and compatibility do not determine a calendar release number.
The monthly counter starts at 0 and increments beyond the highest release
already made that month; breaking changes remain an independent flag and
the first ### Breaking changelog section.
Version Logic
This example accepts stable numeric versions without tag prefixes, including
historical SemVer releases before a non-library project’s CalVer migration.
The caller supplies the project classification and classified changes.
For a library with no releases, bumps start from 0.0.0; a release needs
breaking, feature, or fix changes. SemVer defines library pre-releases;
CalVer pre-release suffix policy remains undecided and is outside this
example:
import re
from dataclasses import dataclass
from datetime import date
@dataclass(frozen=True)
class VersionRecommendation:
version: str
reason: str
breaking_changes: tuple[str, ...]
def calculate_version(
released_versions: list[str],
release_date: date,
breaking_changes: list[str],
*,
is_library: bool,
feature_changes: list[str],
fix_changes: list[str],
) -> VersionRecommendation:
"""Select a library's SemVer bump or anything else's next CalVer number."""
versions = []
for version in released_versions:
match = re.fullmatch(
r"(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)",
version,
)
if match is None:
raise ValueError(f"invalid numeric release version: {version}")
versions.append(tuple(map(int, match.groups())))
if is_library:
major, minor, patch = max(versions, default=(0, 0, 0))
if breaking_changes:
version = f"{major + 1}.0.0"
reason = "Breaking library changes require a MAJOR bump"
elif feature_changes:
version = f"{major}.{minor + 1}.0"
reason = "Compatible library features require a MINOR bump"
elif fix_changes:
version = f"{major}.{minor}.{patch + 1}"
reason = "Library fixes require a PATCH bump"
else:
raise ValueError("library release needs breaking, feature or fix changes")
return VersionRecommendation(version, reason, tuple(breaking_changes))
if release_date.year < 1000:
raise ValueError("release year must have four digits")
counters = []
for year, month, counter in versions:
if year >= 1000 and (year > 9999 or not 1 <= month <= 12):
raise ValueError(f"invalid calendar release version: {year}.{month}.{counter}")
if (year, month) == (release_date.year, release_date.month):
counters.append(counter)
counter = max(counters, default=-1) + 1
return VersionRecommendation(
version=f"{release_date.year}.{release_date.month}.{counter}",
reason="Next release number for the release month",
breaking_changes=tuple(breaking_changes),
)
Do: bump a library from 1.4.2 to 2.0.0 for breaking changes,
1.5.0 for compatible features, or 1.4.3 for fixes. For anything else,
compute 2026.10.1 after 2026.10.0 regardless of change type.
Don’t: use the monthly counter for a library or a compatibility bump
for an application.
Changelog Generation
The agent MUST generate changelogs from the collected commits and pull requests.
Release-note wording, audience, tone, announcements, and approval to publish MUST follow the project’s own release policy.
Why: crefft defines version, tag, changelog, and CI mechanics; the project owns its public communication and publication decisions.
Changelog Format
Keep a Changelog Format (Default)
This is a CalVer example. For libraries, the agent MUST declare
Semantic Versioning 2.0.0 and use SemVer release headings and comparison
links instead, keeping ### Breaking first when present.
Describe completed changes in past tense (Added, Fixed, Improved), matching
the Changelog Agent.
Keep a Changelog’s convention groups completed changes by category and release;
it does not mandate an entry tense or an announcement style.
Why: consistent grammar removes the past-versus-present-tense conflict between the two prompts without imposing a release voice on the project.
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to calendar versioning (`YYYY.M.N`).
## [Unreleased]
## [2026.10.0] - 2026-10-17
### Breaking
- Renamed `getUserProfile()` to `getProfile()`; update callers (#312).
### Added
- User profile editing with avatar upload support (#234)
- Rate limiting for API endpoints to prevent abuse (#256)
### Changed
- Improved error messages for authentication failures (#245)
- Updated dashboard layout for better mobile experience (#251)
### Fixed
- Resolved race condition in concurrent session handling (#242)
- Fixed timezone display in event scheduling (#248)
### Security
- Patched XSS vulnerability in comment rendering (#253)
## [2026.9.0] - 2026-09-15
...
Generation Process
def generate_changelog(
commits: List[Commit],
prs: List[PullRequest],
config: ChangelogConfig
) -> Changelog:
"""
Generate changelog using LLM understanding.
Process:
1. Group changes by category
2. Summarize each change per the project's release policy
3. Link to PRs/issues
4. Highlight breaking changes
5. Format per configured style
"""
# Use LLM for semantic grouping
grouped = llm.group_changes(commits, prs)
# Audience and style come from the project's release policy.
entries = []
for group in grouped:
entry = llm.generate_entry(
changes=group.changes,
audience=config.audience, # "developers" | "users" | "both"
style=config.style, # "concise" | "detailed"
max_length=config.max_entry_length
)
entries.append(entry)
return Changelog(
version=calculated_version,
date=today(),
entries=entries,
breaking_changes=extract_breaking(entries),
contributors=extract_contributors(commits)
)
LLM Changelog Prompt
Generate a changelog entry for the following changes.
Audience from the project's release policy: {audience}
Style from the project's release policy: {style}
Changes:
{changes_json}
Requirements:
1. Follow the project's own release policy for release-note wording,
announcements, and approval to publish
2. Describe what changed, using past tense for completed changes
3. Group related changes by Keep a Changelog category
4. Put breaking changes and migrations under ### Breaking, first in the release section
5. Include issue/PR references where relevant
Output format:
{format_template}
Configuration
changelog:
# Output file path
file: CHANGELOG.md
# Format style
format: keep-a-changelog # or: conventional, github-releases, custom
# Entry generation: set these values from the project's own release policy.
audience: users # or: developers, both
style: concise # or: detailed
max_entry_length: 200
# Sections to include
sections:
- Breaking
- Added
- Changed
- Deprecated
- Removed
- Fixed
- Security
# Include contributor list
include_contributors: true
# Link format for issues/PRs
link_format: "[#{number}]({url})"
# Header template
header_template: |
## [{version}] - {date}
Release Workflows
The agent supports multiple release workflow patterns. Every pattern MUST
enforce require_no_blockers, require_all_checks_pass, and
require_mandatory_gates_pass before release actions or changelog mutation.
These conditions are not overridable by confidence, approval, or skip_checks.
Here, “all checks” means all required checks; skip_checks applies only to advisory checks.
Why: a scheduled or manual preparation has the same mandatory safety gates as preparation triggered by a merge.
Prepare, Then Stop
Prepare a private proposal on a qualifying merge to main. The agent never
executes the publication actions. The legacy continuous and scheduled
workflow selectors below describe preparation triggers only.
workflow: continuous
prepare_only: true
trigger:
branches: [main]
paths_ignore:
- "docs/**"
- "*.md"
conditions:
min_confidence_score: 90
require_all_checks_pass: true
require_no_blockers: true
require_mandatory_gates_pass: true
fail_on_missing_gate_results: true
actions:
- calculate_version
- draft_changelog
- draft_release_notes
- collect_sha_bound_evidence
- prepare_private_change_proposal
- stop_for_owner_review
Scheduled Preparation
Prepare on a schedule set by the owner (e.g., weekly); scheduling never approves publication.
workflow: scheduled
prepare_only: true
schedule:
cron: "0 10 * * 1" # Every Monday at 10 AM
conditions:
min_changes: 1
min_confidence_score: 70
require_all_checks_pass: true
require_no_blockers: true
require_mandatory_gates_pass: true
fail_on_missing_gate_results: true
actions:
- prepare_private_change_proposal
- stop_for_owner_review
Manual Release
Person-triggered preparation with agent assistance. Publication remains a separate protected step after the agent stops.
A supplied version override MUST match the calculated library SemVer bump or non-library CalVer number, or the release MUST stop with an error. An override is not a way to bypass the project’s versioning policy.
workflow: manual
trigger:
workflow_dispatch:
inputs:
version_override:
description: "Requested version; must match the calculated release number"
required: false
skip_checks:
description: "Skip non-critical checks"
required: false
default: false
conditions:
require_all_checks_pass: true
require_no_blockers: true
require_mandatory_gates_pass: true
fail_on_missing_gate_results: true
actions:
- analyze_changes
- calculate_version
- validate_version_override
- present_report
- prepare_private_change_proposal
- stop_for_owner_review
Release Train
Batched releases with multiple stages. This is an owner-operated deployment plan, not agent authority; each deployment requires a person’s approval.
workflow: release_train
conditions:
require_all_checks_pass: true
require_no_blockers: true
require_mandatory_gates_pass: true
fail_on_missing_gate_results: true
stages:
- name: staging
auto_deploy: false
require_approval: true
duration: 24h
rollback_threshold: 5 # % error rate
- name: canary
auto_deploy: false
require_approval: true
traffic_percentage: 10
duration: 48h
rollback_threshold: 2
- name: production
auto_deploy: false
require_approval: true
approver: "${NAMED_RELEASE_OWNER}"
Integrations
GitHub Integration
Primary integration for GitHub-hosted repositories.
Required Permissions
permissions:
contents: read # Read history; cannot create releases or tags
pull-requests: read # Read review evidence
issues: read # Link issues in changelog
actions: read # Read workflow status
security-events: read # Read security alerts
GitHub Actions Workflow
The crefft/release-manager-agent action below is illustrative: it
names the interface a conforming implementation would expose, not a published
action. Substitute your own implementation before running this workflow.
This example runs in a private repository so the proposal artifact stays private. For a public source repository, prepare and review in a separate private proposal repository using read-only access to the source.
Configure release-publication before enabling this example: required reviewers
MUST be named people, self-review and administrator bypass MUST be
disabled, and deployment branches MUST allow only the protected default
branch. Restrict dispatch to maintainers, and deny automated identities access
to that role and to environment approval. The publishing job runs no agent and
receives no model credential. Package publication and announcements, if needed,
belong to similarly protected, person-triggered steps, not the agent job.
Why: a push can prepare a recommendation, but cannot cross the protected environment boundary. Only a person’s dispatch and recorded approval unlock the separate publishing credential.
name: Prepare and review release
on:
push:
branches: [main]
workflow_dispatch:
inputs:
publish:
description: "Request owner approval to publish this prepared commit"
type: boolean
default: false
permissions: {}
jobs:
prepare:
if: github.event.repository.private == true
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
actions: read
security-events: read
outputs:
sha: ${{ steps.target.outputs.sha }}
version: ${{ steps.proposal.outputs.version }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0 # Full history for changelog
ref: ${{ github.sha }} # Pin the tree; do not follow the branch
persist-credentials: false
- name: Resolve release commit
id: target
run: echo "sha=$(git rev-parse HEAD)" >> "${GITHUB_OUTPUT}"
- name: Prepare local proposal and stop
id: proposal
# crefft: illustrative -- This unpublished action only specifies an interface.
uses: crefft/release-manager-agent@v1 # illustrative
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
config_file: .release-manager.yml
prepare_only: true
proposal_dir: release-proposal
# Every collector binds its evidence to this SHA
release_sha: ${{ steps.target.outputs.sha }}
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-proposal
path: |
release-proposal/VERSION
release-proposal/CHANGELOG.md
release-proposal/RELEASE_NOTES.md
release-proposal/evidence.json
if-no-files-found: error
retention-days: 1
publish:
if: >-
github.event.repository.private == true &&
github.event_name == 'workflow_dispatch' && inputs.publish &&
github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
needs: prepare
environment: release-publication
runs-on: ubuntu-latest
permissions:
contents: write # Person-approved publisher only; no agent runs in this job
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.prepare.outputs.sha }}
fetch-depth: 0
persist-credentials: false
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: release-proposal
path: release-proposal
- name: Publish the proposal approved by the named environment reviewer
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ needs.prepare.outputs.version }}
RELEASE_SHA: ${{ needs.prepare.outputs.sha }}
run: |
set -euo pipefail
test "$(cat release-proposal/VERSION)" = "${VERSION}"
test -s release-proposal/CHANGELOG.md
test -s release-proposal/RELEASE_NOTES.md
jq -e --arg sha "${RELEASE_SHA}" \
'.target_sha == $sha and .blockers == [] and
(.mandatory_gates | length == 5 and
has("ci") and has("tests") and has("security") and
has("reviews") and has("evidence") and all(.[]; . == true))' \
release-proposal/evidence.json
if git show-ref --verify --quiet "refs/tags/v${VERSION}"; then
echo "Tag already exists; stop for owner review" >&2
exit 1
fi
gh release create "v${VERSION}" --target "${RELEASE_SHA}" \
--title "v${VERSION}" --notes-file release-proposal/RELEASE_NOTES.md
The illustrative preparation action MUST enforce the existing
release guard, return a
validated version output, and produce the four proposal files shown above.
evidence.json contains target_sha, blockers, the five mandatory_gates,
and links to the underlying runs and artifacts. It contains no transcript.
Before approving, the named reviewer MUST inspect those exact proposal
files, evidence expiry, artifact digests, and the workflow revision. Any change
to the proposal or target MUST require a new preparation and approval.
Keep approval and evidence records private; use a private proposal store when
the source repository is public.
A full-history checkout supplies commits, not evidence identity. fetch-depth: 0 says nothing about which tree the tests ran against or which artifact was
scanned, so release_sha MUST be resolved once and passed to every
collector, and the collectors MUST apply the rules in
Evidence Binding.
GitLab Integration
The crefft/release-manager-agent image and the release-manager CLI below
are illustrative: neither is published by this repository. Substitute your
own image, pinned to a digest, before running this job.
# .gitlab-ci.yml
prepare:
stage: release
image: crefft/release-manager-agent:2026.10.0 # Illustrative, locally built release
script:
- release-manager analyze
- release-manager prepare --output release-proposal
only:
- main
variables:
GITLAB_TOKEN: $READ_ONLY_GITLAB_TOKEN
ANTHROPIC_API_KEY: $ANTHROPIC_API_KEY
Slack Announcement Drafts
The agent returns draft text locally. Only a person-triggered notification step holds the webhook and posts an approved announcement.
notifications:
slack:
enabled: false
draft_only: true
channel: "#releases"
events:
- release_started
- release_completed
- release_failed
- blocker_detected
template: |
:rocket: *{project}* {version} released!
{changelog_summary}
<{release_url}|View Release>
JIRA Integration
The agent MUST use read-only ticket access and return proposed updates locally, not transition tickets or post release announcements.
Why: issue context is release evidence, not authority to act as the release owner.
integrations:
jira:
enabled: true
access: readonly
host: https://company.atlassian.net
auth:
type: api_token
email: ${JIRA_EMAIL}
token: ${JIRA_API_TOKEN}
collect:
- issue_links
- issue_status
- release_context
Configuration
Configuration File
The agent MUST be configured via .release-manager.yml in the repository root.
Complete Configuration Reference
# .release-manager.yml
# Agent version
version: "1.0"
# Project metadata
project:
name: my-project
type: library # library | application | monorepo
# LLM configuration
llm:
provider: anthropic # anthropic | openai | azure
# Pinned snapshot IDs only; verify against the provider model list on every run
model: claude-sonnet-5
# Fallback model for high-volume, simpler tasks
fallback_model: claude-haiku-4-5-20251001
# Thinking depth on Claude 5 models; replaces temperature (see below)
effort: high
# Reject unknown, deprecated or retired IDs before any release work starts
model_lifecycle_check:
enabled: true
fail_closed: true
max_metadata_age: 24h
# Signal collectors configuration
signal_collectors:
git:
enabled: true
branch: main
tag_pattern: "v*"
cicd:
enabled: true
platform: github_actions
required_workflows:
- "CI"
- "Tests"
security:
enabled: true
blocking_severities: [critical, high]
code_review:
enabled: true
min_approvals: 1
dependencies:
enabled: true
package_managers: [npm]
# Decision engine configuration
decision:
require_all_checks_pass: true
require_no_blockers: true
require_mandatory_gates_pass: true
fail_on_missing_gate_results: true
# Confidence score thresholds
thresholds:
owner_review: 90
release_with_review: 70
block_release: 30
# Weight adjustments
weights:
tests: 0.30
security: 0.25
reviews: 0.20
commits: 0.15
dependencies: 0.10
# Changelog configuration
changelog:
file: CHANGELOG.md
format: keep-a-changelog
audience: users
style: concise
include_contributors: true
# Preparation configuration; publication credentials are configured separately
release:
prepare_only: true
# Version file locations
version_files:
- package.json
- VERSION
# Git configuration
git:
tag_prefix: "v"
sign_tags: false
commit_message: "chore(release): {version}"
# Draft notes only; the agent cannot create a GitHub Release
github_release:
enabled: false
draft: true
prerelease_pattern: "*-*" # e.g., 1.0.0-beta.1
# Package publishing
publish:
npm:
enabled: false
registry: https://registry.npmjs.org
access: public
# Workflow configuration
workflow:
type: preparation # preparation | scheduled_preparation | manual | release_train
preparation:
owner_review_threshold: 90
private_proposal_threshold: 70
stop_after_proposal: true
# Notifications
notifications:
slack:
enabled: false
draft_only: true
channel: "#releases"
# Advanced settings
advanced:
# Enable historical learning
historical_learning: true
# Cache LLM responses
cache_responses: true
cache_ttl: 3600
# Dry run mode
dry_run: false
# Debug logging
debug: false
Model Selection and Lifecycle
Configured model IDs MUST be exact, currently available snapshot IDs from the provider. The agent MUST validate every configured ID against the provider’s model list before collecting signals, and MUST refuse to run when an ID is unknown, deprecated, or retired.
Why
A release gate that cannot reach its model produces no assessment, and a
release pipeline that silently falls back to an unvalidated model produces an
assessment nobody reviewed. Model IDs also churn: Anthropic
retired claude-3-5-haiku-20241022 on 2026-02-19,
and requests to retired models fail. Validating up front turns a mid-release
API failure into a configuration error before any state is mutated.
Current Anthropic IDs
Verified against the Claude models overview and model deprecations (both fetched 2026-09-08):
| Role | Model ID | Lifecycle state |
|---|---|---|
| Release decision, breaking-change analysis | claude-opus-5 | Active, retirement not sooner than 2027-07-24 |
| Changelog generation, risk assessment | claude-sonnet-5 | Active, retirement not sooner than 2027-06-30 |
| Commit classification (high volume) | claude-haiku-4-5-20251001 | Active, retirement not sooner than 2026-10-15 |
These are starting points, not a fixed ranking. Teams SHOULD select the model for each task from their own evaluations on their own repositories, and MUST re-run the lifecycle check on a schedule rather than trusting a static table.
Why effort Replaces temperature
Claude 5 models reject non-default sampling parameters: setting temperature,
top_p, or top_k returns
HTTP 400.
Thinking depth is controlled with the
effort parameter
instead. Configuration MUST NOT carry temperature for these models.
Don’t — rejected with 400 on claude-sonnet-5:
llm:
model: claude-sonnet-5
temperature: 0.3
Do — determinism comes from schema-constrained output and deterministic gates, not from a sampling parameter:
llm:
model: claude-sonnet-5
effort: high
Lifecycle Validation
The check MUST run before signal collection and MUST fail closed. The Models API returns the IDs currently available to the calling account; retired IDs are absent from it and requests naming them fail.
#!/usr/bin/env bash
# check-models.sh claude-sonnet-5 claude-haiku-4-5-20251001
set -euo pipefail
# limit defaults to 20; ask for the full list so long lineups are not truncated.
available="$(curl -sSf 'https://api.anthropic.com/v1/models?limit=1000' \
-H @- <<<"x-api-key: ${ANTHROPIC_API_KEY}" \
-H 'anthropic-version: 2023-06-01' | jq -r '.data[].id')"
status=0
for id in "$@"; do
if ! grep -qxF -- "${id}" <<<"${available}"; then
printf 'release-manager: model %s is unknown or retired\n' "${id}" >&2
status=1
fi
done
exit "${status}"
Why: the single-line API key header is read from stdin rather than appearing in process arguments; shell tracing MUST NOT be enabled.
Environment Variables
| Variable | Required | Description |
|---|---|---|
ANTHROPIC_API_KEY | Yes* | Anthropic API key |
OPENAI_API_KEY | Yes* | OpenAI API key |
GITHUB_TOKEN | Yes | GitHub access token |
SLACK_WEBHOOK_URL | No | Slack notification webhook |
JIRA_API_TOKEN | No | JIRA integration token |
*One LLM provider key required
Security Considerations
Secrets Management
The agent MUST NOT:
- Log API keys or tokens
- Include secrets in changelogs
- Expose secrets in error messages
- Store secrets in plaintext
The agent MUST:
- Use environment variables for secrets
- Support secret managers (Vault, AWS Secrets Manager)
- Mask secrets in output
- Rotate tokens regularly
LLM Data Privacy
Content MUST be sanitized before prompt construction, not after. Once a credential reaches the provider it has left the trust boundary, and no downstream masking undoes that.
Secret handling is therefore two stages: detect and drop, then redact what remains.
llm:
privacy:
# Don't send actual code to LLM
send_code: false # Send summaries only
# Exclude sensitive paths
exclude_paths:
- "**/secrets/**"
- "**/*.env*"
- "**/credentials*"
# Stage 1: scan every candidate input with a maintained detector.
secret_scan:
tool: gitleaks
version: v8.30.1 # pinned; bump deliberately
on_detection: drop # drop the whole input, never send a patched copy
fail_closed: true # scanner error or timeout blocks the run
# Stage 2: format-aware redaction of everything that survives stage 1.
redaction:
mode: structured # parse JSON/YAML/dotenv, redact by key
case_sensitive: false
key_patterns: # matched against keys, not whole lines
- "pass(word|wd|phrase)"
- "secret"
- "token"
- "api[_-]?key"
- "access[_-]?key"
- "auth(orization|[_-]?token)?"
- "credentials?"
- "private[_-]?key"
- "session[_-]?id"
value_patterns: # forms with no key at all
- credential_header # Authorization: Bearer <token>
- url_userinfo # scheme://user:secret@host
- pem_block # -----BEGIN ... PRIVATE KEY-----
verify_after_redaction: true # re-scan; refuse to send if anything matches
Why Line-Oriented Regexes Are Not Enough
The patterns this guide previously recommended — password[=:].*,
api[_-]?key[=:].*, and secret[=:].* — miss most real credential formats.
Executed against a 14-case corpus of ordinary credential shapes, 10 samples
survived redaction intact: uppercase PASSWORD=, quoted JSON
"password": "…" and "api_key": "…", Authorization: Bearer …, token=…,
private_key=…, AWS_ACCESS_KEY_ID = …, passphrase: '…', a database URL
with inline userinfo, and a multiline PEM block. None of these are adversarial
or encoded; they are what configuration files and log lines normally look
like.
Path exclusions do not compensate. A credential pasted into a commit message,
a test fixture, or a stack trace lives outside **/secrets/**, and
send_code: false still ships summaries derived from that text.
Reference Redaction
The following implementation redacts all 14 corpus cases with no false positives on benign text, and is idempotent so the verification pass is a simple equality check:
import re
PLACEHOLDER = "[REDACTED]"
SECRET_KEY = r"""(?:
pass(?:word|wd|phrase) | secret | token | api[_-]?key
| access[_-]?key | auth(?:orization|[_-]?token)? | credentials?
| private[_-]?key | session[_-]?id
)"""
ASSIGNMENT = re.compile(
rf"""(?ix)
(?P<key>["']?[A-Za-z0-9_.\-]*{SECRET_KEY}[A-Za-z0-9_.\-]*["']?)
(?P<sep>[ \t]*[:=][ \t]*)
(?P<value>\[REDACTED\] | "[^"\n]*" | '[^'\n]*' | [^\s,;}}\]]+)
"""
)
CREDENTIAL_HEADER = re.compile(
r"(?i)\b(?P<scheme>bearer|basic|token|dsn)[ \t]+"
r"(?P<value>[A-Za-z0-9._~+/=\-]{8,})"
)
URL_USERINFO = re.compile(
r"(?i)(?P<scheme>[a-z][a-z0-9+.\-]*://)"
r"(?P<user>[^/\s:@]+):(?P<secret>[^/\s@]+)@"
)
PEM_BLOCK = re.compile(
r"(?s)-----BEGIN(?P<label>[A-Z ]*)PRIVATE KEY-----"
r"(?P<body>.*?)-----END(?P=label)PRIVATE KEY-----"
)
def _assignment(m: re.Match) -> str:
value = m.group("value")
if value.strip("\"'") == PLACEHOLDER:
return m.group(0)
quote = value[0] if value[:1] in {'"', "'"} else ""
return f"{m.group('key')}{m.group('sep')}{quote}{PLACEHOLDER}{quote}"
def _userinfo(m: re.Match) -> str:
if m.group("secret") == PLACEHOLDER:
return m.group(0)
return f"{m.group('scheme')}{m.group('user')}:{PLACEHOLDER}@"
def _pem(m: re.Match) -> str:
if m.group("body") == PLACEHOLDER:
return m.group(0)
label = m.group("label")
return f"-----BEGIN{label}PRIVATE KEY-----{PLACEHOLDER}-----END{label}PRIVATE KEY-----"
def redact(text: str) -> str:
"""Redact credentials in structured and unstructured text. Idempotent."""
text = PEM_BLOCK.sub(_pem, text)
text = URL_USERINFO.sub(_userinfo, text)
text = CREDENTIAL_HEADER.sub(lambda m: f"{m.group('scheme')} {PLACEHOLDER}", text)
return ASSIGNMENT.sub(_assignment, text)
def is_clean(text: str) -> bool:
"""Fail-closed gate: run after redaction, before prompt construction."""
return redact(text) == text
Ordering matters. PEM_BLOCK runs before the line-oriented rules so a
multiline key is not truncated at its first newline, and CREDENTIAL_HEADER
runs before ASSIGNMENT so Authorization: Bearer <token> redacts the token
rather than the scheme name.
Regression Corpus
Redaction rules MUST ship with a regression test, and the test MUST assert on the redacted output rather than on pattern coverage. Every entry below MUST be covered:
| Format | Example shape |
|---|---|
| dotenv, lower and upper case | password=…, PASSWORD=… |
| JSON, quoted key and value | {"api_key": "…"} |
| YAML, quoted and bare | client_secret: … |
| Authorization header | Authorization: Bearer … |
| Custom header | x-api-key: … |
| Bare assignment | token=…, private_key=… |
| Spaced assignment | AWS_ACCESS_KEY_ID = … |
| URL userinfo | postgres://app:…@host/db |
| Multiline PEM | -----BEGIN … PRIVATE KEY----- |
The corpus MUST also contain benign text that redaction leaves byte-identical
(The password reset flow sends an email., timeout = 30), so tightening the
rules cannot quietly start mangling changelog prose.
Don’t — treat redaction as best-effort and send whatever comes out:
redact_patterns:
- "password[=:].*"
- "api[_-]?key[=:].*"
- "secret[=:].*"
Do — scan, drop, redact, verify, and refuse to build the prompt when verification fails:
if scanner.findings(content):
raise SecretDetected(source) # drop, do not patch and send
redacted = redact(content)
if not is_clean(redacted):
raise RedactionUnverified(source) # fail closed
prompt = build_prompt(redacted)
Audit Logging
The agent SHOULD log all actions:
audit:
enabled: true
destinations:
- file: /var/log/release-manager/audit.log
- syslog: true
events:
- release_started
- release_completed
- release_failed
- config_changed
- manual_override
CLI Reference
This section describes the command surface a conforming implementation
SHOULD expose. The release-manager binary and the synthetic output
below are illustrative; no such binary is published by this repository.
Commands
# Analyze release readiness
release-manager analyze [--output json|text|markdown]
# Generate changelog preview
release-manager changelog [--dry-run] [--since <tag>]
# Calculate next version
release-manager version
# Prepare a private proposal, then stop
release-manager prepare --output release-proposal
# Show release report
release-manager report [--format html|json|markdown]
# Validate configuration
release-manager validate
# Initialize configuration
release-manager init [--preset minimal|standard|enterprise]
Example Usage
# Quick release check
$ release-manager analyze
✓ Collected 47 commits since v2026.10.0
✓ All CI checks passing
✓ No security blockers
✓ 12 PRs with required approvals
Release Readiness: 87/100 (Good)
Recommended Version: 2026.10.1 (next October release; breaking changes flagged separately)
Ready for owner review; no release has been approved.
# Generate changelog preview
$ release-manager changelog --dry-run
## [2026.10.1] - 2026-10-20
### Breaking
- Renamed `getUserProfile()` to `getProfile()`; update callers (#312).
### Added
- User profile editing with avatar upload (#234)
- Rate limiting for API endpoints (#256)
### Fixed
- Race condition in session handling (#242)
- Timezone display in event scheduling (#248)
# Prepare a private proposal
$ release-manager prepare --output release-proposal
✓ Version, changelog, notes, and SHA-bound evidence drafted
Stopped: a named person must review and trigger the separate protected publisher.
Metrics & Observability
Exported Metrics
metrics:
enabled: true
format: prometheus # prometheus | statsd | cloudwatch
# Available metrics
# release_manager_releases_total
# release_manager_confidence_score
# release_manager_blockers_detected
# release_manager_changelog_generation_seconds
# release_manager_llm_tokens_used
# release_manager_release_duration_seconds
Dashboard Queries
# Release frequency
rate(release_manager_releases_total[7d])
# Average confidence score
avg_over_time(release_manager_confidence_score[30d])
# Blocker detection rate
sum(release_manager_blockers_detected) by (severity)
See Also
- AI Workflows - General AI development patterns
- Claude Best Practices - LLM usage guidelines
- Versioning Guide - Library SemVer and CalVer mechanics
- CI Guide - Continuous integration patterns
- GitHub Templates - PR and issue templates
References
- Calendar Versioning
- Keep a Changelog
- Conventional Commits
- semantic-release
- release-please
- changesets
Version: 1.0.0 Last Updated: 2025-01-02 Status: Draft Specification