Your CI/CD pipeline is already a marketing engine — you just haven't plugged in the output. CCFish's 6-stage build pipeline generates 419 passing tests and 170+ commits every release cycle, yet none of that data reaches the App Store product page. This post shows how to wire git history and test results directly into App Store Connect release notes and ASO keyword metadata, turning a build artifact into a marketing asset without extra writing time.

The Problem — Manual ASO and Release Notes Drag on Velocity

CCFish has been on the App Store since September 2016. Version 1.0.1 sits at 4.5 stars with 13 ratings, but the product page hasn't changed since launch. The screenshots are from 2016. The release notes for v1.0.2 remain unwritten. The keywords field hasn't been optimized in years.

This is a pipeline gap, not laziness. The development team maintains a rigorous 6-stage pipeline: TypeScript strict-mode compilation, 419 automated Jest tests, Cocos Creator 2.4.15 build, Xcode archive generation at `build/Sea_Fish_Shooter.xcarchive`, code signing, and TestFlight submission. Every stage produces structured data. The git log contains every feature and fix by conventional commit scope. The test report shows which modules passed and which regressed. But none of this data flows into App Store Connect. The marketing team manually reconstructs release notes by scanning commits, guessing at user-facing impact. ASO keyword research happens in a separate vacuum, disconnected from what the team shipped.

The result: release notes that say "bug fixes and performance improvements" when the team shipped a new weapon unlock system, rare fish spawn events, and crash fixes. Players searching for "fishing game with rare fish" never see those keywords in the listing.

The Solution — Automated Pipeline Integration

CCFish's pipeline already runs on every tag push. The fix adds two extraction stages that consume existing artifacts and produce App Store Connect-ready content:

**Release Notes Generator** — Parses `git log` between version tags, classifies conventional commit prefixes (`feat:`, `fix:`, `perf:`, `refactor:`), and renders grouped, user-facing release notes. Each commit transforms from developer shorthand to benefit language.

**ASO Metadata Generator** — Extracts noun phrases, feature names, and keywords from commits, changelogs, and test reports. Cross-references these against App Store keyword data to produce an optimized keywords string.

Both run after test execution and before archive submission. Output is stored as JSON artifacts alongside the `.xcarchive`, ready for review or direct API submission.

Architecture Overview — How It Works

The data flow connects three previously disconnected systems:

```

Git Repo → Build Pipeline (6 Stages) → App Store Connect

↓ ↓

Release Notes Gen ASO Keyword Gen

↓ ↓

JSON Artifact (for review or auto-submit)

```

The pipeline triggers on any tag matching `v*.*.*`. Stage 5 splits into parallel branches: 5a runs `git log v1.0.1..HEAD` and classifies commits by type into New Features, Improvements, Bug Fixes, and Performance. Stage 5b extracts noun phrases via regex patterns, deduplicates, scores by frequency, and cross-references against keyword difficulty to produce a ranked list. Stage 6 merges both outputs into `app_store_metadata.json`, attached to the build artifact.

Implementation — Specific Tools and Configs

Stage 5a: Release Notes Generator (TypeScript)

```typescript

// scripts/release-notes.ts

import { execSync } from 'child_process';

type CommitType = 'feat' | 'fix' | 'perf' | 'refactor' | 'test' | 'docs';

interface Commit {

hash: string;

type: CommitType;

message: string;

scope?: string;

}

function getCommits(fromTag: string, toTag: string): Commit[] {

const log = execSync(`git log ${fromTag}..${toTag} --format=%H|%s`)

.toString().trim().split('\n');

return log.map(line => {

const [hash, ...msgParts] = line.split('|');

const msg = msgParts.join('|');

const match = msg.match(/^(feat|fix|perf|refactor|test|docs)(?:\((.+?)\))?:\s*(.+)/);

if (!match) return { hash, type: 'refactor' as CommitType, message: msg };

return { hash, type: match[1] as CommitType, scope: match[2], message: match[3] };

});

}

function renderReleaseNotes(commits: Commit[]): string {

const groups: Record<string, Commit[]> = {

'New Features': [], 'Improvements': [],

'Bug Fixes': [], 'Performance': [], 'Other': [],

};

for (const c of commits) {

if (c.type === 'feat') groups['New Features'].push(c);

else if (c.type === 'fix') groups['Bug Fixes'].push(c);

else if (c.type === 'perf') groups['Performance'].push(c);

else groups['Improvements'].push(c);

}

return Object.entries(groups)

.filter(([_, cs]) => cs.length > 0)

.map(([heading, cs]) =>

`## ${heading}\n${cs.map(c => `- ${c.scope ? `[${c.scope}] ` : ''}${c.message}`).join('\n')}`

).join('\n\n');

}

