Files
gh-anton-abyzov-specweave-p…/commands/specweave-jira-import-projects.md
2025-11-29 17:56:44 +08:00

8.8 KiB

name, description
name description
specweave-jira:import-projects Import additional JIRA projects post-init with filtering, resume support, and dry-run preview

Import JIRA Projects Command

You are a JIRA project import expert. Help users add additional JIRA projects to their SpecWeave workspace after initial setup.

Purpose

This command allows users to import additional JIRA projects after initial SpecWeave setup (specweave init), with advanced filtering, resume capability, and dry-run preview.

Use Cases:

  • Adding new JIRA projects to existing workspace
  • Importing archived/paused projects later
  • Selective import with filters (active only, specific types, custom JQL)

Command Syntax

# Basic import (interactive)
/specweave-jira:import-projects

# With filters
/specweave-jira:import-projects --filter active
/specweave-jira:import-projects --type agile --lead "john.doe@company.com"
/specweave-jira:import-projects --jql "project IN (BACKEND, FRONTEND) AND status != Archived"

# Dry-run (preview)
/specweave-jira:import-projects --dry-run

# Resume interrupted import
/specweave-jira:import-projects --resume

# Combined
/specweave-jira:import-projects --filter active --dry-run

Your Task

When the user runs this command:

Step 1: Validate Prerequisites

import { readEnvFile, parseEnvFile } from '../../../src/utils/env-file.js';

// 1. Check if Jira credentials exist
const envContent = readEnvFile(process.cwd());
const parsed = parseEnvFile(envContent);

if (!parsed.JIRA_API_TOKEN || !parsed.JIRA_EMAIL || !parsed.JIRA_DOMAIN) {
  console.log('❌ Missing Jira credentials. Run `specweave init` first.');
  return;
}

// 2. Get existing projects
const existingProjects = parsed.JIRA_PROJECTS?.split(',') || [];
console.log(`\n📋 Current projects: ${existingProjects.join(', ') || 'None'}\n`);

Step 2: Fetch Available Projects

import { getProjectCount } from '../../../src/cli/helpers/project-count-fetcher.js';
import { AsyncProjectLoader } from '../../../src/cli/helpers/async-project-loader.js';

// Count check (< 1 second)
const countResult = await getProjectCount({
  provider: 'jira',
  credentials: {
    domain: parsed.JIRA_DOMAIN,
    email: parsed.JIRA_EMAIL,
    token: parsed.JIRA_API_TOKEN,
    instanceType: 'cloud'
  }
});

console.log(`✓ Found ${countResult.accessible} accessible projects`);

// Fetch all projects (with smart pagination)
const loader = new AsyncProjectLoader(
  {
    domain: parsed.JIRA_DOMAIN,
    email: parsed.JIRA_EMAIL,
    token: parsed.JIRA_API_TOKEN,
    instanceType: 'cloud'
  },
  'jira',
  {
    batchSize: 50,
    updateFrequency: 5,
    showEta: true
  }
);

const result = await loader.fetchAllProjects(countResult.accessible);
let allProjects = result.projects;

Step 3: Apply Filters (if specified)

import { FilterProcessor } from '../../../src/integrations/jira/filter-processor.js';

const options = {
  filter: args.filter,       // 'active' | 'all'
  type: args.type,           // 'agile' | 'software' | 'business'
  lead: args.lead,           // Email address
  jql: args.jql              // Custom JQL
};

const filterProcessor = new FilterProcessor({ domain: parsed.JIRA_DOMAIN, token: parsed.JIRA_API_TOKEN });
const filteredProjects = await filterProcessor.applyFilters(allProjects, options);

console.log(`\n🔍 Filters applied:`);
if (options.filter === 'active') console.log(`   • Active projects only`);
if (options.type) console.log(`   • Type: ${options.type}`);
if (options.lead) console.log(`   • Lead: ${options.lead}`);
if (options.jql) console.log(`   • JQL: ${options.jql}`);
console.log(`\n📊 Results: ${filteredProjects.length} projects (down from ${allProjects.length})\n`);

Step 4: Exclude Existing Projects

const newProjects = filteredProjects.filter(p => !existingProjects.includes(p.key));

if (newProjects.length === 0) {
  console.log('✅ No new projects to import. All filtered projects are already configured.');
  return;
}

console.log(`📥 ${newProjects.length} new project(s) available for import:\n`);
newProjects.forEach(p => {
  console.log(`   ✨ ${p.key} - ${p.name} (${p.projectTypeKey}, lead: ${p.lead?.displayName || 'N/A'})`);
});

Step 5: Dry-Run or Execute

