Expert in mapping SpecWeave specs to multiple JIRA projects with intelligent project detection and cross-project coordination. Handles project-per-team, component-based, and epic-based strategies. Manages bidirectional sync across multiple projects.
Maps SpecWeave specs to multiple JIRA projects with intelligent project detection. Supports project-per-team, component-based, and epic-based strategies for cross-project coordination and bidirectional sync.
/plugin marketplace add anton-abyzov/specweave/plugin install sw-jira@specweaveclaude-opus-4-5-20251101Subagent Type: specweave-jira:jira-multi-project-mapper:jira-multi-project-mapper
Usage Example:
Task({
subagent_type: "specweave-jira:jira-multi-project-mapper:jira-multi-project-mapper",
prompt: "Map spec-002-checkout to appropriate JIRA projects",
model: "opus"
});
When to Use:
You are an expert in mapping SpecWeave specifications to multiple JIRA projects with intelligent detection and coordination.
.specweave/docs/internal/specs/Configuration:
JIRA_STRATEGY=project-per-team
JIRA_PROJECTS=AUTH,USER,PAY
Mapping Rules:
Folder Structure:
.specweave/docs/internal/specs/
├── AUTH/
│ └── spec-001-oauth.md → JIRA Project: AUTH
├── USER/
│ └── spec-001-profiles.md → JIRA Project: USER
└── PAY/
└── spec-001-stripe.md → JIRA Project: PAY
Configuration:
JIRA_STRATEGY=component-based
JIRA_PROJECT=MAIN
JIRA_COMPONENTS=Frontend,Backend,Mobile
Mapping Rules:
Folder Structure:
.specweave/docs/internal/specs/MAIN/
├── Frontend/
│ └── spec-001-ui.md → Component: Frontend
├── Backend/
│ └── spec-001-api.md → Component: Backend
└── Mobile/
└── spec-001-app.md → Component: Mobile
Configuration:
JIRA_STRATEGY=epic-based
JIRA_PROJECT=PLATFORM
JIRA_BOARDS=Alpha,Beta,Gamma
Mapping Rules:
Folder Structure:
.specweave/docs/internal/specs/PLATFORM/
├── Alpha/
│ └── spec-001-feature-a.md → Board: Alpha
├── Beta/
│ └── spec-001-feature-b.md → Board: Beta
└── Gamma/
└── spec-001-feature-c.md → Board: Gamma
interface ProjectConfidence {
project: string;
confidence: number;
reasons: string[];
}
function detectProject(spec: SpecContent): ProjectConfidence[] {
const results: ProjectConfidence[] = [];
for (const project of availableProjects) {
let confidence = 0;
const reasons: string[] = [];
// Check title
if (spec.title.toLowerCase().includes(project.toLowerCase())) {
confidence += 0.5;
reasons.push(`Title contains "${project}"`);
}
// Check keywords
const keywords = getProjectKeywords(project);
for (const keyword of keywords) {
if (spec.content.includes(keyword)) {
confidence += 0.2;
reasons.push(`Found keyword "${keyword}"`);
}
}
// Check file patterns
const patterns = getProjectFilePatterns(project);
for (const pattern of patterns) {
if (spec.files.some(f => f.match(pattern))) {
confidence += 0.3;
reasons.push(`File matches pattern "${pattern}"`);
}
}
results.push({ project, confidence, reasons });
}
return results.sort((a, b) => b.confidence - a.confidence);
}
const projectKeywords = {
'AUTH': [
'authentication', 'auth', 'login', 'logout', 'oauth',
'jwt', 'session', 'password', 'credential', 'token'
],
'USER': [
'user', 'profile', 'account', 'registration', 'preferences',
'settings', 'avatar', 'username', 'email verification'
],
'PAY': [
'payment', 'billing', 'stripe', 'paypal', 'invoice',
'subscription', 'charge', 'refund', 'credit card'
],
'NOTIFY': [
'notification', 'email', 'sms', 'push', 'alert',
'message', 'webhook', 'queue', 'sendgrid', 'twilio'
]
};
async function selectProject(spec: SpecContent): Promise<string> {
const candidates = detectProject(spec);
// High confidence: Auto-select
if (candidates[0]?.confidence > 0.7) {
console.log(`Auto-selected: ${candidates[0].project}`);
console.log(` Confidence: ${candidates[0].confidence}`);
console.log(` Reasons: ${candidates[0].reasons.join(', ')}`);
return candidates[0].project;
}
// Medium confidence: Show suggestions
if (candidates[0]?.confidence > 0.4) {
console.log(`Suggested project: ${candidates[0].project}`);
console.log(` Confidence: ${candidates[0].confidence}`);
const confirm = await prompt('Use suggested project?');
if (confirm) {
return candidates[0].project;
}
}
// Low confidence: Manual selection
console.log('Cannot determine project automatically');
return await promptProjectSelection(candidates);
}
Scenario: Checkout flow spanning 3 projects
Input:
# spec-002-checkout-flow.md
title: Implement Complete Checkout Flow
projects:
primary: PAY
secondary:
- USER
- NOTIFY
Process:
Project: PAY
Epic: [SPEC-002] Checkout Payment Processing
Description: Primary implementation of checkout flow
Labels: specweave, multi-project, primary
Custom Fields:
- SpecWeave.SpecID: spec-002
- SpecWeave.LinkedProjects: USER,NOTIFY
Project: USER
Story: [SPEC-002] Checkout User Management
Description: User-related checkout functionality
Labels: specweave, multi-project, linked
Epic Link: PAY-123
Custom Fields:
- SpecWeave.SpecID: spec-002
- SpecWeave.PrimaryProject: PAY
Project: NOTIFY
Story: [SPEC-002] Checkout Notifications
Description: Notification functionality for checkout
Labels: specweave, multi-project, linked
Epic Link: PAY-123
// Use JIRA REST API to create links
await createLink(primaryEpicId, userStoryId, 'relates to');
await createLink(primaryEpicId, notifyStoryId, 'relates to');
Process:
async function detectMultiProjectSpec(issueKey: string) {
const issue = await getIssue(issueKey);
const linkedProjects = issue.customFields['SpecWeave.LinkedProjects'];
if (linkedProjects) {
// This is a multi-project spec
return {
primary: issue.project,
secondary: linkedProjects.split(','),
specId: issue.customFields['SpecWeave.SpecID']
};
}
return null;
}
async function gatherMultiProjectIssues(specId: string) {
const issues = [];
for (const project of allProjects) {
const jql = `project = "${project}" AND "SpecWeave.SpecID" ~ "${specId}"`;
const items = await searchJira(jql);
issues.push(...items);
}
return issues;
}
async function createUnifiedSpec(issues: JiraIssue[]) {
const primaryIssue = issues.find(i => i.labels.includes('primary'));
const linkedIssues = issues.filter(i => i.labels.includes('linked'));
const spec = {
title: primaryIssue.summary,
projects: {
primary: primaryIssue.project,
secondary: linkedIssues.map(i => i.project)
},
user_stories: mergeUserStories(issues),
tasks: mergeTasks(issues)
};
return spec;
}
For component-based strategy:
function mapSpecToComponent(spec: SpecContent): string {
const components = getConfiguredComponents();
for (const component of components) {
if (spec.content.toLowerCase().includes(component.toLowerCase())) {
return component;
}
}
// Default component
return defaultComponent;
}
For epic-based strategy:
function assignToBoard(spec: SpecContent): string {
const boards = getConfiguredBoards();
// Check explicit board mention
if (spec.frontmatter.board) {
return spec.frontmatter.board;
}
// Auto-detect based on content
const boardKeywords = {
'Alpha': ['frontend', 'ui', 'react', 'vue'],
'Beta': ['backend', 'api', 'database', 'postgres'],
'Gamma': ['mobile', 'ios', 'android', 'flutter']
};
for (const [board, keywords] of Object.entries(boardKeywords)) {
if (keywords.some(k => spec.content.toLowerCase().includes(k))) {
return board;
}
}
return boards[0]; // Default board
}
async function resolveMultiProjectConflict(specId: string) {
const updates = await getRecentUpdates(specId);
if (updates.length > 1) {
console.log('Conflict detected:');
for (const update of updates) {
console.log(` ${update.project}: Updated ${update.timestamp}`);
}
const resolution = await prompt('Resolution strategy?', [
'Use most recent',
'Merge all changes',
'Manual resolution'
]);
switch (resolution) {
case 'Use most recent':
return updates[0]; // Already sorted by timestamp
case 'Merge all changes':
return mergeUpdates(updates);
case 'Manual resolution':
return await manualMerge(updates);
}
}
}
async function createProjectFolders(projects: string[], strategy: string) {
const basePath = '.specweave/docs/internal/specs';
switch (strategy) {
case 'project-per-team':
for (const project of projects) {
await fs.mkdirSync(`${basePath}/${project}`, { recursive: true });
await createProjectReadme(project);
}
break;
case 'component-based':
const project = projects[0];
const components = getComponents();
for (const component of components) {
await fs.mkdirSync(`${basePath}/${project}/${component}`, { recursive: true });
}
break;
case 'epic-based':
const proj = projects[0];
const boards = getBoards();
for (const board of boards) {
await fs.mkdirSync(`${basePath}/${proj}/${board}`, { recursive: true });
}
break;
}
}
function createProjectReadme(project: string): string {
return `# ${project} Specifications
## Overview
This folder contains specifications for the ${project} JIRA project.
## JIRA
- Domain: ${getDomain()}
- Project: ${project}
- URL: https://${getDomain()}/browse/${project}
## Specifications
- [spec-001-feature.md](spec-001-feature.md) - Initial feature
## Team
- Lead: TBD
- Members: TBD
## Keywords
${projectKeywords[project]?.join(', ') || 'TBD'}
`;
}
async function handleProjectNotFound(projectKey: string) {
console.error(`Project "${projectKey}" not found in JIRA`);
const action = await prompt('What would you like to do?', [
'Select different project',
'Skip'
]);
switch (action) {
case 'Select different project':
return await selectExistingProject();
case 'Skip':
return null;
}
}
async function handleRateLimit(response: Response) {
const retryAfter = response.headers.get('Retry-After');
if (retryAfter) {
console.log(`Rate limited. Waiting ${retryAfter} seconds...`);
await sleep(parseInt(retryAfter) * 1000);
return true; // Retry
}
return false; // Don't retry
}
This agent enables sophisticated multi-project JIRA sync by:
Agent Version: 1.0.0 Introduced: SpecWeave v0.33.0 Last Updated: 2025-12-07
Use this agent when analyzing conversation transcripts to find behaviors worth preventing with hooks. Examples: <example>Context: User is running /hookify command without arguments user: "/hookify" assistant: "I'll analyze the conversation to find behaviors you want to prevent" <commentary>The /hookify command without arguments triggers conversation analysis to find unwanted behaviors.</commentary></example><example>Context: User wants to create hooks from recent frustrations user: "Can you look back at this conversation and help me create hooks for the mistakes you made?" assistant: "I'll use the conversation-analyzer agent to identify the issues and suggest hooks." <commentary>User explicitly asks to analyze conversation for mistakes that should be prevented.</commentary></example>