# Research: PR Fetching Behavior — `gfa report` vs `gfa collect`

**Date:** 2026-02-27
**Config investigated:** `~/Duetto/repos/gitflow-all-engineers.yaml`

---

## Summary

PR fetching does NOT happen during `gfa report`. It happens exclusively during `gfa collect` (Stage 1). The current config is also missing the `github_repo` field on every repository entry, which is the second independent reason PRs are never fetched.

There are two separate issues that must both be fixed.

---

## Root Cause 1: `gfa report` Does Not Fetch PRs

### The Three-Stage Pipeline

The `gfa` CLI is split into three independent stages:

| Stage | Command | Purpose |
|-------|---------|---------|
| 1 | `gfa collect` | Fetch commits from git repos + enrich with PRs from GitHub |
| 2 | `gfa classify` | LLM-classify cached commits |
| 3 | `gfa report` | Read classified commits from cache, write reports |

**`gfa report` calls `run_report()` in `pipeline_report.py`, which:**
- Reads commits from the SQLite cache database
- Resolves identities
- Writes CSV/markdown/JSON reports
- Sets `all_prs = []` (hardcoded empty list — line 167)
- Never calls `IntegrationOrchestrator` or `GitHubIntegration`
- Never touches the GitHub API

**`gfa collect` calls `run_collect()` in `pipeline_collect.py`, which:**
- Fetches commits from each local git repository
- Checks `if repo_config.github_repo:` (lines 158–190)
- If that field is set, calls `orchestrator.enrich_repository_data()` which calls `github_integration.enrich_repository_with_prs()`
- Caches PR data into the SQLite database for use by later stages

### What `gfa report` Does With PRs

```python
# pipeline_report.py, line 167
all_prs: list[Any] = []
```

The report stage hardcodes an empty PR list. It reads commits from cache but makes no attempt to load cached PRs from the database. Reports that accept a `prs` argument (like `generate_weekly_velocity_report`, `generate_weekly_dora_report`, `generate_developer_activity_summary`, `generate_summary_report`) all receive this empty list.

### Conclusion on Stage Issue

To get PR data into reports, you must run `gfa collect` first (with correct config), not just `gfa report`.

---

## Root Cause 2: Missing `github_repo` Field in Config

### The Guard in `pipeline_collect.py`

```python
# pipeline_collect.py, lines 158–190
if repo_config.github_repo:
    try:
        # ... fetch commits from cache ...
        enrichment = orchestrator.enrich_repository_data(
            repo_config, commits_for_enrichment, start_date
        )
```

PR enrichment is gated on `repo_config.github_repo` being truthy. If it is `None` (the default), the entire GitHub PR fetch block is skipped silently.

### The Guard in `orchestrator.py`

```python
# orchestrator.py, line 184
if "github" in self.integrations and repo_config.github_repo:
```

`enrich_repository_data()` has the same check — `github` integration must be initialized AND `repo_config.github_repo` must be set.

### How `github_repo` Gets Populated

In `config/repository.py`, `process_repository_config()`:

```python
# Lines 119–124
github_repo = repo_data.get("github_repo")
if github_repo and "/" not in github_repo:
    if self.github_config.organization:
        github_repo = f"{self.github_config.organization}/{github_repo}"
    elif self.github_config.owner:
        github_repo = f"{self.github_config.owner}/{github_repo}"
```

If `github_repo` is absent from the YAML entry, it stays `None`. There is no auto-derivation from the local git remote URL.

### Current Config State

Every repository entry in `gitflow-all-engineers.yaml` looks like:

```yaml
- name: "admin-panel"
  path: "/Users/masa/Duetto/repos/admin-panel"
  branch: "main"
```

There is no `github_repo` field. Since neither `github.organization` nor `github.owner` is set, even short-name fallback would fail. Result: `github_repo = None` for all 146 repos, so PR enrichment is skipped for every repository.

---

## What the Config Needs

### Option A: Set `github.organization` + no per-repo `github_repo` needed (cleanest)

```yaml
github:
  token: "${GITHUB_TOKEN}"
  organization: "duettoresearch"
```

With `organization` set, the loader will:
1. Initialize `GitHubIntegration` (because token exists)
2. For each repo with `github_repo: "admin-panel"` (short name), auto-prefix to `"duettoresearch/admin-panel"`

But you still need `github_repo` on each repository entry, even if it's just the short name — organization only handles the prefix expansion. Without `github_repo` entirely the guard is `None` and enrichment is skipped.

### Option B: Set `github.owner` + per-repo `github_repo` short names

```yaml
github:
  token: "${GITHUB_TOKEN}"
  owner: "duettoresearch"
```

Same behavior as Option A — owner is used for short-name expansion.

### Option C: Fully-qualified `github_repo` per entry (most explicit)

```yaml
repositories:
  - name: "admin-panel"
    path: "/Users/masa/Duetto/repos/admin-panel"
    branch: "main"
    github_repo: "duettoresearch/admin-panel"
```

