Interop
Markdown is the only source of truth (ADR-0001). Notion and Jira are transports (ADR-0008). They must not grow a second graph.
--dry-run defaults true on pb sync and pb export. Pass --dry-run=false to write.
How-to: Set up Notion · Sync with Notion.
Notion
Two-way sync. Identity is the Pilotbook ID property — not Notion's auto unique id, and not a UUID in frontmatter. Page ids live in .pb/notion-map.json and can be rebuilt by querying that property.
Bind existing databases (they may live under different pages). Pilotbook does not create databases and does not require a shared parent.
Config
interop:
notion:
token_env: NOTION_TOKEN
version: "2025-09-03"
push_on_write: false
databases:
epic: { id: "…", data_source_id: "…" }
story: { id: "…", data_source_id: "…" }| Key | Meaning |
|---|---|
token_env | Env var name for the integration token (default NOTION_TOKEN) |
parent_page_id | Ignored. Kept so old configs still parse |
version | Notion API version (default 2025-09-03) |
push_on_write | Opt-in; default false. No webhook daemon |
databases | Map of type → { id, data_source_id } after bind |
The token is never written to yaml.
Commands
pb sync --catalog # JSON list of searchable databases
pb sync --bind '{"epic":"<id-or-url>"}'
pb sync --init # refresh stored ids (does not create DBs)
pb sync --to notion # push markdown → Notion
pb sync --from notion # pull Notion → markdown
pb sync --to notion --from notion --dry-run=false--to upserts by Pilotbook ID. Body is push-only. Edges stay in markdown.
--from pulls bidirectional scalars: title, status, owner, priority, tags, estimate, phase. A Notion row with an empty Pilotbook ID is intake: createItem allocates an id (BR-001) and PATCHes it back.
If both sides changed since the last push hash, Pilotbook wins and the report lists conflict.
pb export --to notion is the same upsert as --to notion.
Preview first (--dry-run default), then apply with --dry-run=false.
Jira
pb export --to jira
pb export --to jira --dry-run=falseDry-run maps work items (epic/story/task) to issue payloads (summary, labels, externalId). Live POST to /rest/api/3/issue requires:
JIRA_BASE_URLJIRA_EMAILJIRA_API_TOKENJIRA_PROJECT
Issue type is Bug only if type === "bug"; Pilotbook has no builtin bug type, so exports are Task. This is a one-way dump, not a sync. Prefer Notion if you need a human board that round-trips scalars.
Peers and manifests
Cross-repo refs look like other#TASK-001 in depends_on. Remote refs never block local ready-state.
pb manifestWrites .pb/graph.json:
{
"name": "pilotbook",
"generated": "2026-08-25",
"items": [{ "id": "TASK-001", "type": "task", "title": "…", "status": "todo", "edges": {} }]
}Point at another repo's manifest:
peers:
- name: other
manifest: /path/to/other/.pb/graph.jsonLint resolves other#TASK-001 against that file. Missing peer ids are dangling-ref.