This guide covers migrating from existing project management tools to LumenFlow. You can migrate incrementally or all at once.
Keep What Works
LumenFlow replaces execution tracking, not roadmaps. Keep Jira/Linear for planning if it works.
Start Fresh
No need to migrate history. Start with new work, archive old tickets.
Link Back
Reference external IDs in WU specs for traceability.
Gradual Adoption
Migrate one team or area first. Expand when comfortable.
| Jira/Linear/GitHub | LumenFlow | Notes |
|---|
| Ticket/Issue | Work Unit (WU) | Atomic unit of work |
| Epic | Initiative | Multi-WU scope |
| Sprint | Lane WIP | No time-boxing, flow-based |
| Status | WU Status | ready, in_progress, blocked, waiting, done |
| Assignee | assigned_to | One owner per WU |
| Labels | lane, type | Structured categorization |
| Story Points | Complexity estimate | Optional, tool-call based for agents |
| Acceptance Criteria | acceptance | YAML array, verifiable outcomes |
| Priority | priority | P0-P3 |
| Jira Status | Linear Status | GitHub Status | LumenFlow Status |
|---|
| To Do | Backlog | Open | ready |
| In Progress | In Progress | In Progress | in_progress |
| Blocked | Blocked | - | blocked |
| In Review | In Review | In Review | waiting |
| Done | Done | Closed | done |
| Jira | Linear | GitHub | LumenFlow |
|---|
| Highest/Blocker | Urgent | - | P0 |
| High | High | - | P1 |
| Medium | Medium | - | P2 |
| Low/Lowest | Low | - | P3 |
-
Export issues to JSON
Use Jira’s bulk export or REST API:
# Using jira-cli
jira issue list \
--project PROJ \
--status "To Do,In Progress" \
--format json > jira-export.json
-
Convert to WU YAML
// convert-jira.mjs
import { readFileSync, writeFileSync, mkdirSync } from 'fs';
const jiraExport = JSON.parse(readFileSync('jira-export.json', 'utf8'));
// Lane mapping
const laneMap = {
frontend: 'Experience: UI',
backend: 'Framework: Core',
devops: 'Operations: Infrastructure',
docs: 'Content: Documentation',
};
// Type mapping
const typeMap = {
Story: 'feature',
Bug: 'bug',
Task: 'chore',
Spike: 'discovery',
};
// Priority mapping
const priorityMap = {
Highest: 'P0',
High: 'P1',
Medium: 'P2',
Low: 'P3',
Lowest: 'P3',
};
mkdirSync('docs/04-operations/tasks/wu', { recursive: true });
jiraExport.issues.forEach((issue, index) => {
const wuId = `WU-${String(index + 1).padStart(3, '0')}`;
const component = issue.fields.components?.[0]?.name?.toLowerCase() || 'backend';
const wu = {
id: wuId,
title: issue.fields.summary,
lane: laneMap[component] || 'Framework: Core',
type: typeMap[issue.fields.issuetype.name] || 'feature',
status: 'ready',
priority: priorityMap[issue.fields.priority.name] || 'P2',
description: issue.fields.description || '',
acceptance: parseAcceptanceCriteria(issue.fields.description),
code_paths: [],
external_refs: [`JIRA-${issue.key}`],
};
writeFileSync(`docs/04-operations/tasks/wu/${wuId}.yaml`, toYAML(wu));
});
function parseAcceptanceCriteria(description) {
// Extract bullet points that look like acceptance criteria
if (!description) return ['Feature works as specified'];
const lines = description.split('\n');
const criteria = lines
.filter((l) => l.match(/^[\-\*]\s/))
.map((l) => l.replace(/^[\-\*]\s+/, '').trim())
.filter((l) => l.length > 0);
return criteria.length > 0 ? criteria : ['Feature works as specified'];
}
function toYAML(obj) {
// Simple YAML serialization (use js-yaml for production)
return Object.entries(obj)
.map(([k, v]) => {
if (Array.isArray(v)) {
return `${k}:\n${v.map((i) => ` - ${JSON.stringify(i)}`).join('\n')}`;
}
return `${k}: ${typeof v === 'string' && v.includes('\n') ? `|\n ${v.replace(/\n/g, '\n ')}` : JSON.stringify(v)}`;
})
.join('\n');
}
-
Run conversion
-
Use Linear API
# Install Linear CLI or use GraphQL API
linear issue list --team TEAM --status "Backlog,In Progress" --json > linear-export.json
-
Convert to WU YAML
// convert-linear.mjs
import { readFileSync, writeFileSync, mkdirSync } from 'fs';
const linearExport = JSON.parse(readFileSync('linear-export.json', 'utf8'));
const laneMap = {
Frontend: 'Experience: UI',
Backend: 'Framework: Core',
Platform: 'Operations: Infrastructure',
Design: 'Experience: Design',
};
const priorityMap = {
0: 'P0', // No priority
1: 'P0', // Urgent
2: 'P1', // High
3: 'P2', // Medium
4: 'P3', // Low
};
mkdirSync('docs/04-operations/tasks/wu', { recursive: true });
linearExport.forEach((issue, index) => {
const wuId = `WU-${String(index + 1).padStart(3, '0')}`;
const wu = {
id: wuId,
title: issue.title,
lane: laneMap[issue.team?.name] || 'Framework: Core',
type: issue.labels?.includes('bug') ? 'bug' : 'feature',
status: 'ready',
priority: priorityMap[issue.priority] || 'P2',
description: issue.description || '',
acceptance: extractAcceptance(issue.description),
code_paths: [],
external_refs: [`LINEAR-${issue.identifier}`],
};
writeFileSync(`docs/04-operations/tasks/wu/${wuId}.yaml`, toYAML(wu));
});
function extractAcceptance(description) {
if (!description) return ['Feature works as specified'];
// Linear often has checkboxes for acceptance
const checkboxes = description.match(/\[ \].+/g) || [];
const criteria = checkboxes.map((c) => c.replace(/^\[ \]\s*/, '').trim());
return criteria.length > 0 ? criteria : ['Feature works as specified'];
}
function toYAML(obj) {
// Use js-yaml in production
return JSON.stringify(obj, null, 2).replace(/"/g, '').replace(/,$/gm, '');
}
-
Use GitHub CLI
gh issue list \
--repo owner/repo \
--state open \
--json number,title,body,labels,assignees,milestone \
> github-export.json
-
Convert to WU YAML
// convert-github.mjs
import { readFileSync, writeFileSync, mkdirSync } from 'fs';
const issues = JSON.parse(readFileSync('github-export.json', 'utf8'));
const laneFromLabel = (labels) => {
const labelNames = labels.map((l) => l.name.toLowerCase());
if (labelNames.some((l) => l.includes('frontend') || l.includes('ui'))) {
return 'Experience: UI';
}
if (labelNames.some((l) => l.includes('backend') || l.includes('api'))) {
return 'Framework: Core';
}
if (labelNames.some((l) => l.includes('infra') || l.includes('devops'))) {
return 'Operations: Infrastructure';
}
if (labelNames.some((l) => l.includes('docs'))) {
return 'Content: Documentation';
}
return 'Framework: Core';
};
const typeFromLabel = (labels) => {
const labelNames = labels.map((l) => l.name.toLowerCase());
if (labelNames.includes('bug')) return 'bug';
if (labelNames.includes('enhancement')) return 'feature';
if (labelNames.includes('documentation')) return 'documentation';
return 'feature';
};
mkdirSync('docs/04-operations/tasks/wu', { recursive: true });
issues.forEach((issue) => {
const wuId = `WU-${String(issue.number).padStart(3, '0')}`;
const wu = {
id: wuId,
title: issue.title,
lane: laneFromLabel(issue.labels),
type: typeFromLabel(issue.labels),
status: 'ready',
priority: 'P2',
description: issue.body || '',
acceptance: extractTasks(issue.body),
code_paths: [],
external_refs: [`GH-${issue.number}`],
assigned_to: issue.assignees?.[0]?.login || null,
};
writeFileSync(`docs/04-operations/tasks/wu/${wuId}.yaml`, toYAML(wu));
});
function extractTasks(body) {
if (!body) return ['Feature works as specified'];
// GitHub uses - [ ] for task lists
const tasks = body.match(/- \[ \].+/g) || [];
const criteria = tasks.map((t) => t.replace(/^- \[ \]\s*/, '').trim());
return criteria.length > 0 ? criteria : ['Feature works as specified'];
}
function toYAML(obj) {
// Use yaml package in production
return `id: ${obj.id}
title: "${obj.title}"
lane: "${obj.lane}"
type: ${obj.type}
status: ${obj.status}
priority: ${obj.priority}
description: |
${obj.description.replace(/\n/g, '\n ')}
acceptance:
${obj.acceptance.map((a) => ` - "${a}"`).join('\n')}
code_paths: []
external_refs:
${obj.external_refs.map((r) => ` - "${r}"`).join('\n')}
${obj.assigned_to ? `assigned_to: ${obj.assigned_to}` : ''}`;
}
-
Audit existing tickets
Count and categorize:
- How many open tickets?
- How many are actually actionable?
- How many are stale (no activity in 30+ days)?
-
Archive stale tickets
Before migrating, close tickets that are:
- Older than 6 months with no activity
- Superseded by other work
- No longer relevant to current goals
-
Set up LumenFlow
Follow the Existing Projects Guide to set up LumenFlow in your repo.
-
Export remaining tickets
Use the scripts above to export actionable tickets.
-
Run conversion
node convert-jira.mjs # or convert-linear.mjs, convert-github.mjs
-
Review converted WUs
Check a sample:
- Are lanes correct?
- Are acceptance criteria specific enough?
- Do priorities make sense?
-
Generate backlog
npx lumenflow backlog:generate
-
Freeze old system
Set ticket system to read-only for the migrated project, or add a banner:
This project now uses LumenFlow. New work: see [repo]/docs/04-operations/tasks/
-
Team onboarding
Jira LumenFlow
──── ─────────
Epics/Roadmap Initiatives
↓ (quarterly) (linked via external_refs)
Stories ──→ WUs (execution)
(planning) ↓
Done
↓
Sprint Velocity ←──────────── DORA Metrics
Keep: High-level planning, stakeholder visibility, sprint reports
Move: Day-to-day execution, developer workflow, CI integration
Close Jira/Linear entirely. Use LumenFlow for:
- WUs (execution)
- Initiatives (multi-WU planning)
- Backlog (prioritized queue)
- DORA metrics (reporting)
Team A: Full LumenFlow
Team B: Hybrid (LumenFlow + Linear for planning)
Team C: Not yet migrated
Teams can adopt at different paces. Cross-team WUs use external_refs for linking.
id: WU-042
title: Implement user search
external_refs:
- JIRA-PROJ-123
- LINEAR-ABC-45
- GH-789
# Find WU by Jira ID
grep -r "JIRA-PROJ-123" docs/04-operations/tasks/wu/
Add WU reference back to original ticket:
Migrated to LumenFlow: WU-042
WUs support dependencies:
id: WU-101
dependencies:
- WU-100 # Must complete first
blocked_by:
- WU-099 # Currently blocked
LumenFlow doesn’t have sub-tasks. Instead:
- Small sub-tasks: Include in parent WU acceptance criteria
- Large sub-tasks: Promote to separate WUs with dependencies
Move attachments to:
- Code comments (if small snippets)
- Documentation files (if specs)
- External storage with links in WU description
LumenFlow provides:
pnpm flow:report - DORA metrics
cat docs/04-operations/tasks/status.md - Recent completions
- Initiative progress tracking
After migration, verify:
# 1. Validate all WU YAML files
npx lumenflow validate
# 2. Check for orphaned WUs (not in backlog)
npx lumenflow backlog:check
# 3. Verify external references are unique
grep -rh "external_refs" docs/04-operations/tasks/wu/ | sort | uniq -d
# 4. Run gates to ensure everything builds
npx gates