@valentia-ai-skills/framework 2.0.6 → 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 +157 -13
- package/bin/cli.js +873 -103
- 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 {
|
|
@@ -1665,22 +2063,33 @@ async function cmdLegacyProjects() {
|
|
|
1665
2063
|
console.log(`\n Console: ${c("dim", p.console_url)}\n`);
|
|
1666
2064
|
|
|
1667
2065
|
} else if (subcommand === "add") {
|
|
1668
|
-
// Download a legacy project
|
|
1669
|
-
//
|
|
2066
|
+
// Download a legacy project from the console and reconstruct the complete
|
|
2067
|
+
// intelligence package folder structure — the exact same layout produced
|
|
2068
|
+
// by the scanner so existing legacy-reading skills can work on it.
|
|
2069
|
+
//
|
|
2070
|
+
// Output:
|
|
2071
|
+
// <outputDir>/<project-slug>/
|
|
2072
|
+
// manifest.json
|
|
2073
|
+
// MASTER_SKILL.md (master skill, if present)
|
|
2074
|
+
// modules/<name>/SKILL.md (module skills)
|
|
2075
|
+
// diagrams/<name>.mmd
|
|
2076
|
+
// OVERVIEW.md / API_REGISTRY.md / RISK_REPORT.md … (reports/guides)
|
|
2077
|
+
|
|
1670
2078
|
const projectName = args[0];
|
|
1671
2079
|
if (!projectName) {
|
|
1672
|
-
console.log(c("red", "Usage: npx ai-skills legacy-projects add <project-name> [--path <dir>]"));
|
|
1673
|
-
console.log(c("dim", " Downloads the project
|
|
2080
|
+
console.log(c("red", "Usage: npx ai-skills legacy-projects add <project-name> [--path <output-dir>]"));
|
|
2081
|
+
console.log(c("dim", " Downloads the project intelligence package and recreates the original folder structure.\n"));
|
|
1674
2082
|
process.exit(1);
|
|
1675
2083
|
}
|
|
1676
2084
|
|
|
1677
|
-
//
|
|
1678
|
-
let
|
|
2085
|
+
// --path defaults to current working directory
|
|
2086
|
+
let outputBase = process.cwd();
|
|
1679
2087
|
for (let i = 1; i < args.length; i++) {
|
|
1680
|
-
if (args[i] === "--path" && args[i + 1])
|
|
2088
|
+
if (args[i] === "--path" && args[i + 1]) outputBase = args[++i];
|
|
1681
2089
|
}
|
|
1682
2090
|
|
|
1683
|
-
console.log(c("blue", `\n━━━
|
|
2091
|
+
console.log(c("blue", `\n━━━ Download Legacy Project: ${projectName} ━━━\n`));
|
|
2092
|
+
|
|
1684
2093
|
let result;
|
|
1685
2094
|
try {
|
|
1686
2095
|
result = await fetchJSONWithAuth(MANAGE_LEGACY_PROJECTS_URL, { action: "download", email, project_name: projectName }, null);
|
|
@@ -1693,80 +2102,141 @@ async function cmdLegacyProjects() {
|
|
|
1693
2102
|
const documents = result.documents || [];
|
|
1694
2103
|
|
|
1695
2104
|
if (documents.length === 0) {
|
|
1696
|
-
console.log(c("yellow",
|
|
2105
|
+
console.log(c("yellow", `No documents found for "${projectName}".\n Check the project name with: npx ai-skills legacy-projects list\n`));
|
|
1697
2106
|
return;
|
|
1698
2107
|
}
|
|
1699
2108
|
|
|
1700
|
-
const skillDocs
|
|
1701
|
-
const reportDocs
|
|
2109
|
+
const skillDocs = documents.filter((d) => d.category === "skill");
|
|
2110
|
+
const reportDocs = documents.filter((d) => d.category === "report");
|
|
2111
|
+
const guideDocs = documents.filter((d) => d.category === "guide");
|
|
1702
2112
|
const diagramDocs = documents.filter((d) => d.category === "diagram");
|
|
1703
2113
|
|
|
1704
|
-
console.log(` Project:
|
|
1705
|
-
console.log(` Status:
|
|
1706
|
-
console.log(` Skills:
|
|
2114
|
+
console.log(` Project: ${c("bold", project.name)}`);
|
|
2115
|
+
console.log(` Status: ${project.status} | ${project.completeness_score}% complete`);
|
|
2116
|
+
console.log(` Skills: ${skillDocs.length} Reports: ${reportDocs.length} Guides: ${guideDocs.length} Diagrams: ${diagramDocs.length}\n`);
|
|
2117
|
+
|
|
2118
|
+
// ── Create project root folder ──
|
|
2119
|
+
const projectSlug = project.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
2120
|
+
const projectDir = path.resolve(outputBase, `${projectSlug}-intelligence`);
|
|
2121
|
+
mkdirp(projectDir);
|
|
2122
|
+
console.log(` Saving to: ${c("bold", projectDir)}\n`);
|
|
2123
|
+
|
|
2124
|
+
// ── Skill documents ──
|
|
2125
|
+
const masterDoc = skillDocs.find((d) => d.document_type === "master");
|
|
2126
|
+
const moduleDocs = skillDocs.filter((d) => d.document_type !== "master").sort((a, b) => a.priority - b.priority);
|
|
2127
|
+
|
|
2128
|
+
if (masterDoc) {
|
|
2129
|
+
fs.writeFileSync(path.join(projectDir, "MASTER_SKILL.md"), masterDoc.content);
|
|
2130
|
+
console.log(` ${c("green", "✓")} MASTER_SKILL.md`);
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
if (moduleDocs.length > 0) {
|
|
2134
|
+
const modulesDir = path.join(projectDir, "modules");
|
|
2135
|
+
mkdirp(modulesDir);
|
|
2136
|
+
for (const doc of moduleDocs) {
|
|
2137
|
+
const safeName = doc.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
2138
|
+
const modDir = path.join(modulesDir, safeName);
|
|
2139
|
+
mkdirp(modDir);
|
|
2140
|
+
fs.writeFileSync(path.join(modDir, "SKILL.md"), doc.content);
|
|
2141
|
+
console.log(` ${c("green", "✓")} modules/${safeName}/SKILL.md`);
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
|
|
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 = [];
|
|
1707
2151
|
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
// Build skill objects in the format installSkillsForTool expects
|
|
1715
|
-
const skills = skillDocs.map((doc) => ({
|
|
1716
|
-
name: `legacy-${doc.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")}`,
|
|
1717
|
-
scope: "global", // global so filterSkillsForProject passes them through
|
|
1718
|
-
content: doc.content,
|
|
1719
|
-
}));
|
|
1720
|
-
|
|
1721
|
-
console.log(` Installing into detected AI tools:\n`);
|
|
1722
|
-
for (const toolKey of tools) {
|
|
1723
|
-
const tool = TOOL_CONFIGS[toolKey];
|
|
1724
|
-
const count = installSkillsForTool(toolKey, skills);
|
|
1725
|
-
console.log(` ${c("green", "✓")} ${tool.name}: ${count} skill(s)`);
|
|
1726
|
-
}
|
|
1727
|
-
console.log("");
|
|
2152
|
+
for (const doc of [...reportDocs, ...guideDocs]) {
|
|
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;
|
|
1728
2158
|
}
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
// ── Optionally save the full raw package to --path ──
|
|
1734
|
-
if (outputPath) {
|
|
1735
|
-
const resolved = path.resolve(outputPath);
|
|
1736
|
-
mkdirp(resolved);
|
|
1737
|
-
|
|
1738
|
-
if (skillDocs.length > 0) {
|
|
1739
|
-
const modulesDir = path.join(resolved, "modules");
|
|
1740
|
-
mkdirp(modulesDir);
|
|
1741
|
-
for (const doc of skillDocs) {
|
|
1742
|
-
const safeName = doc.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
1743
|
-
const docDir = path.join(modulesDir, safeName);
|
|
1744
|
-
mkdirp(docDir);
|
|
1745
|
-
fs.writeFileSync(path.join(docDir, "SKILL.md"), doc.content);
|
|
1746
|
-
}
|
|
2159
|
+
if (!relativePath) {
|
|
2160
|
+
const safeName = doc.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || doc.document_type;
|
|
2161
|
+
relativePath = `reports/${safeName}.md`;
|
|
1747
2162
|
}
|
|
1748
2163
|
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
fs.writeFileSync(path.join(resolved, fileName), doc.content);
|
|
2164
|
+
if (relativePath.includes("/") || relativePath.includes("\\")) {
|
|
2165
|
+
mkdirp(path.dirname(path.join(projectDir, relativePath)));
|
|
1752
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
|
+
}
|
|
2186
|
+
}
|
|
1753
2187
|
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
2188
|
+
// ── Diagram documents ──
|
|
2189
|
+
if (diagramDocs.length > 0) {
|
|
2190
|
+
const diagramsDir = path.join(projectDir, "diagrams");
|
|
2191
|
+
mkdirp(diagramsDir);
|
|
2192
|
+
for (const doc of diagramDocs) {
|
|
2193
|
+
const safeName = doc.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
2194
|
+
const ext = doc.document_type === "mermaid" ? "mmd" : "mmd";
|
|
2195
|
+
const diagramFile = `${safeName}.${ext}`;
|
|
2196
|
+
fs.writeFileSync(path.join(diagramsDir, diagramFile), doc.content);
|
|
2197
|
+
console.log(` ${c("green", "✓")} diagrams/${diagramFile}`);
|
|
1761
2198
|
}
|
|
2199
|
+
}
|
|
1762
2200
|
|
|
1763
|
-
|
|
2201
|
+
// ── Reconstruct manifest.json ──
|
|
2202
|
+
const manifestSkills = [];
|
|
2203
|
+
if (masterDoc) {
|
|
2204
|
+
manifestSkills.push({ name: masterDoc.name, type: "master", file: "MASTER_SKILL.md" });
|
|
1764
2205
|
}
|
|
2206
|
+
for (const doc of moduleDocs) {
|
|
2207
|
+
const safeName = doc.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
2208
|
+
manifestSkills.push({ name: doc.name, type: "module", file: `modules/${safeName}/SKILL.md` });
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
const manifestDiagrams = diagramDocs.map((doc) => {
|
|
2212
|
+
const safeName = doc.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
2213
|
+
return { name: doc.name, file: `diagrams/${safeName}.mmd`, type: doc.document_type };
|
|
2214
|
+
});
|
|
1765
2215
|
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
2216
|
+
const manifest = {
|
|
2217
|
+
project: project.name,
|
|
2218
|
+
scanned_at: project.last_scanned_at || new Date().toISOString(),
|
|
2219
|
+
downloaded_at: new Date().toISOString(),
|
|
2220
|
+
source: "valentia-skills-console",
|
|
2221
|
+
tech_stack: project.tech_stack || {},
|
|
2222
|
+
skills: manifestSkills,
|
|
2223
|
+
diagrams: manifestDiagrams,
|
|
2224
|
+
reports: manifestReports,
|
|
2225
|
+
statistics: {
|
|
2226
|
+
total_modules: moduleDocs.length,
|
|
2227
|
+
total_diagrams: diagramDocs.length,
|
|
2228
|
+
completeness_score: project.completeness_score,
|
|
2229
|
+
},
|
|
2230
|
+
};
|
|
2231
|
+
fs.writeFileSync(path.join(projectDir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
|
|
2232
|
+
console.log(` ${c("green", "✓")} manifest.json`);
|
|
2233
|
+
|
|
2234
|
+
console.log(c("green", `\n✓ "${project.name}" downloaded — ${documents.length} file(s)\n`));
|
|
2235
|
+
console.log(` Folder: ${c("bold", projectDir)}`);
|
|
2236
|
+
if (project.console_url) {
|
|
2237
|
+
console.log(` Console: ${c("dim", project.console_url)}`);
|
|
1769
2238
|
}
|
|
2239
|
+
console.log("");
|
|
1770
2240
|
return;
|
|
1771
2241
|
|
|
1772
2242
|
} else if (subcommand === "remove") {
|
|
@@ -1800,6 +2270,297 @@ async function cmdLegacyProjects() {
|
|
|
1800
2270
|
}
|
|
1801
2271
|
}
|
|
1802
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
|
+
|
|
1803
2564
|
// ── Main ──
|
|
1804
2565
|
|
|
1805
2566
|
const command = process.argv[2] || "setup";
|
|
@@ -1811,6 +2572,8 @@ switch (command) {
|
|
|
1811
2572
|
case "list": cmdList(); break;
|
|
1812
2573
|
case "doctor": cmdDoctor(); break;
|
|
1813
2574
|
case "analyze": cmdAnalyze(); break;
|
|
2575
|
+
case "upload-code-audit": cmdUploadCodeAudit(); break;
|
|
2576
|
+
case "audit-status": cmdAuditStatus(); break;
|
|
1814
2577
|
case "upload-legacy-scan": cmdUploadLegacyScan(); break;
|
|
1815
2578
|
case "legacy-projects": cmdLegacyProjects(); break;
|
|
1816
2579
|
case "help": case "--help": case "-h":
|
|
@@ -1823,9 +2586,11 @@ Usage:
|
|
|
1823
2586
|
npx ai-skills status Show installed toolkit, team, and tools
|
|
1824
2587
|
npx ai-skills list List locally bundled skills
|
|
1825
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
|
|
1826
2591
|
npx ai-skills upload-legacy-scan Upload a legacy codebase intelligence package
|
|
1827
|
-
npx ai-skills legacy-projects add <name> Download project
|
|
1828
|
-
npx ai-skills legacy-projects add <name> --path <dir>
|
|
2592
|
+
npx ai-skills legacy-projects add <name> Download project → recreate intelligence folder locally
|
|
2593
|
+
npx ai-skills legacy-projects add <name> --path <dir> Save to a specific directory (default: cwd)
|
|
1829
2594
|
npx ai-skills legacy-projects list List all legacy projects
|
|
1830
2595
|
npx ai-skills legacy-projects status <name> Show project status and document counts
|
|
1831
2596
|
npx ai-skills legacy-projects remove <name> Delete a project and all its documents
|
|
@@ -1833,6 +2598,9 @@ Usage:
|
|
|
1833
2598
|
|
|
1834
2599
|
Flags:
|
|
1835
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
|
|
1836
2604
|
upload-legacy-scan --path <dir> Path to intelligence folder
|
|
1837
2605
|
upload-legacy-scan --dry-run Validate payload without uploading
|
|
1838
2606
|
upload-legacy-scan --token <tok> Explicit auth token
|
|
@@ -1841,6 +2609,8 @@ Toolkit includes: skills, agents, commands, hooks, rules, MCP configs.
|
|
|
1841
2609
|
|
|
1842
2610
|
Environment:
|
|
1843
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
|
|
1844
2614
|
`); break;
|
|
1845
2615
|
default:
|
|
1846
2616
|
console.log(c("red", `Unknown command: ${command}`));
|