Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Career Archive

Builds a defensible, evidence-backed record of one person's work from Jira, Confluence and GitHub — for performance reviews, promotion cases and CVs.

The design goal is that every sentence in the output can be traced to an artifact, and every number to a computed metric. A career document is only worth having if it survives a reader who was in the room.

Layout

raw/          untouched API payloads, append-only NDJSON
normalized/   one Artifact schema across all sources, with ownership tiers
enriched/     link graph, initiatives, metrics   (deterministic, no LLM)
summaries/    per-artifact LLM summaries, cached on content hash
narrative/    story.md + verification_report.json

Each layer is a pure function of the one below it. Change a detection rule and re-run build; nothing is refetched and no summary is regenerated.

careerkit/ is the only tool — everything runs through it.

Customize before running

Two things are specific to you and need setting up before the tool is useful:

  1. Identity — set via environment variables (see .env.example): SUBJECT_NAME, SUBJECT_EMAIL, SUBJECT_ATLASSIAN_ACCOUNT_ID, SUBJECT_GITHUB_LOGIN, SUBJECT_START_DATE, SUBJECT_TIMEZONE, SUBJECT_WORKDAY_WINDOWS, GITHUB_ORG, COMPANY_NAME.
  2. Internal system namescareerkit/taxonomy.py's _SYSTEM_PATTERNS, _SYSTEM_PATH_PATTERNS, and SYSTEM_BY_REPO ship with commented-out example entries. This is the one part of the tool that can't be generic: only you know what your team calls the things you built. Fill these in with your own systems, or leave them empty — everything else works fine without this, it just means system-level grouping in the narrative won't fire.

Usage

python -m careerkit.cli status
python -m careerkit.cli build
python -m careerkit.cli story

Individual stages:

python -m careerkit.connectors.github --what prs reviews commits
python -m careerkit.connectors.confluence
python -m careerkit.normalize && python -m careerkit.enrich
python -m careerkit.summarize --workers 5
python -m careerkit.narrate && python -m careerkit.verify

Inspect the computed facts without calling an LLM:

python -m careerkit.narrate --facts-only

Ownership tiers

Attribution is a tier derived from evidence, not a confidence score. The narrative layer may only use the verbs a tier licenses, and verify fails the build if it doesn't.

Tier Evidence Permitted language
OWNED Jira assignee, merged PR author, Confluence page creator, commit author built, designed, shipped, owned, implemented, migrated
DROVE ticket reporter, submitted PR review, authored-but-unmerged PR drove, reviewed, specified, scoped, identified
CONTRIBUTED substantive comment (≥80 chars, not an acknowledgement), published page edit contributed to, helped, debugged alongside, advised on
ADJACENT watcher, or name appears in someone else's artifact excluded from all claims

ADJACENT exists because it is the trap. In one archive this ran against, roughly half the Confluence pages the subject's name showed up on were pages someone else had written — team retrospectives, re-org notes — where the subject was merely mentioned. A prompt that says "every item provided is your work" turns that into a false authorship claim; ownership tiers catch it instead of trusting the prompt's assertion.

Verification

careerkit.verify checks the generated narrative and exits non-zero on error:

  • citation-not-found — a cited PR or ticket id that doesn't exist
  • unverified-number — a quantity absent from both metrics.json and every artifact's text. A + suffix ("400+") is always an error, since it asserts a magnitude.
  • number-quoted-from-source (info) — not a computed metric but present in artifact text, e.g. an SLO of 99.5%. Sourced, so worth a glance, not a defect.
  • ownership-inflation — an authorship verb in a sentence citing a non-OWNED artifact
  • adjacent-artifact-mentioned — an ADJACENT title appearing in the story

Run it against any markdown file:

python -m careerkit.verify --story path/to/draft.md

Configuration

Identity, repos and working hours are all environment-variable driven (see "Customize before running" above and .env.example for the full list). Secrets stay in .env (gitignored):

JIRA_BASE_URL=https://<org>.atlassian.net
CONFLUENCE_BASE_URL=https://<org>.atlassian.net/wiki
JIRA_EMAIL=you@example.com
JIRA_API_TOKEN=...

GitHub uses the gh CLI. Locally, gh auth login once and nothing else is needed. Inside the container there's no keyring to authenticate against, so set GITHUB_TOKEN in the secrets file instead — gh reads it natively with no login step.

SUBJECT_TIMEZONE matters. Jira renders timestamps in the requesting account's display timezone, not the subject's. Reading the hour field directly without converting can put the whole activity histogram in the wrong part of the day — in one run it moved the apparent peak by several hours and roughly tripled the reported "night work" percentage versus the subject's actual local time. Set this correctly or the out-of-hours metric will be wrong, possibly in a way that's flattering or unflattering by pure accident of timezone math.

Bump prompt_version in the config to invalidate every cached LLM summary.

Running in Docker

The image bakes in no secrets and no data — both come in via mounts.

docker build -t career-archive .
docker run --rm -it \
  -v ~/career-archive-secrets/dotenv:/secrets/.env:ro \
  -v ~/career-archive-data:/data \
  career-archive status
docker run --rm -it \
  -v ~/career-archive-secrets/dotenv:/secrets/.env:ro \
  -v ~/career-archive-data:/data \
  career-archive all

~/career-archive-secrets/dotenv is a normal .env-format file — name it whatever you like on the host, since the mount target /secrets/.env is what config.py actually reads. ~/career-archive-data becomes the container's raw/normalized/enriched/summaries/narrative/output root (via CAREERKIT_DATA_ROOT), so results persist across runs and are visible on the host immediately.

Any cli.py stage works as the trailing argument: status, fetch, build, story, all.

LLM provider inside the container. summarize/narrate default to --provider claude-cli, which shells out to the claude CLI — but claude's own login is a separate credential (OS keychain on macOS, not a plain file), so it isn't available inside a fresh container. Two options:

  • Pass --provider anthropic (via cli.py story --provider anthropic) and set ANTHROPIC_API_KEY in the secrets file — works headless with no extra setup.
  • Or run summarize/narrate bare-metal (outside Docker) where claude is already logged in, and use the container only for fetch/build.

Privacy

The archive contains colleagues' names and account ids alongside internal project and client detail. output/, raw/, normalized/, enriched/, summaries/ and narrative/ are all gitignored.

Note that this directory sits inside a company repository. Committing the scripts is fine; committing the data is not, and the narrative is personal material. Decide deliberately before the first commit.

About

Personal career evidence archive and narrative generator — built from Jira, Confluence, and GitHub history

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages