Getting Started: Map for Leadership

Map is the data product at the centre of every other tool. It has two layers that you set up in order:

  1. Framework layer — YAML files defining your skills, behaviours, levels, disciplines, and tracks. Validated locally with npx fit-map validate. This is what Pathway, Basecamp, and libskill consume.
  2. Activity layer — A Supabase database that stores your organization roster, GitHub activity, evidence, and GetDX snapshots. Powers Landmark and Summit, and lets Guide write skill evidence back against framework markers.

The framework layer is required. The activity layer is optional but unlocks everything Landmark and Summit do.

Prerequisites

  • Node.js 18+
  • npm

Install

npm install @forwardimpact/map

Framework: initialize starter data

Bootstrap a complete framework skeleton with editable YAML files:

npx fit-map init

This creates ./data/pathway/ with starter definitions for levels, disciplines, capabilities, skills, behaviours, stages, drivers, and tracks. The starter data is a working framework you can customize to match your organization.

Framework: validate

Run the validator to check your YAML files against the schema:

npx fit-map validate

Fix any errors the validator reports before moving on.

Framework: customize

The starter data gives you a complete foundation. Edit the YAML files under data/pathway/ to match your organization's engineering expectations.

Levels

Edit data/pathway/levels.yaml to define your level structure. Each level sets baseline expectations for skill proficiency and behaviour maturity.

- id: J040
  professionalTitle: Level I
  managementTitle: Associate
  ordinalRank: 1
  baseSkillProficiencies:
    primary: foundational
    secondary: awareness
    broad: awareness
  baseBehaviourMaturity: emerging

- id: J060
  professionalTitle: Level II
  managementTitle: Senior Associate
  ordinalRank: 2
  baseSkillProficiencies:
    primary: working
    secondary: foundational
    broad: awareness
  baseBehaviourMaturity: developing

Capabilities and skills

Edit files under data/pathway/capabilities/ to define capability groups containing skills. Each skill needs a human: section with proficiency descriptions at all five levels.

name: Delivery
description: Ship working software reliably.
skills:
  - id: task_execution
    name: Task Execution
    human:
      description: Breaking down and completing engineering work
      proficiencyDescriptions:
        awareness: >
          Understands the team's delivery workflow and follows guidance
          to complete assigned tasks.
        foundational: >
          Breaks work into steps, estimates effort, and completes tasks
          with minimal guidance.
        working: >
          Independently plans and delivers work, adjusting approach when
          requirements change.
        practitioner: >
          Leads delivery across multiple workstreams, mentoring others
          on effective execution.
        expert: >
          Defines delivery practices that scale across the organization.

Disciplines

Edit files under data/pathway/disciplines/ to define role types that reference your capability skills.

specialization: Software Engineering
roleTitle: Software Engineer
coreSkills:
  - task_execution
validTracks:
  - null

Use null in validTracks to allow a trackless (generalist) configuration.

After each change, re-validate with npx fit-map validate.


Activity: install the Supabase CLI

The activity layer runs on Supabase. You need the Supabase CLI to start a local instance and to deploy migrations and edge functions to a hosted project. fit-map wraps the CLI for every activity workflow and will find it whether you install it via Homebrew or as an npm package.

# macOS via Homebrew (recommended if you have brew)
brew install supabase/tap/supabase

# Anywhere, as a project dependency
npm install supabase

# Linux / Windows — see https://supabase.com/docs/guides/local-development

fit-map prefers a supabase binary on your PATH and falls back to npx supabase (resolving from your project's node_modules) if one is not found, so the npm-local install works without any PATH setup.

Verify the install:

supabase --version
# or, for a project-local install:
npx supabase --version

Activity: start the database

Map ships its full Supabase project — config.toml, migrations, edge functions, and kong.yml — inside the npm package. fit-map activity start runs supabase start against the bundled project so you don't need to cd anywhere:

npx fit-map activity start

The CLI prints the local URL and the service-role key when it finishes booting. Copy the export commands it prints — every ingestion command needs them.

export MAP_SUPABASE_URL=http://127.0.0.1:54321
export MAP_SUPABASE_SERVICE_ROLE_KEY=<service-role key from fit-map activity start>

To stop the local instance:

npx fit-map activity stop

To check whether the local stack is running and the activity schema is reachable:

npx fit-map activity status

For a hosted deployment, link the project once, push the migrations, and deploy all four edge functions:

supabase link --project-ref <your-project-ref>
supabase db push
supabase functions deploy github-webhook getdx-sync people-upload transform

Activity: apply migrations

fit-map activity start applies the bundled migrations automatically the first time it runs. Three migrations create everything Map needs:

Migration Creates
20250101000000_activity_schema.sql activity schema with organization_people, GitHub, GetDX, evidence
20250101000001_get_team_function.sql activity.get_team(email) recursive CTE for manager-rooted team walks
20250101000002_raw_bucket.sql raw storage bucket for the ELT extract phase

To re-apply migrations against a clean local database (this drops your data):

npx fit-map activity migrate

Activity: push people

The unified person model lives in activity.organization_people. Email is the join key across HR, GitHub commits, and GetDX. Each row also carries a Pathway job profile (discipline, level, track), so any consumer can derive the full skill matrix for that person.

Create a people.yaml file with your roster:

- email: ada@example.com
  name: Ada Lovelace
  github_username: adalovelace
  discipline: software_engineering
  level: J040
  track: platform
  manager_email: charles@example.com

- email: charles@example.com
  name: Charles Babbage
  github_username: cbabbage
  discipline: software_engineering
  level: J060
  manager_email: null

CSV is also supported. Use the same column names as the YAML keys.

Step 1: validate locally

fit-map people validate checks the file against your framework — every discipline, level, and track must exist in data/pathway/. It does not talk to Supabase. Treat this as a fast pre-flight check before pushing to the database.

npx fit-map people validate ./people.yaml

The CLI reports validation errors row by row. Fix them in people.yaml and re-run until you see a clean result.

Step 2: push to Supabase

Once validation passes, push the roster into the activity database:

npx fit-map people push ./people.yaml

fit-map people push stores the file in the raw bucket for audit, then upserts it into activity.organization_people. People without a manager are inserted before people with one, so the manager_email foreign key always resolves. Re-run the command any time your roster changes — it upserts on email, so it's safe to run repeatedly.

Behind the scenes, fit-map people push talks to the same extract and transform helpers that the people-upload edge function uses. If you prefer to run the upload server-side — for example from a form or an admin workflow — POST the file to the hosted function instead:

curl -X POST \
  -H "Authorization: Bearer $MAP_SUPABASE_SERVICE_ROLE_KEY" \
  -H "Content-Type: application/x-yaml" \
  --data-binary @./people.yaml \
  https://<project-ref>.supabase.co/functions/v1/people-upload

Activity: ingest GitHub activity

Map ships a github-webhook edge function that receives GitHub webhook events, stores the raw payload in the raw bucket, and extracts normalized artifacts into activity.github_artifacts. Pull requests, reviews, and pushes are all handled out of the box.

With the local Supabase running, the function URL is:

http://127.0.0.1:54321/functions/v1/github-webhook

For a hosted deployment, the URL is:

https://<project-ref>.supabase.co/functions/v1/github-webhook

In your GitHub organization or repository settings, add a webhook pointing at that URL with these events selected:

  • Pull requests
  • Pull request reviews
  • Pushes

Set the content type to application/json. Each delivery is stored under raw/github/<delivery-id>.json and processed into activity.github_events and activity.github_artifacts. The function joins each artifact to a person via github_username, so make sure your people.yaml rows have GitHub usernames filled in for the engineers you want to track.

Activity: ingest GetDX snapshots

If your organization uses GetDX, Map can pull snapshot results into the same database so Landmark can correlate survey scores with marker evidence.

Get a GetDX API token from your GetDX admin. Then run the sync — either locally with the CLI, or on a schedule by POSTing to the getdx-sync edge function.

Ad-hoc or one-shot sync

GETDX_API_TOKEN=<your getdx api token> npx fit-map getdx sync

fit-map getdx sync fetches teams.list, snapshots.list, and snapshots.info for every undeleted snapshot, stores each response under raw/getdx/, and upserts:

  • activity.getdx_teams — the GetDX team hierarchy, bridged to your roster via manager_email
  • activity.getdx_snapshots — quarterly survey metadata
  • activity.getdx_snapshot_team_scores — factor and driver scores per team per snapshot, with vs_prev, vs_org, and percentile comparisons

The command prints the imported team, snapshot, and score counts when it finishes.

Scheduled sync

For continuous ingestion, set the GetDX token as a secret on your hosted Supabase project and schedule the getdx-sync edge function on any cron that can send an HTTP POST — GitHub Actions schedule: jobs, a Nomad periodic, or cron.d:

supabase secrets set GETDX_API_TOKEN=<your getdx api token>

curl -X POST \
  -H "Authorization: Bearer $MAP_SUPABASE_SERVICE_ROLE_KEY" \
  https://<project-ref>.supabase.co/functions/v1/getdx-sync

Once a quarter is typical — match your GetDX survey cadence. The edge function and the CLI run the same extract-and-transform code, so switching between them is purely a deployment choice.

The driver IDs in data/pathway/drivers.yaml are the same IDs as getdx_snapshot_team_scores.item_id — GetDX assigns those IDs, and you mirror them when authoring drivers.yaml. That shared namespace is what lets Landmark juxtapose a driver's GetDX score against the marker evidence for its contributing skills.

Activity: re-run transforms

To reprocess every raw document in storage from scratch — for example after restoring a database, after upgrading Map to pick up a transform fix, or to backfill from raw payloads — ask fit-map to re-run every transform against the raw bucket:

npx fit-map activity transform

The command reads people, GetDX, and GitHub raw documents in dependency order and upserts on natural keys, so it is safe to re-run. To reprocess a single source instead of all three:

npx fit-map activity transform people
npx fit-map activity transform getdx
npx fit-map activity transform github

The hosted equivalent is the transform edge function, which runs the same code server-side:

curl -X POST \
  -H "Authorization: Bearer $MAP_SUPABASE_SERVICE_ROLE_KEY" \
  https://<project-ref>.supabase.co/functions/v1/transform

Trying the activity layer with synthetic data

If you want to explore the activity layer before connecting real data sources, Map can populate the database with synthetic data — a realistic roster, GitHub events, and GetDX snapshots generated from a template.

First, generate synthetic data (requires the @forwardimpact/libterrain package):

npx fit-terrain data/synthetic/story.dsl

Then seed the activity database:

npx fit-map activity seed

This uploads the generated roster and raw documents, runs all transforms, and verifies the result. The database will contain realistic but fictional data you can query with Landmark or Summit. When you are ready to switch to real data, push your actual roster with npx fit-map people push — it overwrites the synthetic entries.

Activity: verify the data

Once people are pushed and at least one data source is available — either from real ingestion commands above or from activity seed — verify the database:

npx fit-map activity verify

fit-map activity verify reads activity.organization_people and at least one derived table (getdx_snapshots or github_events), prints the row counts it found, and exits 0 if both are populated. If either is empty it exits non-zero with a message pointing at the step that didn't run.

If verification passes, your activity layer is ready for Landmark, Summit, and Guide.


Next steps