This works without setting `github.organization` or `github.owner`.

---

## Exact Config Changes for `gitflow-all-engineers.yaml`

### Minimal change (recommended)

Add `organization` to the `github:` block AND add `github_repo` (short name) to each repository entry.

**Step 1: Update the github section:**

```yaml
github:
  token: "${GITHUB_TOKEN}"
  organization: "duettoresearch"
```

**Step 2: Add `github_repo` to each repository entry. Example for first few:**

```yaml
repositories:
  - name: "admin-panel"
    path: "/Users/masa/Duetto/repos/admin-panel"
    branch: "main"
    github_repo: "admin-panel"          # <-- add this
  - name: "advance-chatbot"
    path: "/Users/masa/Duetto/repos/advance-chatbot"
    branch: "main"
    github_repo: "advance-chatbot"      # <-- add this
```

The `organization: "duettoresearch"` setting causes the loader to expand `"admin-panel"` to `"duettoresearch/admin-panel"` automatically.

### Alternative: Use `owner` instead of `organization`

```yaml
github:
  token: "${GITHUB_TOKEN}"
  owner: "duettoresearch"
```

`owner` and `organization` behave identically for the purpose of short-name prefix expansion (see `repository.py` lines 121–124). Use `organization` if the GitHub account is an organization account (recommended for "duettoresearch").

### Bob Duetto's Account

The `bob-duetto` GitHub username appears in `developer_aliases` as a git username, not a GitHub API account. The GitHub API token used for PR fetching is set via `${GITHUB_TOKEN}` — this should be an access token scoped to the **duettoresearch** organization (i.e. a PAT or GitHub App token that has `repo` read access to `duettoresearch/*` repos). The token owner's username does not need to match the organization name.

If `GITHUB_TOKEN` is currently set to a PAT for `bob-duetto`, verify that `bob-duetto` has read access to the `duettoresearch` organization's repositories. If it's an org-scoped token for `duettoresearch` directly, it will work as-is.

---

## What About Cached PRs in Reports?

Even after fixing collect to fetch PRs, `pipeline_report.py` hardcodes `all_prs = []`. The PR data is cached in the SQLite database (`PullRequestCache` table) after `gfa collect` runs, but `run_report()` never queries it.

This means reports like DORA metrics, velocity, and summary that accept `all_prs` will still receive an empty list even after PRs are cached.

**This is a code gap, not a config gap.** To surface PR data in reports, `pipeline_report.py` would need to be updated to load cached PRs from the database before generating reports, similar to how `pipeline_collect.py` loads cached commits.

---

## Summary of Issues and Fixes

| Issue | Type | Fix |
|-------|------|-----|
| `gfa report` never fetches or loads PRs | Architecture (by design — report is read-only) | Use `gfa collect` to fetch PRs, then `gfa report` |
| `pipeline_report.py` hardcodes `all_prs = []` | Code gap | Needs code change to load cached PRs from DB |
| No `github_repo` on any repository entry | Config gap | Add `github_repo: "repo-name"` to each entry |
| No `github.organization` or `github.owner` | Config gap | Add `organization: "duettoresearch"` to github block |

### Immediate Action (Config Only)

```yaml
github:
  token: "${GITHUB_TOKEN}"
  organization: "duettoresearch"   # <-- add this

repositories:
  - name: "admin-panel"
    path: "/Users/masa/Duetto/repos/admin-panel"
    branch: "main"
    github_repo: "admin-panel"     # <-- add to all 146 repos
```

Then run:

```bash
gfa collect -c ~/Duetto/repos/gitflow-all-engineers.yaml --weeks 4
gfa classify -c ~/Duetto/repos/gitflow-all-engineers.yaml
gfa report -c ~/Duetto/repos/gitflow-all-engineers.yaml --generate-csv
```

PR data will be fetched and cached during `collect`. Reports will still show `all_prs = []` until the code gap in `pipeline_report.py` is addressed.

---

## Key Files

- `/Users/masa/Projects/gitflow-analytics/src/gitflow_analytics/pipeline_collect.py` — PR enrichment during collect (lines 158–190)
- `/Users/masa/Projects/gitflow-analytics/src/gitflow_analytics/pipeline_report.py` — Report generation; `all_prs = []` hardcoded (line 167)
- `/Users/masa/Projects/gitflow-analytics/src/gitflow_analytics/integrations/orchestrator.py` — `enrich_repository_data()` gated on `repo_config.github_repo` (line 184)
- `/Users/masa/Projects/gitflow-analytics/src/gitflow_analytics/config/repository.py` — `github_repo` population logic (lines 119–124)
- `/Users/masa/Projects/gitflow-analytics/src/gitflow_analytics/config/schema.py` — `RepositoryConfig.github_repo: Optional[str]`, `GitHubConfig.owner`, `GitHubConfig.organization`
- `/Users/masa/Duetto/repos/gitflow-all-engineers.yaml` — Current config (missing `organization` + all `github_repo` fields)