if (args.dryRun) {
  console.log('\n🔎 DRY RUN: No changes will be made.\n');
  console.log('The following projects would be imported:');
  newProjects.forEach(p => {
    const status = p.archived ? '⏭️  (archived - skipped)' : '✨';
    console.log(`   ${status} ${p.key} - ${p.name}`);
  });
  console.log(`\nTotal: ${newProjects.filter(p => !p.archived).length} projects would be imported\n`);
  return;
}

// Confirm import
const { confirmed } = await inquirer.prompt([{
  type: 'confirm',
  name: 'confirmed',
  message: `Import ${newProjects.length} project(s)?`,
  default: true
}]);

if (!confirmed) {
  console.log('⏭️  Import cancelled.');
  return;
}

Step 6: Merge with Existing

import { updateEnvFile, mergeProjectList } from '../../../src/utils/env-manager.js';

const newKeys = newProjects.map(p => p.key);
const mergedProjects = mergeProjectList(existingProjects, newKeys);

// Update .env file (atomic write)
await updateEnvFile('JIRA_PROJECTS', mergedProjects.join(','));

console.log('\n✅ Projects imported successfully!\n');
console.log(`Updated: ${existingProjects.length}${mergedProjects.length} projects`);
console.log(`\nCurrent projects:\n   ${mergedProjects.join(', ')}\n`);

Step 7: Resume Support

if (args.resume) {
  const { CacheManager } = await import('../../../src/core/cache/cache-manager.js');
  const cacheManager = new CacheManager(process.cwd());

  const importState = await cacheManager.get('jira-import-state');

  if (!importState) {
    console.log('⚠️  No import state found. Use without --resume to start fresh.');
    return;
  }

  console.log(`\n📂 Resuming from: ${importState.lastProject} (${importState.completed}/${importState.total})`);

  // Skip already-imported projects
  const remainingProjects = allProjects.filter(p => !importState.succeeded.includes(p.key));

  // Continue import with remaining projects
  // (use same logic as Step 6)
}

Examples

Example 1: Basic Import

User: /specweave-jira:import-projects

Output:

📋 Current projects: BACKEND, FRONTEND

✓ Found 127 accessible projects
📥 5 new project(s) available for import:

   ✨ MOBILE - Mobile App (agile, lead: John Doe)
   ✨ INFRA - Infrastructure (software, lead: Jane Smith)
   ✨ QA - Quality Assurance (business, lead: Bob Wilson)
   ✨ DOCS - Documentation (business, lead: Alice Cooper)
   ✨ LEGACY - Legacy System (archived - skipped)

Import 5 project(s)? (Y/n)

✅ Projects imported successfully!

Updated: 2 → 6 projects

Current projects:
   BACKEND, FRONTEND, MOBILE, INFRA, QA, DOCS

Example 2: Filter Active Only

User: /specweave-jira:import-projects --filter active

Output:

🔍 Filters applied:
   • Active projects only

📊 Results: 120 projects (down from 127)

📥 4 new project(s) available for import:
   (LEGACY project excluded because it's archived)

Example 3: Dry-Run

User: /specweave-jira:import-projects --dry-run

Output:

🔎 DRY RUN: No changes will be made.

The following projects would be imported:
   ✨ MOBILE - Mobile App
   ✨ INFRA - Infrastructure
   ✨ QA - Quality Assurance
   ⏭️  LEGACY - Legacy System (archived - skipped)

Total: 3 projects would be imported

Example 4: Custom JQL Filter

User: /specweave-jira:import-projects --jql "project IN (MOBILE, INFRA) AND status != Archived"

Output:

🔍 Filters applied:
   • JQL: project IN (MOBILE, INFRA) AND status != Archived

📊 Results: 2 projects (down from 127)

📥 2 new project(s) available for import:
   ✨ MOBILE - Mobile App
   ✨ INFRA - Infrastructure

Important Notes

  • Merge Logic: No duplicates. Existing projects are preserved.
  • Atomic Updates: Uses temp file + rename to prevent corruption.
  • Progress Tracking: Shows progress bar for large imports (> 50 projects).
  • Resume Support: Interrupted imports can be resumed with --resume.
  • Backup: Creates .env.backup before modifying .env.
  • /specweave:init - Initial SpecWeave setup
  • /specweave-jira:sync - Sync increments with Jira epics
  • /specweave-jira:refresh-cache - Clear cached project data

Error Handling

  • Missing Credentials: Prompt user to run specweave init first
  • API Errors: Show clear error message with suggestion
  • No New Projects: Inform user all projects already imported
  • Permission Errors: Check .env file permissions

Post-Init Flexibility: This command provides flexibility to add projects after initial setup, supporting evolving team structures and project lifecycles.