@nerviq/cli 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +170 -73
- package/package.json +3 -5
- package/src/activity.js +20 -0
- package/src/aider/domain-packs.js +27 -2
- package/src/aider/mcp-packs.js +231 -0
- package/src/aider/techniques.js +3210 -1397
- package/src/audit.js +290 -9
- package/src/catalog.js +18 -2
- package/src/codex/domain-packs.js +23 -1
- package/src/codex/mcp-packs.js +254 -0
- package/src/codex/techniques.js +4738 -3257
- package/src/copilot/domain-packs.js +23 -1
- package/src/copilot/mcp-packs.js +254 -0
- package/src/copilot/techniques.js +3433 -1936
- package/src/cursor/domain-packs.js +23 -1
- package/src/cursor/mcp-packs.js +257 -0
- package/src/cursor/techniques.js +3697 -1869
- package/src/deprecation.js +98 -0
- package/src/domain-pack-expansion.js +571 -0
- package/src/domain-packs.js +25 -2
- package/src/formatters/otel.js +151 -0
- package/src/gemini/domain-packs.js +23 -1
- package/src/gemini/mcp-packs.js +257 -0
- package/src/gemini/techniques.js +3734 -2238
- package/src/integrations.js +194 -0
- package/src/mcp-packs.js +233 -0
- package/src/opencode/domain-packs.js +23 -1
- package/src/opencode/mcp-packs.js +231 -0
- package/src/opencode/techniques.js +3500 -1687
- package/src/org.js +68 -0
- package/src/source-urls.js +410 -260
- package/src/stack-checks.js +565 -0
- package/src/supplemental-checks.js +767 -0
- package/src/techniques.js +2929 -1449
- package/src/telemetry.js +160 -0
- package/src/windsurf/domain-packs.js +23 -1
- package/src/windsurf/mcp-packs.js +257 -0
- package/src/windsurf/techniques.js +3647 -1834
- package/src/workspace.js +233 -0
- package/CHANGELOG.md +0 -198
- package/content/case-study-template.md +0 -91
- package/content/claims-governance.md +0 -37
- package/content/claude-code/audit-repo/SKILL.md +0 -20
- package/content/claude-native-integration.md +0 -60
- package/content/devto-article.json +0 -9
- package/content/launch-posts.md +0 -226
- package/content/pilot-rollout-kit.md +0 -30
- package/content/release-checklist.md +0 -31
package/src/audit.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Audit engine - evaluates project against CLAUDEX technique database.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
const path = require('path');
|
|
5
6
|
const { TECHNIQUES: CLAUDE_TECHNIQUES, STACKS } = require('./techniques');
|
|
6
7
|
const { ProjectContext } = require('./context');
|
|
7
8
|
const { CODEX_TECHNIQUES } = require('./codex/techniques');
|
|
@@ -25,8 +26,13 @@ const { OpenCodeProjectContext } = require('./opencode/context');
|
|
|
25
26
|
const { getBadgeMarkdown } = require('./badge');
|
|
26
27
|
const { sendInsights, getLocalInsights } = require('./insights');
|
|
27
28
|
const { getRecommendationOutcomeSummary, getRecommendationAdjustment } = require('./activity');
|
|
29
|
+
const { getFeedbackSummary } = require('./feedback');
|
|
28
30
|
const { formatSarif } = require('./formatters/sarif');
|
|
31
|
+
const { formatOtelMetrics } = require('./formatters/otel');
|
|
29
32
|
const { loadPlugins, mergePluginChecks } = require('./plugins');
|
|
33
|
+
const { hasWorkspaceConfig, detectWorkspaceGlobs, detectWorkspaces } = require('./workspace');
|
|
34
|
+
const { detectDeprecationWarnings } = require('./deprecation');
|
|
35
|
+
const { version: packageVersion } = require('../package.json');
|
|
30
36
|
|
|
31
37
|
const COLORS = {
|
|
32
38
|
reset: '\x1b[0m',
|
|
@@ -57,6 +63,8 @@ function formatLocation(file, line) {
|
|
|
57
63
|
|
|
58
64
|
const IMPACT_ORDER = { critical: 3, high: 2, medium: 1, low: 0 };
|
|
59
65
|
const WEIGHTS = { critical: 15, high: 10, medium: 5, low: 2 };
|
|
66
|
+
const LARGE_INSTRUCTION_WARN_BYTES = 50 * 1024;
|
|
67
|
+
const LARGE_INSTRUCTION_SKIP_BYTES = 1024 * 1024;
|
|
60
68
|
const CATEGORY_MODULES = {
|
|
61
69
|
memory: 'CLAUDE.md',
|
|
62
70
|
quality: 'verification',
|
|
@@ -349,6 +357,171 @@ function getAuditSpec(platform = 'claude') {
|
|
|
349
357
|
};
|
|
350
358
|
}
|
|
351
359
|
|
|
360
|
+
function normalizeRelativePath(filePath) {
|
|
361
|
+
return String(filePath || '').replace(/\\/g, '/').replace(/^\.\//, '');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function addPath(target, filePath) {
|
|
365
|
+
if (!filePath || typeof filePath !== 'string') return;
|
|
366
|
+
target.add(normalizeRelativePath(filePath));
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function addDirFiles(ctx, target, dirPath, filter) {
|
|
370
|
+
if (typeof ctx.dirFiles !== 'function') return;
|
|
371
|
+
for (const file of ctx.dirFiles(dirPath)) {
|
|
372
|
+
if (filter && !filter.test(file)) continue;
|
|
373
|
+
addPath(target, path.join(dirPath, file));
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function instructionFileCandidates(spec, ctx) {
|
|
378
|
+
const candidates = new Set();
|
|
379
|
+
|
|
380
|
+
if (spec.platform === 'claude') {
|
|
381
|
+
addPath(candidates, 'CLAUDE.md');
|
|
382
|
+
addPath(candidates, '.claude/CLAUDE.md');
|
|
383
|
+
addDirFiles(ctx, candidates, '.claude/rules', /\.md$/i);
|
|
384
|
+
addDirFiles(ctx, candidates, '.claude/commands', /\.md$/i);
|
|
385
|
+
addDirFiles(ctx, candidates, '.claude/agents', /\.md$/i);
|
|
386
|
+
if (typeof ctx.dirFiles === 'function') {
|
|
387
|
+
for (const skillDir of ctx.dirFiles('.claude/skills')) {
|
|
388
|
+
addPath(candidates, path.join('.claude', 'skills', skillDir, 'SKILL.md'));
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (spec.platform === 'codex') {
|
|
394
|
+
addPath(candidates, 'AGENTS.md');
|
|
395
|
+
addPath(candidates, 'AGENTS.override.md');
|
|
396
|
+
addPath(candidates, typeof ctx.agentsMdPath === 'function' ? ctx.agentsMdPath() : null);
|
|
397
|
+
addDirFiles(ctx, candidates, 'codex/rules');
|
|
398
|
+
addDirFiles(ctx, candidates, '.codex/rules');
|
|
399
|
+
if (typeof ctx.skillDirs === 'function') {
|
|
400
|
+
for (const skillDir of ctx.skillDirs()) {
|
|
401
|
+
addPath(candidates, path.join('.agents', 'skills', skillDir, 'SKILL.md'));
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (spec.platform === 'gemini') {
|
|
407
|
+
addPath(candidates, 'GEMINI.md');
|
|
408
|
+
addPath(candidates, '.gemini/GEMINI.md');
|
|
409
|
+
addDirFiles(ctx, candidates, '.gemini/agents', /\.md$/i);
|
|
410
|
+
if (typeof ctx.skillDirs === 'function') {
|
|
411
|
+
for (const skillDir of ctx.skillDirs()) {
|
|
412
|
+
addPath(candidates, path.join('.gemini', 'skills', skillDir, 'SKILL.md'));
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (spec.platform === 'copilot') {
|
|
418
|
+
addPath(candidates, '.github/copilot-instructions.md');
|
|
419
|
+
addDirFiles(ctx, candidates, '.github/instructions', /\.instructions\.md$/i);
|
|
420
|
+
addDirFiles(ctx, candidates, '.github/prompts', /\.prompt\.md$/i);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (spec.platform === 'cursor') {
|
|
424
|
+
addPath(candidates, '.cursorrules');
|
|
425
|
+
addDirFiles(ctx, candidates, '.cursor/rules', /\.mdc$/i);
|
|
426
|
+
addDirFiles(ctx, candidates, '.cursor/commands', /\.md$/i);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (spec.platform === 'windsurf') {
|
|
430
|
+
addPath(candidates, '.windsurfrules');
|
|
431
|
+
addDirFiles(ctx, candidates, '.windsurf/rules', /\.md$/i);
|
|
432
|
+
addDirFiles(ctx, candidates, '.windsurf/workflows', /\.md$/i);
|
|
433
|
+
addDirFiles(ctx, candidates, '.windsurf/memories', /\.(md|json)$/i);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (spec.platform === 'aider' && typeof ctx.conventionFiles === 'function') {
|
|
437
|
+
for (const file of ctx.conventionFiles()) {
|
|
438
|
+
addPath(candidates, file);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (spec.platform === 'opencode') {
|
|
443
|
+
addPath(candidates, 'AGENTS.md');
|
|
444
|
+
addPath(candidates, 'CLAUDE.md');
|
|
445
|
+
addDirFiles(ctx, candidates, '.opencode/commands', /\.(md|markdown|ya?ml)$/i);
|
|
446
|
+
if (typeof ctx.skillDirs === 'function') {
|
|
447
|
+
for (const skillDir of ctx.skillDirs()) {
|
|
448
|
+
addPath(candidates, path.join('.opencode', 'commands', skillDir, 'SKILL.md'));
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return [...candidates];
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function inspectInstructionFiles(spec, ctx) {
|
|
457
|
+
const warnings = [];
|
|
458
|
+
|
|
459
|
+
for (const filePath of instructionFileCandidates(spec, ctx)) {
|
|
460
|
+
const byteCount = typeof ctx.fileSizeBytes === 'function' ? ctx.fileSizeBytes(filePath) : null;
|
|
461
|
+
if (!Number.isFinite(byteCount) || byteCount <= LARGE_INSTRUCTION_WARN_BYTES) continue;
|
|
462
|
+
|
|
463
|
+
const content = typeof ctx.fileContent === 'function' ? ctx.fileContent(filePath) : null;
|
|
464
|
+
warnings.push({
|
|
465
|
+
file: normalizeRelativePath(filePath),
|
|
466
|
+
byteCount,
|
|
467
|
+
lineCount: typeof content === 'string' ? content.split(/\r?\n/).length : null,
|
|
468
|
+
skipped: byteCount > LARGE_INSTRUCTION_SKIP_BYTES,
|
|
469
|
+
severity: byteCount > LARGE_INSTRUCTION_SKIP_BYTES ? 'critical' : 'warning',
|
|
470
|
+
message: byteCount > LARGE_INSTRUCTION_SKIP_BYTES
|
|
471
|
+
? 'Instruction file exceeds 1MB and will be skipped during audit.'
|
|
472
|
+
: 'Instruction file exceeds 50KB. Audit will continue, but this file may reduce runtime clarity.',
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return warnings;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function guardSkippedInstructionFiles(ctx, warnings) {
|
|
480
|
+
const skippedFiles = new Set(
|
|
481
|
+
warnings.filter((item) => item.skipped).map((item) => normalizeRelativePath(item.file))
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
if (skippedFiles.size === 0) return;
|
|
485
|
+
|
|
486
|
+
const originalFileContent = typeof ctx.fileContent === 'function' ? ctx.fileContent.bind(ctx) : null;
|
|
487
|
+
const originalLineNumber = typeof ctx.lineNumber === 'function' ? ctx.lineNumber.bind(ctx) : null;
|
|
488
|
+
|
|
489
|
+
if (originalFileContent) {
|
|
490
|
+
ctx.fileContent = (filePath) => {
|
|
491
|
+
if (skippedFiles.has(normalizeRelativePath(filePath))) return null;
|
|
492
|
+
return originalFileContent(filePath);
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (originalLineNumber) {
|
|
497
|
+
ctx.lineNumber = (filePath, matcher) => {
|
|
498
|
+
if (skippedFiles.has(normalizeRelativePath(filePath))) return null;
|
|
499
|
+
return originalLineNumber(filePath, matcher);
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function buildWorkspaceHint(dir) {
|
|
505
|
+
if (!hasWorkspaceConfig(dir)) {
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const patterns = detectWorkspaceGlobs(dir);
|
|
510
|
+
const workspaces = detectWorkspaces(dir);
|
|
511
|
+
if (patterns.length === 0 && workspaces.length === 0) {
|
|
512
|
+
return null;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return {
|
|
516
|
+
detected: true,
|
|
517
|
+
patterns,
|
|
518
|
+
workspaces,
|
|
519
|
+
suggestedCommand: patterns.length > 0
|
|
520
|
+
? `npx nerviq audit --workspace ${patterns.join(',')}`
|
|
521
|
+
: `npx nerviq audit --workspace ${workspaces.join(',')}`,
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
352
525
|
function riskFromImpact(impact) {
|
|
353
526
|
if (impact === 'critical') return 'high';
|
|
354
527
|
if (impact === 'high') return 'medium';
|
|
@@ -441,24 +614,48 @@ function getQuickWins(failed, options = {}) {
|
|
|
441
614
|
.slice(0, 3);
|
|
442
615
|
}
|
|
443
616
|
|
|
444
|
-
|
|
617
|
+
/**
|
|
618
|
+
* Compute a multiplier based on FP (helpful/not-helpful) feedback for a check key.
|
|
619
|
+
* - >50% "not helpful" feedback: lower priority by 30% (multiplier 0.7)
|
|
620
|
+
* - >80% "helpful" feedback: boost priority by 20% (multiplier 1.2)
|
|
621
|
+
* - Otherwise: no change (multiplier 1.0)
|
|
622
|
+
* @param {Object} fpFeedbackByKey - Keyed feedback summary from getFeedbackSummary().byKey
|
|
623
|
+
* @param {string} key - The check key to look up
|
|
624
|
+
* @returns {number} Multiplier to apply to priority score
|
|
625
|
+
*/
|
|
626
|
+
function getFpFeedbackMultiplier(fpFeedbackByKey, key) {
|
|
627
|
+
if (!fpFeedbackByKey) return 1.0;
|
|
628
|
+
const bucket = fpFeedbackByKey[key];
|
|
629
|
+
if (!bucket || bucket.total === 0) return 1.0;
|
|
630
|
+
|
|
631
|
+
const unhelpfulRate = bucket.unhelpful / bucket.total;
|
|
632
|
+
const helpfulRate = bucket.helpful / bucket.total;
|
|
633
|
+
|
|
634
|
+
if (unhelpfulRate > 0.5) return 0.7;
|
|
635
|
+
if (helpfulRate > 0.8) return 1.2;
|
|
636
|
+
return 1.0;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function getRecommendationPriorityScore(item, outcomeSummaryByKey = {}, fpFeedbackByKey = null) {
|
|
445
640
|
const impactScore = (IMPACT_ORDER[item.impact] ?? 0) * 100;
|
|
446
641
|
const feedbackAdjustment = getRecommendationAdjustment(outcomeSummaryByKey, item.key);
|
|
447
642
|
const brevityPenalty = Math.min((item.fix || '').length, 240) / 20;
|
|
448
|
-
|
|
643
|
+
const raw = impactScore + (feedbackAdjustment * 10) - brevityPenalty;
|
|
644
|
+
return raw * getFpFeedbackMultiplier(fpFeedbackByKey, item.key);
|
|
449
645
|
}
|
|
450
646
|
|
|
451
647
|
function buildTopNextActions(failed, limit = 5, outcomeSummaryByKey = {}, options = {}) {
|
|
452
648
|
const pool = getPrioritizedFailed(failed);
|
|
649
|
+
const fpByKey = options.fpFeedbackByKey || null;
|
|
453
650
|
|
|
454
651
|
return [...pool]
|
|
455
652
|
.sort((a, b) => {
|
|
456
653
|
const scoreB = options.platform === 'codex'
|
|
457
654
|
? codexPriorityScore(b, outcomeSummaryByKey)
|
|
458
|
-
: getRecommendationPriorityScore(b, outcomeSummaryByKey);
|
|
655
|
+
: getRecommendationPriorityScore(b, outcomeSummaryByKey, fpByKey);
|
|
459
656
|
const scoreA = options.platform === 'codex'
|
|
460
657
|
? codexPriorityScore(a, outcomeSummaryByKey)
|
|
461
|
-
: getRecommendationPriorityScore(a, outcomeSummaryByKey);
|
|
658
|
+
: getRecommendationPriorityScore(a, outcomeSummaryByKey, fpByKey);
|
|
462
659
|
return scoreB - scoreA;
|
|
463
660
|
})
|
|
464
661
|
.slice(0, limit)
|
|
@@ -479,7 +676,7 @@ function buildTopNextActions(failed, limit = 5, outcomeSummaryByKey = {}, option
|
|
|
479
676
|
const evidenceClass = options.platform === 'codex' ? codexEvidenceClass(fullItem) : (feedback ? 'measured' : 'estimated');
|
|
480
677
|
const priorityScore = options.platform === 'codex'
|
|
481
678
|
? codexPriorityScore(fullItem, outcomeSummaryByKey)
|
|
482
|
-
: Math.max(0, Math.min(100, Math.round(getRecommendationPriorityScore(fullItem, outcomeSummaryByKey) / 3)));
|
|
679
|
+
: Math.max(0, Math.min(100, Math.round(getRecommendationPriorityScore(fullItem, outcomeSummaryByKey, fpByKey) / 3)));
|
|
483
680
|
|
|
484
681
|
signals.push(`evidence:${evidenceClass}`);
|
|
485
682
|
if (options.platform === 'codex' && CODEX_HARD_FAIL_KEYS.has(key)) {
|
|
@@ -677,12 +874,20 @@ function printLiteAudit(result, dir) {
|
|
|
677
874
|
if (result.platformScopeNote) {
|
|
678
875
|
console.log(colorize(` Scope: ${result.platformScopeNote.message}`, 'dim'));
|
|
679
876
|
}
|
|
877
|
+
if (result.workspaceHint && result.workspaceHint.workspaces.length > 0) {
|
|
878
|
+
console.log(colorize(` Workspaces: ${result.workspaceHint.workspaces.join(', ')}`, 'dim'));
|
|
879
|
+
}
|
|
680
880
|
if (result.platformCaveats && result.platformCaveats.length > 0) {
|
|
681
881
|
console.log(colorize(' Platform caveats:', 'yellow'));
|
|
682
882
|
result.platformCaveats.slice(0, 2).forEach((item) => {
|
|
683
883
|
console.log(colorize(` - ${item.title}: ${item.message}`, 'dim'));
|
|
684
884
|
});
|
|
685
885
|
}
|
|
886
|
+
if (result.largeInstructionFiles && result.largeInstructionFiles.length > 0) {
|
|
887
|
+
result.largeInstructionFiles.slice(0, 2).forEach((item) => {
|
|
888
|
+
console.log(colorize(` Large file: ${item.file} (${Math.round(item.byteCount / 1024)}KB)`, 'yellow'));
|
|
889
|
+
});
|
|
890
|
+
}
|
|
686
891
|
console.log('');
|
|
687
892
|
|
|
688
893
|
if (result.failed === 0) {
|
|
@@ -725,9 +930,13 @@ async function audit(options) {
|
|
|
725
930
|
const spec = getAuditSpec(options.platform || 'claude');
|
|
726
931
|
const silent = options.silent || false;
|
|
727
932
|
const ctx = new spec.ContextClass(options.dir);
|
|
933
|
+
const largeInstructionFiles = inspectInstructionFiles(spec, ctx);
|
|
934
|
+
guardSkippedInstructionFiles(ctx, largeInstructionFiles);
|
|
728
935
|
const stacks = ctx.detectStacks(STACKS);
|
|
729
936
|
const results = [];
|
|
730
937
|
const outcomeSummary = getRecommendationOutcomeSummary(options.dir);
|
|
938
|
+
const fpFeedback = getFeedbackSummary(options.dir);
|
|
939
|
+
const workspaceHint = buildWorkspaceHint(options.dir);
|
|
731
940
|
|
|
732
941
|
// Load and merge plugin checks
|
|
733
942
|
const plugins = loadPlugins(options.dir);
|
|
@@ -749,6 +958,24 @@ async function audit(options) {
|
|
|
749
958
|
});
|
|
750
959
|
}
|
|
751
960
|
|
|
961
|
+
if (largeInstructionFiles.length > 0) {
|
|
962
|
+
results.push({
|
|
963
|
+
key: 'largeInstructionFile',
|
|
964
|
+
id: null,
|
|
965
|
+
name: 'Large instruction file warning',
|
|
966
|
+
category: 'performance',
|
|
967
|
+
impact: 'medium',
|
|
968
|
+
rating: null,
|
|
969
|
+
fix: 'Split oversized instruction files so they stay under 50KB, and keep any single instruction file below 1MB.',
|
|
970
|
+
sourceUrl: null,
|
|
971
|
+
confidence: 'high',
|
|
972
|
+
file: largeInstructionFiles[0].file,
|
|
973
|
+
line: null,
|
|
974
|
+
passed: null,
|
|
975
|
+
details: largeInstructionFiles,
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
|
|
752
979
|
// null = not applicable (skip), true = pass, false = fail
|
|
753
980
|
const applicable = results.filter(r => r.passed !== null);
|
|
754
981
|
const skipped = results.filter(r => r.passed === null);
|
|
@@ -795,10 +1022,27 @@ async function audit(options) {
|
|
|
795
1022
|
const organicEarned = organicPassed.reduce((sum, r) => sum + (WEIGHTS[r.impact] || 5), 0);
|
|
796
1023
|
const organicScore = maxScore > 0 ? Math.round((organicEarned / maxScore) * 100) : 0;
|
|
797
1024
|
const quickWins = getQuickWins(failed, { platform: spec.platform });
|
|
798
|
-
const topNextActions = buildTopNextActions(failed, 5, outcomeSummary.byKey, { platform: spec.platform });
|
|
1025
|
+
const topNextActions = buildTopNextActions(failed, 5, outcomeSummary.byKey, { platform: spec.platform, fpFeedbackByKey: fpFeedback.byKey });
|
|
799
1026
|
const categoryScores = computeCategoryScores(applicable, passed);
|
|
800
1027
|
const platformScopeNote = getPlatformScopeNote(spec, ctx);
|
|
801
1028
|
const platformCaveats = getPlatformCaveats(spec, ctx);
|
|
1029
|
+
const deprecationWarnings = detectDeprecationWarnings(failed, packageVersion);
|
|
1030
|
+
const warnings = [
|
|
1031
|
+
...largeInstructionFiles.map((item) => ({
|
|
1032
|
+
kind: 'large-instruction-file',
|
|
1033
|
+
severity: item.severity,
|
|
1034
|
+
message: item.message,
|
|
1035
|
+
file: item.file,
|
|
1036
|
+
lineCount: item.lineCount,
|
|
1037
|
+
byteCount: item.byteCount,
|
|
1038
|
+
skipped: item.skipped,
|
|
1039
|
+
})),
|
|
1040
|
+
...deprecationWarnings.map((item) => ({
|
|
1041
|
+
kind: 'deprecated-feature',
|
|
1042
|
+
severity: 'warning',
|
|
1043
|
+
...item,
|
|
1044
|
+
})),
|
|
1045
|
+
];
|
|
802
1046
|
const recommendedDomainPacks = spec.platform === 'codex'
|
|
803
1047
|
? detectCodexDomainPacks(ctx, stacks, getCodexDomainPackSignals(ctx))
|
|
804
1048
|
: [];
|
|
@@ -824,6 +1068,10 @@ async function audit(options) {
|
|
|
824
1068
|
totalEntries: outcomeSummary.totalEntries,
|
|
825
1069
|
keysTracked: outcomeSummary.keys,
|
|
826
1070
|
},
|
|
1071
|
+
largeInstructionFiles,
|
|
1072
|
+
deprecationWarnings,
|
|
1073
|
+
warnings,
|
|
1074
|
+
workspaceHint,
|
|
827
1075
|
platformScopeNote,
|
|
828
1076
|
platformCaveats,
|
|
829
1077
|
recommendedDomainPacks,
|
|
@@ -841,9 +1089,8 @@ async function audit(options) {
|
|
|
841
1089
|
}
|
|
842
1090
|
|
|
843
1091
|
if (options.json) {
|
|
844
|
-
const { version } = require('../package.json');
|
|
845
1092
|
console.log(JSON.stringify({
|
|
846
|
-
version,
|
|
1093
|
+
version: packageVersion,
|
|
847
1094
|
timestamp: new Date().toISOString(),
|
|
848
1095
|
...result
|
|
849
1096
|
}, null, 2));
|
|
@@ -855,6 +1102,11 @@ async function audit(options) {
|
|
|
855
1102
|
return result;
|
|
856
1103
|
}
|
|
857
1104
|
|
|
1105
|
+
if (options.format === 'otel') {
|
|
1106
|
+
console.log(JSON.stringify(formatOtelMetrics(result), null, 2));
|
|
1107
|
+
return result;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
858
1110
|
if (options.lite) {
|
|
859
1111
|
printLiteAudit(result, options.dir);
|
|
860
1112
|
sendInsights(result);
|
|
@@ -888,6 +1140,35 @@ async function audit(options) {
|
|
|
888
1140
|
console.log('');
|
|
889
1141
|
}
|
|
890
1142
|
|
|
1143
|
+
if (largeInstructionFiles.length > 0) {
|
|
1144
|
+
console.log(colorize(' Large instruction files', 'yellow'));
|
|
1145
|
+
for (const item of largeInstructionFiles) {
|
|
1146
|
+
const sizeKb = Math.round(item.byteCount / 1024);
|
|
1147
|
+
console.log(colorize(` ${item.file} (${sizeKb}KB, ${item.lineCount || '?'} lines)`, 'bold'));
|
|
1148
|
+
console.log(colorize(` → ${item.message}`, 'dim'));
|
|
1149
|
+
}
|
|
1150
|
+
console.log('');
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
if (deprecationWarnings.length > 0) {
|
|
1154
|
+
console.log(colorize(' Deprecated feature warnings', 'yellow'));
|
|
1155
|
+
for (const item of deprecationWarnings) {
|
|
1156
|
+
console.log(colorize(` ${item.feature}`, 'bold'));
|
|
1157
|
+
console.log(colorize(` → ${item.message}`, 'dim'));
|
|
1158
|
+
console.log(colorize(` Alternative: ${item.alternative}`, 'dim'));
|
|
1159
|
+
}
|
|
1160
|
+
console.log('');
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
if (workspaceHint && !options.workspace) {
|
|
1164
|
+
console.log(colorize(' Monorepo detected', 'blue'));
|
|
1165
|
+
if (workspaceHint.workspaces.length > 0) {
|
|
1166
|
+
console.log(colorize(` Workspaces: ${workspaceHint.workspaces.join(', ')}`, 'dim'));
|
|
1167
|
+
}
|
|
1168
|
+
console.log(colorize(` Tip: ${workspaceHint.suggestedCommand}`, 'dim'));
|
|
1169
|
+
console.log('');
|
|
1170
|
+
}
|
|
1171
|
+
|
|
891
1172
|
if (stacks.length > 0) {
|
|
892
1173
|
console.log(colorize(` Detected: ${stacks.map(s => s.label).join(', ')}`, 'blue'));
|
|
893
1174
|
}
|
|
@@ -1008,4 +1289,4 @@ async function audit(options) {
|
|
|
1008
1289
|
return result;
|
|
1009
1290
|
}
|
|
1010
1291
|
|
|
1011
|
-
module.exports = { audit, buildTopNextActions };
|
|
1292
|
+
module.exports = { audit, buildTopNextActions, getFpFeedbackMultiplier, getRecommendationPriorityScore };
|
package/src/catalog.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
|
+
const { version } = require('../package.json');
|
|
8
9
|
|
|
9
10
|
const { TECHNIQUES: CLAUDE_TECHNIQUES } = require('./techniques');
|
|
10
11
|
const { CODEX_TECHNIQUES } = require('./codex/techniques');
|
|
@@ -31,7 +32,7 @@ const PLATFORM_MAP = {
|
|
|
31
32
|
* Generate a unified catalog array from all platform technique files.
|
|
32
33
|
* Each entry contains:
|
|
33
34
|
* platform, id, key, name, category, impact, rating, fix, sourceUrl,
|
|
34
|
-
* confidence, template, deprecated
|
|
35
|
+
* confidence, lastVerified, template, deprecated
|
|
35
36
|
*/
|
|
36
37
|
function generateCatalog() {
|
|
37
38
|
const catalog = [];
|
|
@@ -62,6 +63,7 @@ function generateCatalog() {
|
|
|
62
63
|
fix: tech.fix ?? null,
|
|
63
64
|
sourceUrl: tech.sourceUrl ?? null,
|
|
64
65
|
confidence: tech.confidence ?? null,
|
|
66
|
+
lastVerified: tech.lastVerified ?? null,
|
|
65
67
|
template: tech.template ?? null,
|
|
66
68
|
deprecated: tech.deprecated ?? false,
|
|
67
69
|
});
|
|
@@ -71,6 +73,20 @@ function generateCatalog() {
|
|
|
71
73
|
return catalog;
|
|
72
74
|
}
|
|
73
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Generate a catalog envelope with version metadata.
|
|
78
|
+
* @returns {{ catalogVersion: string, generatedAt: string, totalChecks: number, checks: Object[] }}
|
|
79
|
+
*/
|
|
80
|
+
function generateCatalogWithVersion() {
|
|
81
|
+
const checks = generateCatalog();
|
|
82
|
+
return {
|
|
83
|
+
catalogVersion: version,
|
|
84
|
+
generatedAt: new Date().toISOString(),
|
|
85
|
+
totalChecks: checks.length,
|
|
86
|
+
checks,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
74
90
|
/**
|
|
75
91
|
* Write the catalog as formatted JSON to the given output path.
|
|
76
92
|
* @param {string} outputPath - Absolute or relative path for the JSON file
|
|
@@ -84,4 +100,4 @@ function writeCatalogJson(outputPath) {
|
|
|
84
100
|
return { path: resolved, count: catalog.length };
|
|
85
101
|
}
|
|
86
102
|
|
|
87
|
-
module.exports = { generateCatalog, writeCatalogJson };
|
|
103
|
+
module.exports = { generateCatalog, generateCatalogWithVersion, writeCatalogJson };
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
const
|
|
1
|
+
const { buildAdditionalDomainPacks, detectAdditionalDomainPacks } = require('../domain-pack-expansion');
|
|
2
|
+
|
|
3
|
+
const BASE_CODEX_DOMAIN_PACKS = [
|
|
2
4
|
{
|
|
3
5
|
key: 'baseline-general',
|
|
4
6
|
label: 'Baseline General',
|
|
@@ -162,6 +164,13 @@ const CODEX_DOMAIN_PACKS = [
|
|
|
162
164
|
},
|
|
163
165
|
];
|
|
164
166
|
|
|
167
|
+
const CODEX_DOMAIN_PACKS = [
|
|
168
|
+
...BASE_CODEX_DOMAIN_PACKS,
|
|
169
|
+
...buildAdditionalDomainPacks('codex', {
|
|
170
|
+
existingKeys: new Set(BASE_CODEX_DOMAIN_PACKS.map((pack) => pack.key)),
|
|
171
|
+
}),
|
|
172
|
+
];
|
|
173
|
+
|
|
165
174
|
function uniqueByKey(items) {
|
|
166
175
|
const seen = new Set();
|
|
167
176
|
const result = [];
|
|
@@ -354,6 +363,19 @@ function detectCodexDomainPacks(ctx, stacks = [], assets = {}) {
|
|
|
354
363
|
]);
|
|
355
364
|
}
|
|
356
365
|
|
|
366
|
+
detectAdditionalDomainPacks({
|
|
367
|
+
ctx,
|
|
368
|
+
pkg,
|
|
369
|
+
deps,
|
|
370
|
+
stackKeys,
|
|
371
|
+
addMatch,
|
|
372
|
+
hasBackend,
|
|
373
|
+
hasFrontend,
|
|
374
|
+
hasInfra,
|
|
375
|
+
hasCi,
|
|
376
|
+
isEnterpriseGoverned,
|
|
377
|
+
});
|
|
378
|
+
|
|
357
379
|
if (matches.length === 0) {
|
|
358
380
|
addMatch('baseline-general', [
|
|
359
381
|
'No stronger platform-specific domain dominated, so a safe general Codex baseline is the best starting point.',
|