Deterministic calculation of specification completeness scores.
From spec-iterator-mcpnpx claudepluginhub JesseHenson/claude_code_apex_marketplace --plugin spec-iterator-mcpThis skill uses the workspace's default tool permissions.
Provides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Fetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.
Designs KPI dashboards with metrics selection (MRR, churn, LTV/CAC), visualization best practices, real-time monitoring, and hierarchy for executives, operations, and product teams.
Deterministic calculation of specification completeness scores.
Provides consistent, reproducible completeness scoring based on answered questions and their categories/priorities.
const CATEGORY_WEIGHTS = {
functional: 0.30, // What the system does
technical: 0.25, // How it's built
ux: 0.20, // User experience
edgeCases: 0.15, // Error handling
constraints: 0.10 // Business limits
};
const PRIORITY_WEIGHTS = {
critical: 40,
important: 35,
nice_to_have: 25
};
function calculateCompleteness(
clarifications: Clarification[],
pendingQuestions: Question[]
): Completeness {
const allQuestions = [
...clarifications.map(c => ({
category: c.category,
priority: getPriority(c.questionId),
answered: true
})),
...pendingQuestions.map(q => ({
category: q.category,
priority: q.priority,
answered: false
}))
];
const categoryScores: Record<Category, number> = {
functional: 0,
technical: 0,
ux: 0,
edgeCases: 0,
constraints: 0
};
// Calculate per-category scores
for (const category of Object.keys(categoryScores) as Category[]) {
const categoryQuestions = allQuestions.filter(q => q.category === category);
if (categoryQuestions.length === 0) {
// No questions in category = assume partial coverage
categoryScores[category] = 30;
continue;
}
const maxPoints = categoryQuestions.reduce(
(sum, q) => sum + PRIORITY_WEIGHTS[q.priority],
0
);
const earnedPoints = categoryQuestions
.filter(q => q.answered)
.reduce((sum, q) => sum + PRIORITY_WEIGHTS[q.priority], 0);
categoryScores[category] = Math.round((earnedPoints / maxPoints) * 100);
}
// Calculate weighted overall
const overall = Math.round(
categoryScores.functional * CATEGORY_WEIGHTS.functional +
categoryScores.technical * CATEGORY_WEIGHTS.technical +
categoryScores.ux * CATEGORY_WEIGHTS.ux +
categoryScores.edgeCases * CATEGORY_WEIGHTS.edgeCases +
categoryScores.constraints * CATEGORY_WEIGHTS.constraints
);
return {
overall,
functional: categoryScores.functional,
technical: categoryScores.technical,
ux: categoryScores.ux,
edgeCases: categoryScores.edgeCases,
constraints: categoryScores.constraints
};
}
const THRESHOLDS = {
LOW: 40, // Below this: significant gaps
PARTIAL: 60, // Below this: needs more work
GOOD: 80, // At or above: can generate
EXCELLENT: 90 // High confidence spec
};
function getStatus(overall: number): string {
if (overall < THRESHOLDS.LOW) return 'low';
if (overall < THRESHOLDS.PARTIAL) return 'partial';
if (overall < THRESHOLDS.GOOD) return 'good';
return 'ready';
}
function canGenerate(completeness: Completeness): boolean {
return completeness.overall >= THRESHOLDS.GOOD;
}
function renderProgressBar(score: number, width: number = 20): string {
const filled = Math.round((score / 100) * width);
const empty = width - filled;
return '█'.repeat(filled) + '░'.repeat(empty);
}
function renderCompleteness(completeness: Completeness): string {
return `
Completeness:
Overall: ${renderProgressBar(completeness.overall)} ${completeness.overall}%
Functional: ${renderProgressBar(completeness.functional)} ${completeness.functional}%
Technical: ${renderProgressBar(completeness.technical)} ${completeness.technical}%
UX: ${renderProgressBar(completeness.ux)} ${completeness.ux}%
Edge Cases: ${renderProgressBar(completeness.edgeCases)} ${completeness.edgeCases}%
Constraints: ${renderProgressBar(completeness.constraints)} ${completeness.constraints}%
`.trim();
}
// After answering questions
const completeness = calculateCompleteness(
session.clarifications,
session.pendingQuestions || []
);
// Update session
session.completeness = completeness;
// Check if ready
if (canGenerate(completeness)) {
session.status = 'ready_to_generate';
}