This guide covers migrating from existing project management tools to LumenFlow. You can migrate incrementally or all at once.
If you’re already on LumenFlow v2.x, see below for what changed in v3.0.0.
Kernel + Pack architecture : The runtime is now a domain-agnostic kernel that loads pluggable packs. The Software Delivery Pack provides the 90+ tools you already use.
workspace.yaml : Kernel-level workspace spec plus CLI/tooling config in software_delivery. Defines packs, lanes, security boundaries, namespaces, and delivery workflow settings. See Workspace Spec .
5 new packages : @lumenflow/kernel, @lumenflow/runtime, @lumenflow/packs, @lumenflow/mcp, @lumenflow/control-plane-sdk.
14 granular lanes : Framework lanes split from 2 (Core, CLI) into 7 (Core Lifecycle, Core Validation, Core State Recovery, CLI WU Commands, CLI Memory State, CLI Orchestration, CLI Enforcement). Variable WIP limits with wip_justification.
Removed deprecated features : isAgentBranchSync(), single-word lane names, config version: '1.0'.
# 1. Upgrade all packages
pnpm lumenflow:upgrade --latest
# 2. Install new packages (if not pulled by upgrade)
pnpm add -D @lumenflow/kernel @lumenflow/runtime @lumenflow/packs \
@lumenflow/mcp @lumenflow/control-plane-sdk
# 3. Create workspace.yaml
# See the Workspace Spec reference for the full schema
# 4. (Optional) Adopt granular lanes
# Update workspace.yaml lane definitions
# e.g., "Framework: Core" → "Framework: Core Lifecycle"
# 5. Verify
pnpm list @lumenflow/cli # Should show 3.0.0
pnpm gates # Ensure gates still pass
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_toOne owner per WU Labels lane, typeStructured categorization Story Points Complexity estimate Optional, tool-call based for agents Acceptance Criteria acceptanceYAML array, verifiable outcomes Priority priorityP0-P3
Jira Status Linear Status GitHub Status LumenFlow Status To Do Backlog Open readyIn Progress In Progress In Progress in_progressBlocked Blocked - blockedIn Review In Review In Review waitingDone 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