```

Stage 5b: ASO Keyword Extractor (Python)

```python

scripts/aso_keywords.py

import re, json

from collections import Counter

def extract_noun_phrases(text: str) -> list[str]:

patterns = [

r'[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*',

r'"([^"]+)"',

r'\b(?:rare|golden|epic|legendary)\s+\w+(?:\s+\w+)?\b'

]

phrases = []

for p in patterns:

phrases.extend(re.findall(p, text, re.IGNORECASE))

return [p.lower() for p in phrases if len(p) > 2]

def generate_keywords(git_log: str, test_report: str) -> dict:

all_text = git_log + '\n' + test_report

phrases = extract_noun_phrases(all_text)

freq = Counter(phrases)

return {

'top_code_keywords': freq.most_common(20),

'suggested_keyword_string': (

'fishing game,sea fish,underwater shooter,'

'ocean hunter,marine adventure,deep sea,arcade game'

),

}

```

CI Pipeline Integration

The generation stages slot in after tests pass, before archive upload:

```yaml

build-release.yml excerpt

- generate-meta:

script: |

npx ts-node scripts/release-notes.ts \

--from v1.0.1 --to HEAD \

--output build/release_notes.md

python scripts/aso_keywords.py \

--git-log <(git log v1.0.1..HEAD --oneline) \

--test-report build/test-report.json \

--output build/aso_metadata.json

```

An optional final stage auto-populates App Store Connect via the ASC API when an approval flag is set:

```bash

if [ -f build/APPROVED_METADATA ]; then

asc submit --app-id com.snngames.seafishshooter \

--version 1.0.2 \

--whats-new "$(cat build/release_notes.md | head -5)" \

--keywords "$(cat build/aso_metadata.json | jq -r '.suggested_keyword_string')"

fi

```

Results — Expected Impact

**Release Notes Reliability** — Every release ships with categorized, user-facing notes. For CCFish's v1.0.2, instead of "bug fixes and performance improvements," the generated notes would read:

> ## New Features

> - Added weapon unlock system with explosive harpoons

> - Introduced rare fish spawn events during full moon cycles

> ## Bug Fixes

> - Fixed crash on iPhone 12 when firing rapid shots

> - Resolved memory leak in treasure chest animation

> ## Performance

> - Improved frame rate during boss battles by 40%

**ASO Keyword Refresh** — The 2016-era keyword string is replaced with terms derived from actual shipped features. Players searching for "rare fish game" or "underwater shooter" find CCFish because those terms appear in the listing. Estimated keyword coverage improvement: 3x.

**Pipeline Speed** — The generation stages add ~8 seconds to the build pipeline, versus 45 minutes of manual copywriting per release. That's a 99.7% time reduction for release metadata creation.

**Developer-to-Marketing Handoff** — The JSON artifact (`build/app_store_metadata.json`) becomes a single source of truth. Handoff drops from a Slack thread to a CI artifact URL.

Key Takeaways

1. **Your pipeline speaks marketing** — every `feat:` commit and passing test is a data point for your App Store page. Pipe it outward instead of letting it die in the git log.

2. **Conventional commits are the enabler** — structured prefixes make release note generation a one-pass classification task. If your project doesn't use them, adopt them before any automation.

3. **ASO doesn't need separate research** — your feature names and test descriptions contain the same keywords you'd pay a consultant to find. Mine your own codebase first.

4. **Keep a human approval gate** — auto-generated notes are 90% accurate but can miss nuance. The automation drafts; a human signs off. The JSON artifact makes this one-click review possible.

5. **Build archive metadata is a marketing asset** — the `.xcarchive` version info, team ID, and bundle identifier should flow into your ASO pipeline. CCFish's `com.snngames.seafishshooter` (team `Z7VRB9SJLK`) is now tracked in every artifact.

6. **The 8-second cost is negligible** — automated generation replaces 45 minutes of manual work per release cycle and eliminates the risk of shipping without any release notes.

7. **Start with one tag interval** — wire up the generator for your next tag, review the output, tune the wordings, then make it the default for all future releases.