@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/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 reportFileMap = {
1486
- overview: ["OVERVIEW.md", "overview.md"],
1487
- api_registry: ["API_REGISTRY.md", "api_registry.md"],
1488
- business_rules: ["BUSINESS_RULES.md", "business_rules.md"],
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
- for (const [key, candidates] of Object.entries(reportFileMap)) {
1498
- for (const candidate of candidates) {
1499
- const reportPaths = [
1500
- path.join(resolvedPath, candidate),
1501
- path.join(resolvedPath, "reports", candidate),
1502
- ];
1503
- const found = reportPaths.find((p) => fs.existsSync(p));
1504
- if (found) {
1505
- const isGuide = GUIDE_TYPES.has(key);
1506
- const displayName = key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
1507
- reportPayload.push({
1508
- category: isGuide ? "guide" : "report",
1509
- document_type: key,
1510
- name: displayName,
1511
- content: fs.readFileSync(found, "utf-8"),
1512
- priority: 0,
1513
- metadata: {},
1514
- });
1515
- break;
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 reportCount = reportPayload.length;
1521
- console.log(`Reports: ${c("green", reportCount.toString())} file(s)\n`);
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/Guides: ${reportCount}`);
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's skills from the console and install them
1669
- // into the developer's local AI tools (Claude Code, Cursor, Copilot, Windsurf).
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's skills from the console and installs them into your AI tools.\n"));
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
- // Optional --path flag to also save raw files to a local directory
1678
- let outputPath = null;
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]) outputPath = args[++i];
2088
+ if (args[i] === "--path" && args[i + 1]) outputBase = args[++i];
1681
2089
  }
1682
2090
 
1683
- console.log(c("blue", `\n━━━ Install Legacy Project: ${projectName} ━━━\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", "No documents found for this project.\n Check the project name or upload a scan first with:\n npx ai-skills upload-legacy-scan --path <dir>\n"));
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 = documents.filter((d) => d.category === "skill");
1701
- const reportDocs = documents.filter((d) => d.category === "report" || d.category === "guide");
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: ${c("bold", project.name)}`);
1705
- console.log(` Status: ${project.status} | ${project.completeness_score}% complete`);
1706
- console.log(` Skills: ${skillDocs.length} Reports: ${reportDocs.length} Diagrams: ${diagramDocs.length}\n`);
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
- // ── Install skill documents into local AI tools ──
1709
- if (skillDocs.length > 0) {
1710
- const tools = detectTools();
1711
- if (tools.length === 0) {
1712
- console.log(c("yellow", " ⚠ No AI tools detected — install Claude Code, Cursor, Copilot, or Windsurf first.\n"));
1713
- } else {
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
- } else {
1730
- console.log(c("dim", " No skills in this project (reports/diagrams only).\n"));
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
- for (const doc of reportDocs) {
1750
- const fileName = doc.document_type.toUpperCase() + ".md";
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
- if (diagramDocs.length > 0) {
1755
- const diagramsDir = path.join(resolved, "diagrams");
1756
- mkdirp(diagramsDir);
1757
- for (const doc of diagramDocs) {
1758
- const safeName = doc.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1759
- fs.writeFileSync(path.join(diagramsDir, `${safeName}.mmd`), doc.content);
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
- console.log(` ${c("green", "✓")} Raw files saved to: ${c("bold", resolved)}\n`);
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
- console.log(c("green", `✓ "${project.name}" is ready. Restart your AI tool to activate the skills.\n`));
1767
- if (result.project?.console_url) {
1768
- console.log(` Console: ${c("dim", result.project.console_url)}\n`);
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 skills install into AI tools
1828
- npx ai-skills legacy-projects add <name> --path <dir> Also save raw files to a local directory
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}`));