by.waclaw.online / pm-agent / 06

The Backend: Jira for Stories, GitHub for Docs-as-Code

Part 6 of 8 — the concrete wiring: where stories live, where docs live, how the agent reaches both through MCP, and the traceability round-trip that ties a Jira key to a doc path.

Two backends, one division of labor

The architecture (chapter 2) names two systems of record: Jira for user stories and GitHub for documentation-as-code. That split is not an accident of tooling — it reflects what each system is genuinely good at, and it is the line along which the two subagents divide. The Analyst lives on the Jira side; the Librarian lives on the GitHub side; the catalog they share physically lives in the GitHub repo. This chapter is the plumbing.

flowchart LR subgraph AGENT["The PM Agent"] direction TB AN["Analyst<br/>owns stories"] LIB["Librarian<br/>owns docs"] end AN <-->|"Jira MCP server"| JIRA[("Jira<br/>PROMO project<br/>stories · acceptance · status")] LIB <-->|"GitHub MCP server"| GH[("GitHub<br/>promodesk-docs<br/>Markdown · Mermaid · schema")] JIRA -. "story PROMO-512<br/>links to doc paths" .-> GH GH -. "PR & commits<br/>carry PROMO-512" .-> JIRA

Each subagent reaches its system of record through a dedicated MCP server. The dotted links are the traceability round-trip: the story points at the docs it touches, and the docs commits carry the story key back.

Why Jira is the system of record for stories

The agent does not introduce a new place to plan work. The organization already runs its delivery on Jira — sprints, boards, backlog, status transitions, the whole apparatus that the engineering team checks every morning. Inventing a parallel store for "the agent's stories" would create exactly the silo this system is supposed to dissolve. So the rule is simple: the agent meets the team where it is. The Analyst writes into the same PromoDesk project (`PROMO`) the humans already use.

What lives in Jira is the work: the user story, its acceptance criteria, its place in the backlog, its status as it moves from refinement to done. What does not live in Jira is the system knowledge. A Jira story is a transient unit — it is created, worked, closed, and then it stops being read. The description of how PromoDesk's fund-balance calculation actually works must outlive any single story. That description belongs in the catalog, in GitHub. Jira tells you what changed; the catalog tells you what is true.

Lives in Jira (transient work)Lives in GitHub (durable knowledge)
User stories & epicsThe 11(+2) entity catalog (chapter 3)
Acceptance criteria for a given storyThe canonical rules, schema, screens, processes
Backlog ordering & sprint assignmentThe conceptual & physical data models
Status / workflow transitionsDecision log / ADRs — why choices were made
Story-level estimates & assigneesThe agent's own config (skills, subagents, CLAUDE.md)

Why GitHub + docs-as-code for the catalog

The catalog is the asset that must not rot, and the discipline that keeps a codebase from rotting is exactly the discipline we borrow. Treating documentation as code means it gets version control (every change has an author, a timestamp, and a diff), pull requests as the review mechanism (no edit lands without a second pair of eyes), diffs as the audit trail (you can answer "when did the $50k threshold become $50k, and who approved it?"), and CI as the quality gate (a machine refuses to merge a doc that violates the rules).

Concretely, the catalog is plain Markdown with embedded Mermaid diagrams and committed schema files — nothing proprietary, nothing that needs a server to render. And one more thing lives in the same repo: the agent's own configuration — its skill definitions, subagent definitions, and CLAUDE.md. The agent that maintains the catalog is itself versioned alongside the catalog. Chapter 7 details those files; here we just reserve their place in the tree.

The docs repo, laid out against the 11 entities

The repo layout is not arbitrary: /docs/ maps one-to-one onto the canonical entity catalog from chapter 3. Find any entity by knowing its number; find its docs by knowing its folder.

