@valentia-ai-skills/framework 2.0.7 → 2.0.8
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/README.md +150 -6
- package/bin/cli.js +772 -56
- package/package.json +1 -1
- package/skills/global/aisupportapp-project-architecture/SKILL.md +1 -1
- package/skills/global/aisupportapp-project-conventions/SKILL.md +1 -1
- package/skills/global/aisupportapp-project-workflows/SKILL.md +1 -1
- package/skills/global/api-design/SKILL.md +1 -1
- package/skills/global/appointment-oas-app/SKILL.md +1 -1
- package/skills/global/code-quality-auditor/SKILL.md +704 -0
- package/skills/global/code-standards/SKILL.md +1 -1
- package/skills/global/codebase-legacy-intelligence/SKILL.md +1 -1
- package/skills/global/legacy-api-converter/SKILL.md +979 -0
- package/skills/global/legacy-redevelopment-planner/SKILL.md +622 -0
- package/skills/global/observability-integrations/SKILL.md +835 -0
- package/skills/global/project-scanner/SKILL.md +1 -1
- package/skills/global/ui-replication-engine/SKILL.md +591 -0
- package/skills/global/aisupportapp-test-installation/SKILL.md +0 -32
- package/skills/global/viteapp-core-workflows/SKILL.md +0 -32
package/bin/cli.js
CHANGED
|
@@ -17,6 +17,7 @@ const path = require("path");
|
|
|
17
17
|
const readline = require("readline");
|
|
18
18
|
const https = require("https");
|
|
19
19
|
const http = require("http");
|
|
20
|
+
const { pathToFileURL } = require("url");
|
|
20
21
|
|
|
21
22
|
// ── Constants ──
|
|
22
23
|
|
|
@@ -31,6 +32,24 @@ const ANALYZE_FUNCTION_URL =
|
|
|
31
32
|
process.env.AI_SKILLS_ANALYZE_URL || "https://znshdhjquohrzvbnloki.supabase.co/functions/v1/analyze-commit";
|
|
32
33
|
const SCAN_FUNCTION_URL =
|
|
33
34
|
process.env.AI_SKILLS_SCAN_URL || "https://znshdhjquohrzvbnloki.supabase.co/functions/v1/scan-results";
|
|
35
|
+
const UPLOAD_CODE_AUDIT_URL =
|
|
36
|
+
process.env.AI_SKILLS_UPLOAD_CODE_AUDIT_URL ||
|
|
37
|
+
"https://znshdhjquohrzvbnloki.supabase.co/functions/v1/upload-code-audit";
|
|
38
|
+
const MANAGE_CODE_AUDITS_URL =
|
|
39
|
+
process.env.AI_SKILLS_MANAGE_CODE_AUDITS_URL ||
|
|
40
|
+
"https://znshdhjquohrzvbnloki.supabase.co/functions/v1/manage-code-audits";
|
|
41
|
+
|
|
42
|
+
let codeAuditConfigPromise = null;
|
|
43
|
+
|
|
44
|
+
async function loadCodeAuditConfig() {
|
|
45
|
+
if (!codeAuditConfigPromise) {
|
|
46
|
+
const configUrl = pathToFileURL(
|
|
47
|
+
path.join(__dirname, "..", "skills-console", "code-audit-config.js")
|
|
48
|
+
).href;
|
|
49
|
+
codeAuditConfigPromise = import(configUrl);
|
|
50
|
+
}
|
|
51
|
+
return codeAuditConfigPromise;
|
|
52
|
+
}
|
|
34
53
|
|
|
35
54
|
// ── Language Detection ──
|
|
36
55
|
|
|
@@ -1321,6 +1340,320 @@ function fetchJSONWithAuth(url, body, token) {
|
|
|
1321
1340
|
});
|
|
1322
1341
|
}
|
|
1323
1342
|
|
|
1343
|
+
function walkFiles(rootPath) {
|
|
1344
|
+
const files = [];
|
|
1345
|
+
|
|
1346
|
+
function visit(currentPath) {
|
|
1347
|
+
const entries = fs.readdirSync(currentPath, { withFileTypes: true });
|
|
1348
|
+
for (const entry of entries) {
|
|
1349
|
+
const absolutePath = path.join(currentPath, entry.name);
|
|
1350
|
+
if (entry.isDirectory()) {
|
|
1351
|
+
visit(absolutePath);
|
|
1352
|
+
} else if (entry.isFile()) {
|
|
1353
|
+
files.push(absolutePath);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
visit(rootPath);
|
|
1359
|
+
return files.sort();
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
function countLines(content) {
|
|
1363
|
+
if (!content) return 0;
|
|
1364
|
+
return content.split(/\r?\n/).length;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
function coerceNumber(value, fallback = 0) {
|
|
1368
|
+
const number = Number(value);
|
|
1369
|
+
return Number.isFinite(number) ? number : fallback;
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
function normalizeAuditFindingCount(value) {
|
|
1373
|
+
const source = value && typeof value === "object" ? value : {};
|
|
1374
|
+
return {
|
|
1375
|
+
critical: Math.max(0, coerceNumber(source.critical, 0)),
|
|
1376
|
+
high: Math.max(0, coerceNumber(source.high, 0)),
|
|
1377
|
+
medium: Math.max(0, coerceNumber(source.medium, 0)),
|
|
1378
|
+
low: Math.max(0, coerceNumber(source.low, 0)),
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
function normalizeAuditScoreEntry(rawEntry, fallbackWeight, codeAuditConfig) {
|
|
1383
|
+
if (typeof rawEntry === "number") {
|
|
1384
|
+
const score = Math.max(0, Math.min(100, Math.round(rawEntry)));
|
|
1385
|
+
return {
|
|
1386
|
+
score,
|
|
1387
|
+
grade: codeAuditConfig.gradeFromCodeAuditScore(score),
|
|
1388
|
+
weight: fallbackWeight,
|
|
1389
|
+
finding_count: normalizeAuditFindingCount({}),
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
const entry = rawEntry && typeof rawEntry === "object" ? rawEntry : {};
|
|
1394
|
+
const score = Math.max(
|
|
1395
|
+
0,
|
|
1396
|
+
Math.min(
|
|
1397
|
+
100,
|
|
1398
|
+
Math.round(
|
|
1399
|
+
coerceNumber(
|
|
1400
|
+
entry.score ??
|
|
1401
|
+
entry.value ??
|
|
1402
|
+
entry.category_score,
|
|
1403
|
+
0
|
|
1404
|
+
)
|
|
1405
|
+
)
|
|
1406
|
+
)
|
|
1407
|
+
);
|
|
1408
|
+
|
|
1409
|
+
return {
|
|
1410
|
+
score,
|
|
1411
|
+
grade: String(
|
|
1412
|
+
entry.grade ??
|
|
1413
|
+
entry.category_grade ??
|
|
1414
|
+
codeAuditConfig.gradeFromCodeAuditScore(score)
|
|
1415
|
+
).toUpperCase(),
|
|
1416
|
+
weight:
|
|
1417
|
+
entry.weight === undefined || entry.weight === null
|
|
1418
|
+
? fallbackWeight
|
|
1419
|
+
: coerceNumber(entry.weight, fallbackWeight ?? 0),
|
|
1420
|
+
finding_count: normalizeAuditFindingCount(
|
|
1421
|
+
entry.finding_count ??
|
|
1422
|
+
entry.findings ??
|
|
1423
|
+
entry.severity_counts ??
|
|
1424
|
+
{}
|
|
1425
|
+
),
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
async function normalizeAuditManifestForCli(manifest) {
|
|
1430
|
+
const codeAuditConfig = await loadCodeAuditConfig();
|
|
1431
|
+
const summary = manifest.summary && typeof manifest.summary === "object" ? manifest.summary : {};
|
|
1432
|
+
const overall = manifest.overall && typeof manifest.overall === "object" ? manifest.overall : {};
|
|
1433
|
+
const rawScores =
|
|
1434
|
+
(manifest.scores && typeof manifest.scores === "object" ? manifest.scores : null) ||
|
|
1435
|
+
(manifest.category_scores && typeof manifest.category_scores === "object" ? manifest.category_scores : null) ||
|
|
1436
|
+
(manifest.categories && typeof manifest.categories === "object" ? manifest.categories : null) ||
|
|
1437
|
+
(summary.scores && typeof summary.scores === "object" ? summary.scores : null) ||
|
|
1438
|
+
{};
|
|
1439
|
+
|
|
1440
|
+
const scores = {};
|
|
1441
|
+
for (const definition of codeAuditConfig.getCodeAuditDashboardDefinitions()) {
|
|
1442
|
+
if (!definition.scoreKey) continue;
|
|
1443
|
+
scores[definition.scoreKey] = normalizeAuditScoreEntry(
|
|
1444
|
+
rawScores[definition.scoreKey] ?? rawScores[definition.documentType] ?? {},
|
|
1445
|
+
definition.weight ?? null,
|
|
1446
|
+
codeAuditConfig
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
const severitySource =
|
|
1451
|
+
(manifest.findings && typeof manifest.findings === "object" ? manifest.findings : null) ||
|
|
1452
|
+
(manifest.finding_summary && typeof manifest.finding_summary === "object" ? manifest.finding_summary : null) ||
|
|
1453
|
+
(summary.findings && typeof summary.findings === "object" ? summary.findings : null) ||
|
|
1454
|
+
{};
|
|
1455
|
+
const normalizedCounts = normalizeAuditFindingCount(severitySource);
|
|
1456
|
+
|
|
1457
|
+
const overallScore = Math.max(
|
|
1458
|
+
0,
|
|
1459
|
+
Math.min(
|
|
1460
|
+
100,
|
|
1461
|
+
Math.round(
|
|
1462
|
+
coerceNumber(
|
|
1463
|
+
manifest.overall_score ??
|
|
1464
|
+
manifest.overallScore ??
|
|
1465
|
+
overall.score ??
|
|
1466
|
+
summary.overall_score,
|
|
1467
|
+
0
|
|
1468
|
+
)
|
|
1469
|
+
)
|
|
1470
|
+
)
|
|
1471
|
+
);
|
|
1472
|
+
|
|
1473
|
+
const overallGrade = String(
|
|
1474
|
+
manifest.overall_grade ??
|
|
1475
|
+
manifest.overallGrade ??
|
|
1476
|
+
overall.grade ??
|
|
1477
|
+
summary.overall_grade ??
|
|
1478
|
+
codeAuditConfig.gradeFromCodeAuditScore(overallScore)
|
|
1479
|
+
).toUpperCase();
|
|
1480
|
+
|
|
1481
|
+
return {
|
|
1482
|
+
audited_at:
|
|
1483
|
+
manifest.audited_at ??
|
|
1484
|
+
manifest.auditedAt ??
|
|
1485
|
+
manifest.generated_at ??
|
|
1486
|
+
manifest.generatedAt ??
|
|
1487
|
+
summary.audited_at ??
|
|
1488
|
+
new Date().toISOString(),
|
|
1489
|
+
tech_stack:
|
|
1490
|
+
(manifest.tech_stack && typeof manifest.tech_stack === "object" ? manifest.tech_stack : null) ||
|
|
1491
|
+
(manifest.techStack && typeof manifest.techStack === "object" ? manifest.techStack : null) ||
|
|
1492
|
+
(manifest.stack && typeof manifest.stack === "object" ? manifest.stack : null) ||
|
|
1493
|
+
{},
|
|
1494
|
+
overall_score: overallScore,
|
|
1495
|
+
overall_grade: overallGrade,
|
|
1496
|
+
scores,
|
|
1497
|
+
finding_count: normalizedCounts,
|
|
1498
|
+
total_findings:
|
|
1499
|
+
coerceNumber(
|
|
1500
|
+
severitySource.total ??
|
|
1501
|
+
manifest.total_findings ??
|
|
1502
|
+
summary.total_findings,
|
|
1503
|
+
0
|
|
1504
|
+
) ||
|
|
1505
|
+
normalizedCounts.critical +
|
|
1506
|
+
normalizedCounts.high +
|
|
1507
|
+
normalizedCounts.medium +
|
|
1508
|
+
normalizedCounts.low,
|
|
1509
|
+
is_healthcare: Boolean(
|
|
1510
|
+
manifest.is_healthcare ??
|
|
1511
|
+
manifest.isHealthcare ??
|
|
1512
|
+
summary.is_healthcare ??
|
|
1513
|
+
false
|
|
1514
|
+
),
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
function validateAuditManifestForCli(summary, codeAuditConfig) {
|
|
1519
|
+
const missing = [];
|
|
1520
|
+
if (!summary.audited_at) missing.push("audited_at");
|
|
1521
|
+
if (summary.overall_score === undefined || summary.overall_score === null) missing.push("overall_score");
|
|
1522
|
+
if (!summary.overall_grade) missing.push("overall_grade");
|
|
1523
|
+
|
|
1524
|
+
const missingScores = codeAuditConfig
|
|
1525
|
+
.getCodeAuditDashboardDefinitions()
|
|
1526
|
+
.map((definition) => definition.scoreKey)
|
|
1527
|
+
.filter((scoreKey) => scoreKey && !summary.scores[scoreKey]);
|
|
1528
|
+
|
|
1529
|
+
if (missing.length > 0) {
|
|
1530
|
+
return `manifest.json missing required fields: ${missing.join(", ")}`;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
if (missingScores.length > 0) {
|
|
1534
|
+
return `manifest.scores missing required categories: ${missingScores.join(", ")}`;
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
return null;
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
function formatScoreDelta(delta) {
|
|
1541
|
+
if (delta === null || delta === undefined) return "n/a";
|
|
1542
|
+
const numericDelta = Number(delta);
|
|
1543
|
+
if (!Number.isFinite(numericDelta)) return String(delta);
|
|
1544
|
+
return numericDelta > 0 ? `+${numericDelta}` : `${numericDelta}`;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
const BUILTIN_REPORT_SPECS = {
|
|
1548
|
+
overview: {
|
|
1549
|
+
category: "guide",
|
|
1550
|
+
name: "Overview",
|
|
1551
|
+
fileNames: ["OVERVIEW.md", "overview.md"],
|
|
1552
|
+
},
|
|
1553
|
+
api_registry: {
|
|
1554
|
+
category: "report",
|
|
1555
|
+
name: "API Registry",
|
|
1556
|
+
fileNames: ["API_REGISTRY.md", "api_registry.md"],
|
|
1557
|
+
},
|
|
1558
|
+
business_rules: {
|
|
1559
|
+
category: "report",
|
|
1560
|
+
name: "Business Rules",
|
|
1561
|
+
fileNames: ["BUSINESS_RULES.md", "business_rules.md"],
|
|
1562
|
+
},
|
|
1563
|
+
data_models: {
|
|
1564
|
+
category: "report",
|
|
1565
|
+
name: "Data Models",
|
|
1566
|
+
fileNames: ["DATA_MODELS.md", "data_models.md"],
|
|
1567
|
+
},
|
|
1568
|
+
dependencies: {
|
|
1569
|
+
category: "report",
|
|
1570
|
+
name: "Dependencies",
|
|
1571
|
+
fileNames: ["DEPENDENCIES.md", "dependencies.md"],
|
|
1572
|
+
},
|
|
1573
|
+
env_config: {
|
|
1574
|
+
category: "report",
|
|
1575
|
+
name: "Env Config",
|
|
1576
|
+
fileNames: ["ENV_CONFIG.md", "env_config.md"],
|
|
1577
|
+
},
|
|
1578
|
+
risk_report: {
|
|
1579
|
+
category: "report",
|
|
1580
|
+
name: "Risk Report",
|
|
1581
|
+
fileNames: ["RISK_REPORT.md", "risk_report.md"],
|
|
1582
|
+
},
|
|
1583
|
+
glossary: {
|
|
1584
|
+
category: "report",
|
|
1585
|
+
name: "Glossary",
|
|
1586
|
+
fileNames: ["GLOSSARY.md", "glossary.md"],
|
|
1587
|
+
},
|
|
1588
|
+
reproduction_guide: {
|
|
1589
|
+
category: "guide",
|
|
1590
|
+
name: "Reproduction Guide",
|
|
1591
|
+
fileNames: ["REPRODUCTION_GUIDE.md", "reproduction_guide.md"],
|
|
1592
|
+
},
|
|
1593
|
+
};
|
|
1594
|
+
|
|
1595
|
+
function titleizeReportKey(value) {
|
|
1596
|
+
return String(value || "")
|
|
1597
|
+
.replace(/\.[^.]+$/, "")
|
|
1598
|
+
.replace(/[_-]+/g, " ")
|
|
1599
|
+
.replace(/\s+/g, " ")
|
|
1600
|
+
.trim()
|
|
1601
|
+
.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
function normalizeDocumentType(value) {
|
|
1605
|
+
const normalized = String(value || "")
|
|
1606
|
+
.trim()
|
|
1607
|
+
.toLowerCase()
|
|
1608
|
+
.replace(/\.[^.]+$/, "")
|
|
1609
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
1610
|
+
.replace(/^_+|_+$/g, "");
|
|
1611
|
+
return normalized || "report";
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
function toPosixRelative(rootPath, filePath) {
|
|
1615
|
+
return path.relative(rootPath, filePath).split(path.sep).join("/");
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
function findLegacyDocumentPath(rootPath, relativeFile) {
|
|
1619
|
+
if (!relativeFile) return null;
|
|
1620
|
+
const candidates = [path.join(rootPath, relativeFile)];
|
|
1621
|
+
if (!relativeFile.includes("/") && !relativeFile.includes("\\")) {
|
|
1622
|
+
candidates.push(path.join(rootPath, "reports", relativeFile));
|
|
1623
|
+
}
|
|
1624
|
+
return candidates.find((candidate) => fs.existsSync(candidate)) || null;
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
function normalizeManifestReportEntries(manifestReports) {
|
|
1628
|
+
if (!manifestReports) return [];
|
|
1629
|
+
|
|
1630
|
+
if (Array.isArray(manifestReports)) {
|
|
1631
|
+
return manifestReports.map((entry, index) => {
|
|
1632
|
+
if (typeof entry === "string") {
|
|
1633
|
+
return { file: entry, sourceKey: `report_${index + 1}` };
|
|
1634
|
+
}
|
|
1635
|
+
if (entry && typeof entry === "object") {
|
|
1636
|
+
return { ...entry, sourceKey: entry.sourceKey || entry.document_type || entry.name || `report_${index + 1}` };
|
|
1637
|
+
}
|
|
1638
|
+
return null;
|
|
1639
|
+
}).filter(Boolean);
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
if (typeof manifestReports === "object") {
|
|
1643
|
+
return Object.entries(manifestReports).map(([key, value]) => {
|
|
1644
|
+
if (typeof value === "string") {
|
|
1645
|
+
return { file: value, sourceKey: key };
|
|
1646
|
+
}
|
|
1647
|
+
if (value && typeof value === "object") {
|
|
1648
|
+
return { ...value, sourceKey: key };
|
|
1649
|
+
}
|
|
1650
|
+
return null;
|
|
1651
|
+
}).filter(Boolean);
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
return [];
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1324
1657
|
async function cmdUploadLegacyScan() {
|
|
1325
1658
|
console.log(c("blue", "\n━━━ AI Skills Framework — Upload Legacy Scan ━━━\n"));
|
|
1326
1659
|
|
|
@@ -1480,45 +1813,109 @@ async function cmdUploadLegacyScan() {
|
|
|
1480
1813
|
console.log(`Diagrams: ${c("green", diagramPayload.length.toString())} file(s)`);
|
|
1481
1814
|
|
|
1482
1815
|
// ── Read report/guide files ──
|
|
1483
|
-
const GUIDE_TYPES = new Set(["overview", "reproduction_guide"]);
|
|
1484
1816
|
const reportPayload = [];
|
|
1485
|
-
const
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
data_models: ["DATA_MODELS.md", "data_models.md"],
|
|
1490
|
-
dependencies: ["DEPENDENCIES.md", "dependencies.md"],
|
|
1491
|
-
env_config: ["ENV_CONFIG.md", "env_config.md"],
|
|
1492
|
-
risk_report: ["RISK_REPORT.md", "risk_report.md"],
|
|
1493
|
-
glossary: ["GLOSSARY.md", "glossary.md"],
|
|
1494
|
-
reproduction_guide: ["REPRODUCTION_GUIDE.md", "reproduction_guide.md"],
|
|
1495
|
-
};
|
|
1817
|
+
const usedReportPaths = new Set();
|
|
1818
|
+
const usedReportTypes = new Set();
|
|
1819
|
+
const manifestReportEntries = normalizeManifestReportEntries(manifest.reports);
|
|
1820
|
+
let customReportCount = 0;
|
|
1496
1821
|
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1822
|
+
function addReportDocument({ category, documentType, name, absolutePath, relativePath, source }) {
|
|
1823
|
+
if (!absolutePath || usedReportPaths.has(absolutePath) || usedReportTypes.has(documentType)) {
|
|
1824
|
+
return false;
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
reportPayload.push({
|
|
1828
|
+
category,
|
|
1829
|
+
document_type: documentType,
|
|
1830
|
+
name,
|
|
1831
|
+
content: fs.readFileSync(absolutePath, "utf-8"),
|
|
1832
|
+
priority: 0,
|
|
1833
|
+
metadata: {
|
|
1834
|
+
source_path: relativePath,
|
|
1835
|
+
source_origin: source,
|
|
1836
|
+
},
|
|
1837
|
+
});
|
|
1838
|
+
usedReportPaths.add(absolutePath);
|
|
1839
|
+
usedReportTypes.add(documentType);
|
|
1840
|
+
if (!(documentType in BUILTIN_REPORT_SPECS)) {
|
|
1841
|
+
customReportCount += 1;
|
|
1842
|
+
}
|
|
1843
|
+
return true;
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
for (const entry of manifestReportEntries) {
|
|
1847
|
+
const absolutePath = findLegacyDocumentPath(resolvedPath, entry.file || entry.path);
|
|
1848
|
+
if (!absolutePath) {
|
|
1849
|
+
if (entry.file || entry.path) {
|
|
1850
|
+
console.log(c("yellow", ` ⚠ Report file not found: ${entry.file || entry.path} — skipping`));
|
|
1516
1851
|
}
|
|
1852
|
+
continue;
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
const inferredType = normalizeDocumentType(entry.document_type || entry.slug || entry.id || entry.sourceKey || path.basename(absolutePath, path.extname(absolutePath)));
|
|
1856
|
+
const builtinSpec = BUILTIN_REPORT_SPECS[inferredType];
|
|
1857
|
+
const requestedCategory = String(entry.category || entry.type || "").trim().toLowerCase();
|
|
1858
|
+
|
|
1859
|
+
if (!builtinSpec && requestedCategory && requestedCategory !== "report") {
|
|
1860
|
+
console.log(c("yellow", ` ⚠ Custom report "${entry.name || inferredType}" must use type/category "report" — skipping`));
|
|
1861
|
+
continue;
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
addReportDocument({
|
|
1865
|
+
category: builtinSpec?.category || "report",
|
|
1866
|
+
documentType: builtinSpec ? inferredType : inferredType,
|
|
1867
|
+
name: entry.name || builtinSpec?.name || titleizeReportKey(inferredType),
|
|
1868
|
+
absolutePath,
|
|
1869
|
+
relativePath: toPosixRelative(resolvedPath, absolutePath),
|
|
1870
|
+
source: "manifest",
|
|
1871
|
+
});
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
for (const [documentType, spec] of Object.entries(BUILTIN_REPORT_SPECS)) {
|
|
1875
|
+
const found = spec.fileNames
|
|
1876
|
+
.map((candidate) => findLegacyDocumentPath(resolvedPath, candidate))
|
|
1877
|
+
.find(Boolean);
|
|
1878
|
+
if (found) {
|
|
1879
|
+
addReportDocument({
|
|
1880
|
+
category: spec.category,
|
|
1881
|
+
documentType,
|
|
1882
|
+
name: spec.name,
|
|
1883
|
+
absolutePath: found,
|
|
1884
|
+
relativePath: toPosixRelative(resolvedPath, found),
|
|
1885
|
+
source: "builtin",
|
|
1886
|
+
});
|
|
1517
1887
|
}
|
|
1518
1888
|
}
|
|
1519
1889
|
|
|
1520
|
-
const
|
|
1521
|
-
|
|
1890
|
+
const reportsDir = path.join(resolvedPath, "reports");
|
|
1891
|
+
if (fs.existsSync(reportsDir)) {
|
|
1892
|
+
for (const file of fs.readdirSync(reportsDir).sort()) {
|
|
1893
|
+
if (!/\.md$/i.test(file)) continue;
|
|
1894
|
+
const absolutePath = path.join(reportsDir, file);
|
|
1895
|
+
if (usedReportPaths.has(absolutePath)) continue;
|
|
1896
|
+
|
|
1897
|
+
const documentType = normalizeDocumentType(path.basename(file, path.extname(file)));
|
|
1898
|
+
const builtinSpec = BUILTIN_REPORT_SPECS[documentType];
|
|
1899
|
+
|
|
1900
|
+
addReportDocument({
|
|
1901
|
+
category: builtinSpec?.category || "report",
|
|
1902
|
+
documentType,
|
|
1903
|
+
name: builtinSpec?.name || titleizeReportKey(documentType),
|
|
1904
|
+
absolutePath,
|
|
1905
|
+
relativePath: toPosixRelative(resolvedPath, absolutePath),
|
|
1906
|
+
source: "auto",
|
|
1907
|
+
});
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
const reportCount = reportPayload.filter((doc) => doc.category === "report").length;
|
|
1912
|
+
const guideCount = reportPayload.filter((doc) => doc.category === "guide").length;
|
|
1913
|
+
const reportSummary = guideCount > 0 ? `${reportCount} report(s), ${guideCount} guide(s)` : `${reportCount} report(s)`;
|
|
1914
|
+
console.log(`Reports: ${c("green", reportSummary)}`);
|
|
1915
|
+
if (customReportCount > 0) {
|
|
1916
|
+
console.log(c("dim", ` Included ${customReportCount} custom report(s) from manifest entries or reports/.`));
|
|
1917
|
+
}
|
|
1918
|
+
console.log("");
|
|
1522
1919
|
|
|
1523
1920
|
// ── Build unified documents array ──
|
|
1524
1921
|
const documentsPayload = [...skillPayload, ...diagramPayload, ...reportPayload];
|
|
@@ -1531,7 +1928,8 @@ async function cmdUploadLegacyScan() {
|
|
|
1531
1928
|
console.log(` Documents total: ${documentsPayload.length}`);
|
|
1532
1929
|
console.log(` — Skills: ${skillPayload.length}`);
|
|
1533
1930
|
console.log(` — Diagrams: ${diagramPayload.length}`);
|
|
1534
|
-
console.log(` — Reports
|
|
1931
|
+
console.log(` — Reports: ${reportCount}`);
|
|
1932
|
+
console.log(` — Guides: ${guideCount}`);
|
|
1535
1933
|
if (stats.completeness_score !== undefined) {
|
|
1536
1934
|
console.log(` Completeness: ${stats.completeness_score}%`);
|
|
1537
1935
|
}
|
|
@@ -1557,7 +1955,7 @@ async function cmdUploadLegacyScan() {
|
|
|
1557
1955
|
|
|
1558
1956
|
// ── Upload ──
|
|
1559
1957
|
console.log(c("dim", `Uploading to Valentia (${projectName})...\n`));
|
|
1560
|
-
process.stdout.write(` ${c("dim", "→")} Sending ${documentsPayload.length} documents (${skillPayload.length} skills, ${diagramPayload.length} diagrams, ${reportCount} reports)...`);
|
|
1958
|
+
process.stdout.write(` ${c("dim", "→")} Sending ${documentsPayload.length} documents (${skillPayload.length} skills, ${diagramPayload.length} diagrams, ${reportCount} reports, ${guideCount} guides)...`);
|
|
1561
1959
|
|
|
1562
1960
|
let result;
|
|
1563
1961
|
try {
|
|
@@ -1744,23 +2142,47 @@ async function cmdLegacyProjects() {
|
|
|
1744
2142
|
}
|
|
1745
2143
|
}
|
|
1746
2144
|
|
|
1747
|
-
// ── Report & guide documents —
|
|
1748
|
-
const REPORT_FILENAMES =
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
env_config: "ENV_CONFIG.md",
|
|
1754
|
-
risk_report: "RISK_REPORT.md",
|
|
1755
|
-
glossary: "GLOSSARY.md",
|
|
1756
|
-
overview: "OVERVIEW.md",
|
|
1757
|
-
reproduction_guide: "REPRODUCTION_GUIDE.md",
|
|
1758
|
-
};
|
|
2145
|
+
// ── Report & guide documents — preserve built-ins, keep custom reports under reports/ ──
|
|
2146
|
+
const REPORT_FILENAMES = Object.fromEntries(
|
|
2147
|
+
Object.entries(BUILTIN_REPORT_SPECS).map(([documentType, spec]) => [documentType, spec.fileNames[0]])
|
|
2148
|
+
);
|
|
2149
|
+
const customReportsDir = path.join(projectDir, "reports");
|
|
2150
|
+
const manifestReports = [];
|
|
1759
2151
|
|
|
1760
2152
|
for (const doc of [...reportDocs, ...guideDocs]) {
|
|
1761
|
-
const
|
|
1762
|
-
|
|
1763
|
-
|
|
2153
|
+
const metadataPath = doc.metadata?.source_path ? String(doc.metadata.source_path).replace(/^\/+/, "") : "";
|
|
2154
|
+
let relativePath = REPORT_FILENAMES[doc.document_type] || "";
|
|
2155
|
+
|
|
2156
|
+
if (!relativePath && metadataPath) {
|
|
2157
|
+
relativePath = metadataPath;
|
|
2158
|
+
}
|
|
2159
|
+
if (!relativePath) {
|
|
2160
|
+
const safeName = doc.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || doc.document_type;
|
|
2161
|
+
relativePath = `reports/${safeName}.md`;
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
if (relativePath.includes("/") || relativePath.includes("\\")) {
|
|
2165
|
+
mkdirp(path.dirname(path.join(projectDir, relativePath)));
|
|
2166
|
+
}
|
|
2167
|
+
fs.writeFileSync(path.join(projectDir, relativePath), doc.content);
|
|
2168
|
+
console.log(` ${c("green", "✓")} ${relativePath}`);
|
|
2169
|
+
|
|
2170
|
+
if (doc.document_type in BUILTIN_REPORT_SPECS) {
|
|
2171
|
+
manifestReports.push({
|
|
2172
|
+
file: relativePath,
|
|
2173
|
+
document_type: doc.document_type,
|
|
2174
|
+
name: doc.name,
|
|
2175
|
+
type: doc.category,
|
|
2176
|
+
});
|
|
2177
|
+
} else {
|
|
2178
|
+
mkdirp(customReportsDir);
|
|
2179
|
+
manifestReports.push({
|
|
2180
|
+
file: relativePath,
|
|
2181
|
+
document_type: doc.document_type,
|
|
2182
|
+
name: doc.name,
|
|
2183
|
+
type: "report",
|
|
2184
|
+
});
|
|
2185
|
+
}
|
|
1764
2186
|
}
|
|
1765
2187
|
|
|
1766
2188
|
// ── Diagram documents ──
|
|
@@ -1791,12 +2213,6 @@ async function cmdLegacyProjects() {
|
|
|
1791
2213
|
return { name: doc.name, file: `diagrams/${safeName}.mmd`, type: doc.document_type };
|
|
1792
2214
|
});
|
|
1793
2215
|
|
|
1794
|
-
const manifestReports = {};
|
|
1795
|
-
for (const doc of [...reportDocs, ...guideDocs]) {
|
|
1796
|
-
const fileName = REPORT_FILENAMES[doc.document_type] || (doc.document_type.toUpperCase() + ".md");
|
|
1797
|
-
manifestReports[doc.document_type] = fileName;
|
|
1798
|
-
}
|
|
1799
|
-
|
|
1800
2216
|
const manifest = {
|
|
1801
2217
|
project: project.name,
|
|
1802
2218
|
scanned_at: project.last_scanned_at || new Date().toISOString(),
|
|
@@ -1854,6 +2270,297 @@ async function cmdLegacyProjects() {
|
|
|
1854
2270
|
}
|
|
1855
2271
|
}
|
|
1856
2272
|
|
|
2273
|
+
// ── Code Audit Commands ──
|
|
2274
|
+
|
|
2275
|
+
async function cmdUploadCodeAudit() {
|
|
2276
|
+
console.log(c("blue", "\n━━━ AI Skills Framework — Upload Code Audit ━━━\n"));
|
|
2277
|
+
|
|
2278
|
+
const args = process.argv.slice(3);
|
|
2279
|
+
let auditPath = null;
|
|
2280
|
+
let authToken = null;
|
|
2281
|
+
let dryRun = false;
|
|
2282
|
+
|
|
2283
|
+
for (let i = 0; i < args.length; i++) {
|
|
2284
|
+
if (args[i] === "--path" && args[i + 1]) auditPath = args[++i];
|
|
2285
|
+
else if (args[i] === "--token" && args[i + 1]) authToken = args[++i];
|
|
2286
|
+
else if (args[i] === "--dry-run") dryRun = true;
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
if (!auditPath) {
|
|
2290
|
+
console.log(c("red", "✗ --path is required."));
|
|
2291
|
+
console.log(c("dim", " Usage: npx ai-skills upload-code-audit --path ./CodeMatters/\n"));
|
|
2292
|
+
process.exit(1);
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
const resolvedPath = path.resolve(auditPath);
|
|
2296
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
2297
|
+
console.log(c("red", `✗ Path not found: ${resolvedPath}`));
|
|
2298
|
+
process.exit(1);
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
const manifestPath = path.join(resolvedPath, "manifest.json");
|
|
2302
|
+
if (!fs.existsSync(manifestPath)) {
|
|
2303
|
+
console.log(c("red", `✗ manifest.json not found in ${resolvedPath}`));
|
|
2304
|
+
console.log(c("dim", " The CodeMatters folder must contain a manifest.json file.\n"));
|
|
2305
|
+
process.exit(1);
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
const codeAuditConfig = await loadCodeAuditConfig();
|
|
2309
|
+
|
|
2310
|
+
let manifest;
|
|
2311
|
+
try {
|
|
2312
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
2313
|
+
} catch (err) {
|
|
2314
|
+
console.log(c("red", `✗ Failed to parse manifest.json: ${err.message}`));
|
|
2315
|
+
process.exit(1);
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
const manifestSummary = await normalizeAuditManifestForCli(manifest);
|
|
2319
|
+
const manifestError = validateAuditManifestForCli(manifestSummary, codeAuditConfig);
|
|
2320
|
+
if (manifestError) {
|
|
2321
|
+
console.log(c("red", `✗ ${manifestError}`));
|
|
2322
|
+
process.exit(1);
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
const projectName =
|
|
2326
|
+
manifest.project_name ??
|
|
2327
|
+
manifest.project ??
|
|
2328
|
+
manifest.name ??
|
|
2329
|
+
manifest.repository_name;
|
|
2330
|
+
|
|
2331
|
+
if (!projectName || typeof projectName !== "string") {
|
|
2332
|
+
console.log(c("red", "✗ manifest.json missing required field: project_name"));
|
|
2333
|
+
process.exit(1);
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
console.log(`Project: ${c("bold", projectName)}`);
|
|
2337
|
+
console.log(`Audited: ${c("dim", manifestSummary.audited_at)}`);
|
|
2338
|
+
console.log(`Overall: ${c("bold", `${manifestSummary.overall_score}/100 (${manifestSummary.overall_grade})`)}`);
|
|
2339
|
+
if (manifestSummary.tech_stack && Object.keys(manifestSummary.tech_stack).length > 0) {
|
|
2340
|
+
const stackSummary = Object.entries(manifestSummary.tech_stack)
|
|
2341
|
+
.filter(([, value]) => value)
|
|
2342
|
+
.map(([key, value]) => `${key}: ${value}`)
|
|
2343
|
+
.join(", ");
|
|
2344
|
+
console.log(`Stack: ${c("cyan", stackSummary)}`);
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
const allFiles = walkFiles(resolvedPath);
|
|
2348
|
+
const includedFiles = allFiles.filter((absolutePath) => {
|
|
2349
|
+
const relativePath = path.relative(resolvedPath, absolutePath).split(path.sep).join("/");
|
|
2350
|
+
if (relativePath === "manifest.json") return false;
|
|
2351
|
+
return /\.(md|markdown|mmd|mermaid)$/i.test(absolutePath);
|
|
2352
|
+
});
|
|
2353
|
+
|
|
2354
|
+
const documentsPayload = [];
|
|
2355
|
+
for (const absolutePath of includedFiles) {
|
|
2356
|
+
const relativePath = path.relative(resolvedPath, absolutePath).split(path.sep).join("/");
|
|
2357
|
+
const fileName = path.basename(absolutePath);
|
|
2358
|
+
const ext = path.extname(fileName).toLowerCase();
|
|
2359
|
+
const content = fs.readFileSync(absolutePath, "utf-8");
|
|
2360
|
+
const documentType = ext === ".mmd" || ext === ".mermaid"
|
|
2361
|
+
? "diagram"
|
|
2362
|
+
: codeAuditConfig.inferCodeAuditDocumentType(fileName);
|
|
2363
|
+
const definition = codeAuditConfig.getCodeAuditDocumentConfig(documentType);
|
|
2364
|
+
const scoreEntry = definition?.scoreKey ? manifestSummary.scores[definition.scoreKey] : null;
|
|
2365
|
+
|
|
2366
|
+
documentsPayload.push({
|
|
2367
|
+
name: fileName,
|
|
2368
|
+
relative_path: `./${relativePath}`,
|
|
2369
|
+
document_type: documentType,
|
|
2370
|
+
category: ext === ".mmd" || ext === ".mermaid"
|
|
2371
|
+
? "diagram"
|
|
2372
|
+
: definition?.category || codeAuditConfig.inferCodeAuditDocumentCategory(documentType),
|
|
2373
|
+
content,
|
|
2374
|
+
line_count: countLines(content),
|
|
2375
|
+
category_score: scoreEntry?.score ?? null,
|
|
2376
|
+
category_grade: scoreEntry?.grade ?? null,
|
|
2377
|
+
finding_count: scoreEntry?.finding_count ?? null,
|
|
2378
|
+
});
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
const manifestContent = fs.readFileSync(manifestPath, "utf-8");
|
|
2382
|
+
documentsPayload.push({
|
|
2383
|
+
name: "manifest.json",
|
|
2384
|
+
relative_path: "./manifest.json",
|
|
2385
|
+
document_type: "manifest",
|
|
2386
|
+
category: "meta",
|
|
2387
|
+
content: manifestContent,
|
|
2388
|
+
line_count: countLines(manifestContent),
|
|
2389
|
+
category_score: null,
|
|
2390
|
+
category_grade: null,
|
|
2391
|
+
finding_count: null,
|
|
2392
|
+
});
|
|
2393
|
+
|
|
2394
|
+
const overviewCount = documentsPayload.filter((doc) => doc.category === "overview").length;
|
|
2395
|
+
const reportCount = documentsPayload.filter((doc) => doc.category === "audit-report").length;
|
|
2396
|
+
const planCount = documentsPayload.filter((doc) => doc.category === "plan").length;
|
|
2397
|
+
const diagramCount = documentsPayload.filter((doc) => doc.category === "diagram").length;
|
|
2398
|
+
const metaCount = documentsPayload.filter((doc) => doc.category === "meta").length;
|
|
2399
|
+
|
|
2400
|
+
console.log(`Documents: ${c("green", documentsPayload.length.toString())} file(s)`);
|
|
2401
|
+
console.log(c("dim", ` Overview: ${overviewCount} Reports: ${reportCount} Plans: ${planCount} Diagrams: ${diagramCount} Meta: ${metaCount}`));
|
|
2402
|
+
console.log(`Findings: ${c("dim", `critical ${manifestSummary.finding_count.critical}, high ${manifestSummary.finding_count.high}, medium ${manifestSummary.finding_count.medium}, low ${manifestSummary.finding_count.low}`)}`);
|
|
2403
|
+
console.log("");
|
|
2404
|
+
|
|
2405
|
+
if (dryRun) {
|
|
2406
|
+
console.log(c("yellow", "Dry run — payload summary:"));
|
|
2407
|
+
console.log(` Project name: ${projectName}`);
|
|
2408
|
+
console.log(` Audited at: ${manifestSummary.audited_at}`);
|
|
2409
|
+
console.log(` Overall: ${manifestSummary.overall_score}/100 (${manifestSummary.overall_grade})`);
|
|
2410
|
+
console.log(` Documents: ${documentsPayload.length}`);
|
|
2411
|
+
console.log(c("dim", "\nNo data was uploaded. Remove --dry-run to upload.\n"));
|
|
2412
|
+
return;
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
let email = null;
|
|
2416
|
+
if (!authToken) {
|
|
2417
|
+
const config = loadConfig();
|
|
2418
|
+
if (config?.email) {
|
|
2419
|
+
email = config.email;
|
|
2420
|
+
console.log(c("dim", `Using saved email: ${email}`));
|
|
2421
|
+
} else {
|
|
2422
|
+
console.log(c("yellow", "No saved config found. Enter your email to authenticate."));
|
|
2423
|
+
email = await ask(`${c("bold", "Work email:")} `);
|
|
2424
|
+
const otpResult = await requestOtpForEmail(email);
|
|
2425
|
+
email = otpResult.email;
|
|
2426
|
+
await verifyOtp(email);
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
console.log(c("dim", `Uploading audit to Valentia (${projectName})...\n`));
|
|
2431
|
+
process.stdout.write(` ${c("dim", "→")} Sending ${documentsPayload.length} documents...`);
|
|
2432
|
+
|
|
2433
|
+
let result;
|
|
2434
|
+
try {
|
|
2435
|
+
result = await fetchJSONWithAuth(
|
|
2436
|
+
UPLOAD_CODE_AUDIT_URL,
|
|
2437
|
+
{
|
|
2438
|
+
project_name: projectName,
|
|
2439
|
+
manifest,
|
|
2440
|
+
documents: documentsPayload,
|
|
2441
|
+
email,
|
|
2442
|
+
},
|
|
2443
|
+
authToken
|
|
2444
|
+
);
|
|
2445
|
+
console.log(c("green", " done!\n"));
|
|
2446
|
+
} catch (err) {
|
|
2447
|
+
console.log(c("red", " failed!"));
|
|
2448
|
+
console.log(c("red", `\n✗ Upload failed: ${err.message}`));
|
|
2449
|
+
console.log(c("dim", " Retry with --dry-run to validate your payload without uploading.\n"));
|
|
2450
|
+
process.exit(1);
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
console.log(c("green", "✓ Upload complete!\n"));
|
|
2454
|
+
console.log(` Audit ID: ${c("bold", result.audit_id)}`);
|
|
2455
|
+
console.log(` Overall: ${c("bold", `${result.overall_score}/100 (${result.overall_grade})`)}`);
|
|
2456
|
+
console.log(` Documents stored: ${c("bold", String(result.documents_stored || 0))}`);
|
|
2457
|
+
if (result.previous_audit) {
|
|
2458
|
+
console.log(` Previous audit: ${c("dim", `${result.previous_audit.score}/100 (${result.previous_audit.grade || "?"})`)}`);
|
|
2459
|
+
console.log(` Delta: ${c("dim", result.previous_audit.delta || "0")}`);
|
|
2460
|
+
}
|
|
2461
|
+
if ((result.storage_backups_failed || 0) > 0) {
|
|
2462
|
+
console.log(c("yellow", ` Storage backups skipped for ${result.storage_backups_failed} document(s)`));
|
|
2463
|
+
}
|
|
2464
|
+
if (result.console_url) {
|
|
2465
|
+
console.log(`\n View in console: ${c("bold", result.console_url)}`);
|
|
2466
|
+
}
|
|
2467
|
+
console.log("");
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2470
|
+
async function cmdAuditStatus() {
|
|
2471
|
+
const args = process.argv.slice(3);
|
|
2472
|
+
let projectName = null;
|
|
2473
|
+
|
|
2474
|
+
for (let i = 0; i < args.length; i++) {
|
|
2475
|
+
if (args[i] === "--project" && args[i + 1]) projectName = args[++i];
|
|
2476
|
+
else if (!projectName) projectName = args[i];
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
if (!projectName) {
|
|
2480
|
+
console.log(c("red", "Usage: npx ai-skills audit-status --project <project-name>"));
|
|
2481
|
+
process.exit(1);
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
const config = loadConfig();
|
|
2485
|
+
const email = config?.email;
|
|
2486
|
+
if (!email) {
|
|
2487
|
+
console.log(c("yellow", "No saved config. Run 'npx ai-skills setup' first."));
|
|
2488
|
+
process.exit(1);
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
const codeAuditConfig = await loadCodeAuditConfig();
|
|
2492
|
+
|
|
2493
|
+
console.log(c("blue", `\n━━━ Code Audit Status: ${projectName} ━━━\n`));
|
|
2494
|
+
|
|
2495
|
+
let result;
|
|
2496
|
+
try {
|
|
2497
|
+
result = await fetchJSONWithAuth(
|
|
2498
|
+
MANAGE_CODE_AUDITS_URL,
|
|
2499
|
+
{ action: "status", email, project_name: projectName },
|
|
2500
|
+
null
|
|
2501
|
+
);
|
|
2502
|
+
} catch (err) {
|
|
2503
|
+
console.log(c("red", `✗ ${err.message}`));
|
|
2504
|
+
process.exit(1);
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
const audit = result.audit;
|
|
2508
|
+
const overallColor =
|
|
2509
|
+
audit.overall_score >= 90 ? "green" :
|
|
2510
|
+
audit.overall_score >= 75 ? "blue" :
|
|
2511
|
+
audit.overall_score >= 60 ? "yellow" :
|
|
2512
|
+
audit.overall_score >= 40 ? "yellow" :
|
|
2513
|
+
"red";
|
|
2514
|
+
const categories = Array.isArray(result.categories) ? result.categories : [];
|
|
2515
|
+
const categoryByType = Object.fromEntries(categories.map((category) => [category.document_type, category]));
|
|
2516
|
+
const history = Array.isArray(result.history) ? result.history : [];
|
|
2517
|
+
|
|
2518
|
+
console.log(` Project: ${c("bold", result.project_name)}`);
|
|
2519
|
+
console.log(` Overall: ${c(overallColor, `${audit.overall_score}/100 (${audit.overall_grade})`)}`);
|
|
2520
|
+
console.log(` Status: ${audit.status}`);
|
|
2521
|
+
console.log(` Audited: ${new Date(audit.audited_at).toLocaleString()}`);
|
|
2522
|
+
console.log(` Findings: critical ${audit.critical_count} high ${audit.high_count} medium ${audit.medium_count} low ${audit.low_count} total ${audit.total_findings}`);
|
|
2523
|
+
if (audit.score_delta !== null && audit.score_delta !== undefined) {
|
|
2524
|
+
console.log(` Delta: ${formatScoreDelta(audit.score_delta)}`);
|
|
2525
|
+
}
|
|
2526
|
+
if (audit.console_url) {
|
|
2527
|
+
console.log(` Console: ${c("dim", audit.console_url)}`);
|
|
2528
|
+
}
|
|
2529
|
+
console.log("");
|
|
2530
|
+
|
|
2531
|
+
console.log(c("bold", " Category Scores"));
|
|
2532
|
+
for (const definition of codeAuditConfig.getCodeAuditDashboardDefinitions()) {
|
|
2533
|
+
const category = categoryByType[definition.documentType];
|
|
2534
|
+
if (!category) continue;
|
|
2535
|
+
const findingCount = category.finding_count || {};
|
|
2536
|
+
console.log(
|
|
2537
|
+
` ${definition.label.padEnd(16)} ${String(category.score).padStart(3)}/100 ${category.grade || "?"} ` +
|
|
2538
|
+
`${findingCount.critical || 0}c ${findingCount.high || 0}h ${findingCount.medium || 0}m ${findingCount.low || 0}l`
|
|
2539
|
+
);
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
if (history.length > 0) {
|
|
2543
|
+
console.log(`\n${c("bold", " Recent History")}`);
|
|
2544
|
+
for (const entry of history.slice(-5)) {
|
|
2545
|
+
console.log(
|
|
2546
|
+
` ${new Date(entry.audited_at).toLocaleDateString()} ` +
|
|
2547
|
+
`${String(entry.overall_score).padStart(3)}/100 (${entry.overall_grade}) ` +
|
|
2548
|
+
`findings ${entry.total_findings} critical ${entry.critical_count}`
|
|
2549
|
+
);
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
|
|
2553
|
+
if (result.previous_audit) {
|
|
2554
|
+
console.log(`\n${c("bold", " Previous Audit")}`);
|
|
2555
|
+
console.log(
|
|
2556
|
+
` ${new Date(result.previous_audit.audited_at).toLocaleDateString()} ` +
|
|
2557
|
+
`${result.previous_audit.overall_score}/100 (${result.previous_audit.overall_grade})`
|
|
2558
|
+
);
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
console.log("");
|
|
2562
|
+
}
|
|
2563
|
+
|
|
1857
2564
|
// ── Main ──
|
|
1858
2565
|
|
|
1859
2566
|
const command = process.argv[2] || "setup";
|
|
@@ -1865,6 +2572,8 @@ switch (command) {
|
|
|
1865
2572
|
case "list": cmdList(); break;
|
|
1866
2573
|
case "doctor": cmdDoctor(); break;
|
|
1867
2574
|
case "analyze": cmdAnalyze(); break;
|
|
2575
|
+
case "upload-code-audit": cmdUploadCodeAudit(); break;
|
|
2576
|
+
case "audit-status": cmdAuditStatus(); break;
|
|
1868
2577
|
case "upload-legacy-scan": cmdUploadLegacyScan(); break;
|
|
1869
2578
|
case "legacy-projects": cmdLegacyProjects(); break;
|
|
1870
2579
|
case "help": case "--help": case "-h":
|
|
@@ -1877,6 +2586,8 @@ Usage:
|
|
|
1877
2586
|
npx ai-skills status Show installed toolkit, team, and tools
|
|
1878
2587
|
npx ai-skills list List locally bundled skills
|
|
1879
2588
|
npx ai-skills analyze Analyze last commit against active skills
|
|
2589
|
+
npx ai-skills upload-code-audit Upload a CodeMatters audit package
|
|
2590
|
+
npx ai-skills audit-status --project <name> Show latest audit summary for a project
|
|
1880
2591
|
npx ai-skills upload-legacy-scan Upload a legacy codebase intelligence package
|
|
1881
2592
|
npx ai-skills legacy-projects add <name> Download project → recreate intelligence folder locally
|
|
1882
2593
|
npx ai-skills legacy-projects add <name> --path <dir> Save to a specific directory (default: cwd)
|
|
@@ -1887,6 +2598,9 @@ Usage:
|
|
|
1887
2598
|
|
|
1888
2599
|
Flags:
|
|
1889
2600
|
analyze --last Analyze the most recent commit
|
|
2601
|
+
upload-code-audit --path <dir> Path to CodeMatters folder
|
|
2602
|
+
upload-code-audit --dry-run Validate payload without uploading
|
|
2603
|
+
upload-code-audit --token <tok> Explicit auth token
|
|
1890
2604
|
upload-legacy-scan --path <dir> Path to intelligence folder
|
|
1891
2605
|
upload-legacy-scan --dry-run Validate payload without uploading
|
|
1892
2606
|
upload-legacy-scan --token <tok> Explicit auth token
|
|
@@ -1895,6 +2609,8 @@ Toolkit includes: skills, agents, commands, hooks, rules, MCP configs.
|
|
|
1895
2609
|
|
|
1896
2610
|
Environment:
|
|
1897
2611
|
AI_SKILLS_API_URL Override the Supabase Edge Function URL
|
|
2612
|
+
AI_SKILLS_UPLOAD_CODE_AUDIT_URL Override the upload-code-audit Edge Function URL
|
|
2613
|
+
AI_SKILLS_MANAGE_CODE_AUDITS_URL Override the manage-code-audits Edge Function URL
|
|
1898
2614
|
`); break;
|
|
1899
2615
|
default:
|
|
1900
2616
|
console.log(c("red", `Unknown command: ${command}`));
|