UNPKG

aiwg

Version:

Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo

165 lines 5.36 kB
/** * ID Extractor - Extract requirement IDs from various file types * Supports: Use Cases (UC-xxx), NFRs (NFR-XXX-xxx), User Stories (US-xxx), Features (F-xxx) */ /** * IDExtractor - Extract requirement IDs from code, tests, and documentation */ export class IDExtractor { // Regex patterns for different ID types patterns = { useCase: /\bUC-\d{3}\b/g, nfr: /\bNFR-[A-Z]{3,10}-\d{3}\b/g, userStory: /\bUS-\d{3}\b/g, feature: /\bF-\d{3}\b/g, acceptanceCriteria: /\bAC-\d{3}\b/g }; // Combined pattern for all ID types combinedPattern = /\b(?:UC-\d{3}|NFR-[A-Z]{3,10}-\d{3}|US-\d{3}|F-\d{3}|AC-\d{3})\b/g; /** * Extract IDs from a single line of text */ extractFromLine(line, lineNumber) { const ids = []; const matches = line.match(this.combinedPattern); if (!matches) { return ids; } // Remove duplicates const uniqueMatches = Array.from(new Set(matches)); for (const id of uniqueMatches) { ids.push({ id, type: this.determineType(id), lineNumber, context: this.extractContext(line, id) }); } return ids; } /** * Extract IDs from file content */ extractFromContent(content, _filePath) { const lines = content.split('\n'); const allIds = []; for (let i = 0; i < lines.length; i++) { const lineIds = this.extractFromLine(lines[i], i + 1); allIds.push(...lineIds); } // Remove duplicate IDs (keep first occurrence) const seen = new Set(); const uniqueIds = []; for (const reqId of allIds) { if (!seen.has(reqId.id)) { seen.add(reqId.id); uniqueIds.push(reqId); } } return uniqueIds; } /** * Extract IDs from multiple files (parallel processing) */ async extractFromFiles(files) { const results = new Map(); // Process all files in parallel const promises = Array.from(files.entries()).map(async ([filePath, content]) => { const startTime = performance.now(); const ids = this.extractFromContent(content, filePath); const extractionTime = performance.now() - startTime; return { filePath, result: { filePath, ids, extractionTime } }; }); const completed = await Promise.all(promises); for (const { filePath, result } of completed) { results.set(filePath, result); } return results; } /** * Determine requirement type from ID pattern */ determineType(id) { if (id.startsWith('UC-')) return 'use-case'; if (id.startsWith('NFR-')) return 'nfr'; if (id.startsWith('US-')) return 'user-story'; if (id.startsWith('F-')) return 'feature'; if (id.startsWith('AC-')) return 'acceptance-criteria'; // Should never happen due to regex, but TypeScript needs it return 'use-case'; } /** * Extract context around the ID (up to 50 characters before/after) */ extractContext(line, id) { const index = line.indexOf(id); if (index === -1) return line.trim(); const start = Math.max(0, index - 50); const end = Math.min(line.length, index + id.length + 50); let context = line.substring(start, end).trim(); // Add ellipsis if truncated if (start > 0) context = '...' + context; if (end < line.length) context = context + '...'; return context; } /** * Validate ID format */ isValidId(id) { const pattern = /\b(?:UC-\d{3}|NFR-[A-Z]{3,10}-\d{3}|US-\d{3}|F-\d{3}|AC-\d{3})\b/; return pattern.test(id); } /** * Parse ID to extract components (e.g., NFR-PERF-001 -> {prefix: 'NFR', category: 'PERF', number: '001'}) */ parseId(id) { // Use case: UC-001 const ucMatch = id.match(/^(UC)-(\d{3})$/); if (ucMatch) { return { prefix: ucMatch[1], number: ucMatch[2] }; } // NFR: NFR-PERF-001 const nfrMatch = id.match(/^(NFR)-([A-Z]{3,6})-(\d{3})$/); if (nfrMatch) { return { prefix: nfrMatch[1], category: nfrMatch[2], number: nfrMatch[3] }; } // User Story: US-001 const usMatch = id.match(/^(US)-(\d{3})$/); if (usMatch) { return { prefix: usMatch[1], number: usMatch[2] }; } // Feature: F-001 const fMatch = id.match(/^(F)-(\d{3})$/); if (fMatch) { return { prefix: fMatch[1], number: fMatch[2] }; } // Acceptance Criteria: AC-001 const acMatch = id.match(/^(AC)-(\d{3})$/); if (acMatch) { return { prefix: acMatch[1], number: acMatch[2] }; } return null; } /** * Get all patterns for testing/validation */ getPatterns() { return { ...this.patterns }; } } //# sourceMappingURL=id-extractor.js.map