promodesk-docs/
├── docs/
│   ├── glossary.md                  # 1  Ubiquitous language
│   ├── data/
│   │   ├── conceptual-model.md       # 2  Business entities + Mermaid ER
│   │   ├── schema.md                 # 3  Logical/physical model (prose)
│   │   └── schema.sql                # 3  The schema of record
│   ├── rules/                        # 4  Accrual rates, claim-match tolerances
│   │   ├── accrual-posting.md
│   │   └── claim-matching.md
│   ├── ui/                           # 5  Screen inventory
│   │   ├── promotion-planner.md
│   │   ├── fund-dashboard.md
│   │   └── claims-workbench.md
│   ├── process/                      # 6  Workflows + state machines
│   │   └── promotion-lifecycle.md
│   ├── batch/                        # 7  Scheduled jobs
│   │   ├── accrual-posting-job.md
│   │   └── claims-matcher-job.md
│   ├── integrations/                 # 8  ERP GL export, retailer EDI claims
│   │   ├── erp-gl-export.md
│   │   └── retailer-edi-claims.md
│   ├── security/
│   │   └── authz.md                  # 9  Roles + approval thresholds
│   ├── nfr.md                        # 10 SOX, audit, retention, SLAs
│   ├── decisions/                    # 11 ADRs — institutional memory
│   │   └── 0007-vp-threshold-250k.md
│   ├── acceptance/                   # +1 Test & acceptance catalog
│   │   └── PROMO-512.md
│   └── open-questions.md             # +2 Ambiguity backlog
├── .claude/                          # agent config — chapter 7
│   ├── CLAUDE.md
│   ├── skills/                       # grill-me, draft-story, reconcile-docs ...
│   └── agents/                       # analyst.md, librarian.md
├── .mcp.json                         # MCP server wiring — chapter 7
└── .github/
    └── workflows/
        └── docs-ci.yml               # the quality gate (below)

One folder per entity, the optional +1/+2 alongside, and the agent's own brain in .claude/. The catalog and the agent that tends it ship in the same repo.

MCP: how the agent reaches each backend

MCP — the Model Context Protocol — is a standard way to give the agent tools it can call to act on an external system, so the agent does not hand-roll API clients for every backend. In this design the Analyst is given a Jira/Atlassian MCP server (create and update stories, read the backlog, set status); the Librarian is given the GitHub MCP server (read files, open branches, raise pull requests, comment on reviews). Each subagent is granted only the server it needs — the Analyst cannot push to the docs repo, the Librarian cannot move a story to Done — which keeps each one's blast radius small.

On MCP server names and the recency caveat. MCP is young and moving fast. The exact server packages, names, and endpoints for the Atlassian/Jira and GitHub MCP servers drift between releases — what is canonical this quarter may be renamed or superseded next quarter. Treat the wiring here as the shape of the integration, not a copy-paste configuration. When you build, pull the current server from the official source and confirm its tool names; the architecture is stable even as the package names are not. Chapter 7 shows where this lands in .mcp.json.

The round-trip: story ↔ docs traceability

Knowledge is only trustworthy if you can navigate between intent and reality in both directions. The wiring makes that a two-way street. The Jira story carries links to the doc paths it touches, so from a story you can jump straight to the authoritative entity. And the docs pull request and every commit carry the Jira key (`PROMO-512`), so from a doc change you can jump straight back to the work that motivated it. Story→docs and docs→story, always.

Here is a sample story the Analyst writes — note the entity links in its body:

PROMO-512  ·  Story  ·  Status: Refinement
Title: Show committed-vs-actual fund balance on the Fund Dashboard

As a key-account manager
I want the Fund Dashboard to show committed accruals separately from posted actuals
So that I can see remaining spendable budget before a promotion locks it up.

Acceptance criteria (INVEST-checked):
  • Dashboard shows three figures per fund: committed, posted, remaining.
  • "Committed" = sum of accruals on promotions in Planned or Approved state.
  • Remaining = fund cap − committed − posted; never negative (clamp + warn).
  • Figures refresh after the nightly accrual-posting job completes.

Impacted catalog entities:
  • docs/ui/fund-dashboard.md          (screen change)
  • docs/rules/accrual-posting.md      (committed vs posted definition)
  • docs/batch/accrual-posting-job.md  (refresh dependency)

