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
JavaScript
/**
* 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