@nerviq/cli 1.0.1 → 1.2.1
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 +1 -1
- 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 +3211 -1397
- package/src/audit.js +257 -2
- 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 +3698 -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 +3501 -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 +817 -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 +3648 -1834
- package/src/workspace.js +233 -0
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');
|
|
@@ -27,7 +28,11 @@ const { sendInsights, getLocalInsights } = require('./insights');
|
|
|
27
28
|
const { getRecommendationOutcomeSummary, getRecommendationAdjustment } = require('./activity');
|
|
28
29
|
const { getFeedbackSummary } = require('./feedback');
|
|
29
30
|
const { formatSarif } = require('./formatters/sarif');
|
|
31
|
+
const { formatOtelMetrics } = require('./formatters/otel');
|
|
30
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');
|
|
31
36
|
|
|
32
37
|
const COLORS = {
|
|
33
38
|
reset: '\x1b[0m',
|
|
@@ -58,6 +63,8 @@ function formatLocation(file, line) {
|
|
|
58
63
|
|
|
59
64
|
const IMPACT_ORDER = { critical: 3, high: 2, medium: 1, low: 0 };
|
|
60
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;
|
|
61
68
|
const CATEGORY_MODULES = {
|
|
62
69
|
memory: 'CLAUDE.md',
|
|
63
70
|
quality: 'verification',
|
|
@@ -350,6 +357,171 @@ function getAuditSpec(platform = 'claude') {
|
|
|
350
357
|
};
|
|
351
358
|
}
|
|
352
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
|
+
|
|
353
525
|
function riskFromImpact(impact) {
|
|
354
526
|
if (impact === 'critical') return 'high';
|
|
355
527
|
if (impact === 'high') return 'medium';
|
|
@@ -702,12 +874,20 @@ function printLiteAudit(result, dir) {
|
|
|
702
874
|
if (result.platformScopeNote) {
|
|
703
875
|
console.log(colorize(` Scope: ${result.platformScopeNote.message}`, 'dim'));
|
|
704
876
|
}
|
|
877
|
+
if (result.workspaceHint && result.workspaceHint.workspaces.length > 0) {
|
|
878
|
+
console.log(colorize(` Workspaces: ${result.workspaceHint.workspaces.join(', ')}`, 'dim'));
|
|
879
|
+
}
|
|
705
880
|
if (result.platformCaveats && result.platformCaveats.length > 0) {
|
|
706
881
|
console.log(colorize(' Platform caveats:', 'yellow'));
|
|
707
882
|
result.platformCaveats.slice(0, 2).forEach((item) => {
|
|
708
883
|
console.log(colorize(` - ${item.title}: ${item.message}`, 'dim'));
|
|
709
884
|
});
|
|
710
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
|
+
}
|
|
711
891
|
console.log('');
|
|
712
892
|
|
|
713
893
|
if (result.failed === 0) {
|
|
@@ -750,10 +930,13 @@ async function audit(options) {
|
|
|
750
930
|
const spec = getAuditSpec(options.platform || 'claude');
|
|
751
931
|
const silent = options.silent || false;
|
|
752
932
|
const ctx = new spec.ContextClass(options.dir);
|
|
933
|
+
const largeInstructionFiles = inspectInstructionFiles(spec, ctx);
|
|
934
|
+
guardSkippedInstructionFiles(ctx, largeInstructionFiles);
|
|
753
935
|
const stacks = ctx.detectStacks(STACKS);
|
|
754
936
|
const results = [];
|
|
755
937
|
const outcomeSummary = getRecommendationOutcomeSummary(options.dir);
|
|
756
938
|
const fpFeedback = getFeedbackSummary(options.dir);
|
|
939
|
+
const workspaceHint = buildWorkspaceHint(options.dir);
|
|
757
940
|
|
|
758
941
|
// Load and merge plugin checks
|
|
759
942
|
const plugins = loadPlugins(options.dir);
|
|
@@ -775,6 +958,24 @@ async function audit(options) {
|
|
|
775
958
|
});
|
|
776
959
|
}
|
|
777
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
|
+
|
|
778
979
|
// null = not applicable (skip), true = pass, false = fail
|
|
779
980
|
const applicable = results.filter(r => r.passed !== null);
|
|
780
981
|
const skipped = results.filter(r => r.passed === null);
|
|
@@ -825,6 +1026,23 @@ async function audit(options) {
|
|
|
825
1026
|
const categoryScores = computeCategoryScores(applicable, passed);
|
|
826
1027
|
const platformScopeNote = getPlatformScopeNote(spec, ctx);
|
|
827
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
|
+
];
|
|
828
1046
|
const recommendedDomainPacks = spec.platform === 'codex'
|
|
829
1047
|
? detectCodexDomainPacks(ctx, stacks, getCodexDomainPackSignals(ctx))
|
|
830
1048
|
: [];
|
|
@@ -850,6 +1068,10 @@ async function audit(options) {
|
|
|
850
1068
|
totalEntries: outcomeSummary.totalEntries,
|
|
851
1069
|
keysTracked: outcomeSummary.keys,
|
|
852
1070
|
},
|
|
1071
|
+
largeInstructionFiles,
|
|
1072
|
+
deprecationWarnings,
|
|
1073
|
+
warnings,
|
|
1074
|
+
workspaceHint,
|
|
853
1075
|
platformScopeNote,
|
|
854
1076
|
platformCaveats,
|
|
855
1077
|
recommendedDomainPacks,
|
|
@@ -867,9 +1089,8 @@ async function audit(options) {
|
|
|
867
1089
|
}
|
|
868
1090
|
|
|
869
1091
|
if (options.json) {
|
|
870
|
-
const { version } = require('../package.json');
|
|
871
1092
|
console.log(JSON.stringify({
|
|
872
|
-
version,
|
|
1093
|
+
version: packageVersion,
|
|
873
1094
|
timestamp: new Date().toISOString(),
|
|
874
1095
|
...result
|
|
875
1096
|
}, null, 2));
|
|
@@ -881,6 +1102,11 @@ async function audit(options) {
|
|
|
881
1102
|
return result;
|
|
882
1103
|
}
|
|
883
1104
|
|
|
1105
|
+
if (options.format === 'otel') {
|
|
1106
|
+
console.log(JSON.stringify(formatOtelMetrics(result), null, 2));
|
|
1107
|
+
return result;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
884
1110
|
if (options.lite) {
|
|
885
1111
|
printLiteAudit(result, options.dir);
|
|
886
1112
|
sendInsights(result);
|
|
@@ -914,6 +1140,35 @@ async function audit(options) {
|
|
|
914
1140
|
console.log('');
|
|
915
1141
|
}
|
|
916
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
|
+
|
|
917
1172
|
if (stacks.length > 0) {
|
|
918
1173
|
console.log(colorize(` Detected: ${stacks.map(s => s.label).join(', ')}`, 'blue'));
|
|
919
1174
|
}
|
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.',
|