When that story ships, the Librarian opens the matching docs pull request — and its title and body carry the key back:

PR title:  PROMO-512  reconcile fund-dashboard + accrual rules for committed balance

Body:
  Reconciles the catalog after PROMO-512 shipped.

  - docs/ui/fund-dashboard.md: add committed/posted/remaining fields,
    bump last_verified to 2026-06-26.
  - docs/rules/accrual-posting.md: define "committed" (Planned|Approved
    accruals) vs "posted" (after nightly job).
  - docs/batch/accrual-posting-job.md: note dashboard refresh dependency.

  Closes-knowledge-gap-for: PROMO-512
The key is the join. The Jira key PROMO-512 appears in the story's links, in the docs commit messages, in the PR title, and in each touched entity's related_stories front-matter. That single shared token is what makes trace (chapter 5) able to answer "where is committed-balance documented, and which story put it there?" in one hop. No key, no round-trip.

CI: the quality gate that stops the catalog rotting

Docs-as-code only beats doc-rot if a machine enforces the rules. The docs repo runs CI on every pull request, and the checks exist specifically to catch the failure modes that let documentation drift out of truth.

CI checkWhat it enforcesWhat it prevents
Front-matter linterRequired fields present; status in {draft, reviewed, authoritative}; last_verified is a real dateUntracked, ownerless, or undated entities sneaking in
Link checkerEvery cross-link between entities resolvesBroken references after a file is renamed or moved
Freshness gateWarn when last_verified is older than the thresholdSilent staleness — docs that look authoritative but aren't
Schema validationschema.sql parses and matches the migrationsThe schema-of-record drifting from the real database

A trimmed GitHub Actions workflow showing two of these gates:

name: docs-ci
on:
  pull_request:
    paths: ["docs/**"]

jobs:
  catalog-checks:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Front-matter lint
        run: |
          # required fields + valid status enum + last_verified is a date
          python scripts/lint_frontmatter.py docs/ \
            --require owner,status,last_verified,source_of_truth \
            --enum status=draft,reviewed,authoritative \
            --date last_verified

      - name: Cross-link check
        run: python scripts/check_links.py docs/   # fails on any broken entity link

      - name: Freshness audit (warn-only)
        continue-on-error: true
        run: python scripts/freshness.py docs/ --max-age-days 180

      - name: Schema parses & matches migrations
        run: |
          sqlfluff parse docs/data/schema.sql
          python scripts/schema_matches_migrations.py \
            docs/data/schema.sql db/migrations/
The freshness gate warns; it does not block. A stale last_verified is a signal for the Librarian's freshness-audit (chapter 4), not a reason to reject an unrelated PR. Hard-failing on staleness would punish whoever touches a file near a stale neighbor. Make freshness loud and visible, but keep the merge-blockers to objective facts: required fields, valid enums, resolvable links, a schema that parses.

Branch protection: humans stay in control

Nothing in the catalog auto-merges. The docs repo's default branch is protected: a change lands only through a pull request that passes the blocking CI checks and carries at least one human review approval. The Librarian's job ends at opening a well-formed PR — with the right entity edits, bumped freshness dates, and the Jira key in the title. A person presses merge.

This is the deliberate seam where human judgment sits. The agent can draft, reconcile, and propose at machine speed; it cannot quietly rewrite the system of record. Every fact in the catalog has, somewhere in its history, a human who approved the PR that put it there — which is exactly what makes the catalog something the Analyst, and the team, can believe.

What we have, and what's next

The backend is now concrete: Jira holds the transient work and GitHub holds the durable knowledge; the Analyst reaches Jira and the Librarian reaches GitHub, each through its own MCP server; the Jira key threads through stories, commits, PRs, and front-matter to make the round-trip navigable both ways; CI stops the catalog from rotting; and branch protection keeps a human on the merge button. Chapter 7 turns this wiring into the actual files — the subagent definitions, the skills, the soul.md judgment file, the CLAUDE.md, and the .mcp.json that names the servers we kept deliberately light here.