@valentia-ai-skills/framework 2.0.1 → 2.0.3

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
@@ -1217,7 +1217,6 @@ async function cmdAnalyze() {
1217
1217
  console.log(`Analyzing ${c("bold", filesChanged.length)} file(s) against ${c("bold", skillNames.length)} skill(s)...`);
1218
1218
  console.log(c("dim", `Commit: ${commitHash?.slice(0, 8)} — ${commitMessage}`));
1219
1219
  console.log(c("dim", `Branch: ${branch}\n`));
1220
-
1221
1220
  let projectName = null;
1222
1221
  try {
1223
1222
  const pkgPath = path.join(PROJECT_ROOT, "package.json");
@@ -1279,6 +1278,299 @@ async function cmdAnalyze() {
1279
1278
  }
1280
1279
  }
1281
1280
 
1281
+ // ── Upload Legacy Scan Command ──
1282
+
1283
+ const UPLOAD_LEGACY_SCAN_URL =
1284
+ process.env.AI_SKILLS_UPLOAD_LEGACY_URL ||
1285
+ "https://znshdhjquohrzvbnloki.supabase.co/functions/v1/upload-legacy-scan";
1286
+
1287
+ function fetchJSONWithAuth(url, body, token) {
1288
+ return new Promise((resolve, reject) => {
1289
+ const parsed = new URL(url);
1290
+ const mod = parsed.protocol === "https:" ? https : http;
1291
+ const postData = JSON.stringify(body);
1292
+
1293
+ const headers = {
1294
+ "Content-Type": "application/json",
1295
+ "Content-Length": Buffer.byteLength(postData),
1296
+ };
1297
+ if (token) headers["Authorization"] = `Bearer ${token}`;
1298
+
1299
+ const req = mod.request(
1300
+ { hostname: parsed.hostname, port: parsed.port, path: parsed.pathname + parsed.search, method: "POST", headers },
1301
+ (res) => {
1302
+ let data = "";
1303
+ res.on("data", (chunk) => (data += chunk));
1304
+ res.on("end", () => {
1305
+ try {
1306
+ const json = JSON.parse(data);
1307
+ if (res.statusCode >= 400) {
1308
+ reject(new Error(json.error || `HTTP ${res.statusCode}`));
1309
+ } else {
1310
+ resolve(json);
1311
+ }
1312
+ } catch {
1313
+ reject(new Error(`Invalid response: ${data.slice(0, 200)}`));
1314
+ }
1315
+ });
1316
+ }
1317
+ );
1318
+ req.on("error", reject);
1319
+ req.write(postData);
1320
+ req.end();
1321
+ });
1322
+ }
1323
+
1324
+ async function cmdUploadLegacyScan() {
1325
+ console.log(c("blue", "\n━━━ AI Skills Framework — Upload Legacy Scan ━━━\n"));
1326
+
1327
+ const args = process.argv.slice(3);
1328
+ let scanPath = null;
1329
+ let authToken = null;
1330
+ let dryRun = false;
1331
+
1332
+ for (let i = 0; i < args.length; i++) {
1333
+ if (args[i] === "--path" && args[i + 1]) { scanPath = args[++i]; }
1334
+ else if (args[i] === "--token" && args[i + 1]) { authToken = args[++i]; }
1335
+ else if (args[i] === "--dry-run") { dryRun = true; }
1336
+ }
1337
+
1338
+ if (!scanPath) {
1339
+ console.log(c("red", "✗ --path is required."));
1340
+ console.log(c("dim", " Usage: npx ai-skills upload-legacy-scan --path ./my-project-intelligence/\n"));
1341
+ process.exit(1);
1342
+ }
1343
+
1344
+ const resolvedPath = path.resolve(scanPath);
1345
+ if (!fs.existsSync(resolvedPath)) {
1346
+ console.log(c("red", `✗ Path not found: ${resolvedPath}`));
1347
+ process.exit(1);
1348
+ }
1349
+
1350
+ // ── Read and validate manifest.json ──
1351
+ const manifestPath = path.join(resolvedPath, "manifest.json");
1352
+ if (!fs.existsSync(manifestPath)) {
1353
+ console.log(c("red", `✗ manifest.json not found in ${resolvedPath}`));
1354
+ console.log(c("dim", " The intelligence folder must contain a manifest.json file.\n"));
1355
+ process.exit(1);
1356
+ }
1357
+
1358
+ let manifest;
1359
+ try {
1360
+ manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
1361
+ } catch (err) {
1362
+ console.log(c("red", `✗ Failed to parse manifest.json: ${err.message}`));
1363
+ process.exit(1);
1364
+ }
1365
+
1366
+ const missing = ["project", "scanned_at", "skills", "statistics"].filter((k) => !manifest[k]);
1367
+ if (missing.length > 0) {
1368
+ console.log(c("red", `✗ manifest.json missing required fields: ${missing.join(", ")}`));
1369
+ process.exit(1);
1370
+ }
1371
+
1372
+ // ── Normalize manifest fields (handles alternate scanner output formats) ──
1373
+ if (!manifest.statistics) manifest.statistics = {};
1374
+ const mstats = manifest.statistics;
1375
+ // Some scanners output completionPercentage instead of completeness_score
1376
+ if (mstats.completeness_score === undefined) {
1377
+ mstats.completeness_score = mstats.completionPercentage ?? 0;
1378
+ }
1379
+ // Some scanners put module count in source.modules
1380
+ if (mstats.total_modules === undefined) {
1381
+ mstats.total_modules =
1382
+ manifest.source?.modules ??
1383
+ manifest.source?.fileCount ??
1384
+ (manifest.skills?.length ?? 0);
1385
+ }
1386
+ // Build tech_stack from source fields or scalar values in technologies if missing
1387
+ if (!manifest.tech_stack) {
1388
+ if (manifest.source) {
1389
+ const ts = {};
1390
+ if (manifest.source.language) ts.language = manifest.source.language;
1391
+ if (manifest.source.framework) ts.framework = manifest.source.framework;
1392
+ manifest.tech_stack = ts;
1393
+ } else if (manifest.technologies && typeof manifest.technologies === "object" && !Array.isArray(manifest.technologies)) {
1394
+ // Only use scalar leaf values to avoid [object Object] in display
1395
+ const ts = {};
1396
+ for (const [k, v] of Object.entries(manifest.technologies)) {
1397
+ if (typeof v === "string" || typeof v === "number") ts[k] = v;
1398
+ }
1399
+ manifest.tech_stack = ts;
1400
+ } else {
1401
+ manifest.tech_stack = {};
1402
+ }
1403
+ }
1404
+
1405
+ const projectName = manifest.project;
1406
+ console.log(`Project: ${c("bold", projectName)}`);
1407
+ console.log(`Scanned: ${c("dim", manifest.scanned_at)}`);
1408
+ if (manifest.tech_stack) {
1409
+ const stackParts = Object.entries(manifest.tech_stack)
1410
+ .filter(([, v]) => v)
1411
+ .map(([k, v]) => `${k}: ${v}`)
1412
+ .join(", ");
1413
+ if (stackParts) console.log(`Stack: ${c("cyan", stackParts)}`);
1414
+ }
1415
+
1416
+ // ── Read skill files ──
1417
+ const skillPayload = [];
1418
+ for (const skillEntry of (manifest.skills || [])) {
1419
+ const skillFile = skillEntry.file || skillEntry.path; // accept both field names
1420
+ if (!skillEntry.name || !skillFile) continue;
1421
+ const skillFilePath = path.join(resolvedPath, skillFile);
1422
+ if (!fs.existsSync(skillFilePath)) {
1423
+ console.log(c("yellow", ` ⚠ Skill file not found: ${skillFile} — skipping`));
1424
+ continue;
1425
+ }
1426
+ skillPayload.push({
1427
+ name: skillEntry.name,
1428
+ type: skillEntry.type || "module",
1429
+ content: fs.readFileSync(skillFilePath, "utf-8"),
1430
+ });
1431
+ }
1432
+ console.log(`Skills: ${c("green", skillPayload.length.toString())} module(s)`);
1433
+
1434
+ // ── Read diagram files ──
1435
+ const diagramPayload = [];
1436
+ for (const diagramEntry of (manifest.diagrams || [])) {
1437
+ const diagramFile = diagramEntry.file || diagramEntry.path; // accept both field names
1438
+ if (!diagramEntry.name || !diagramFile) continue;
1439
+ const diagramFilePath = path.join(resolvedPath, diagramFile);
1440
+ if (!fs.existsSync(diagramFilePath)) {
1441
+ console.log(c("yellow", ` ⚠ Diagram file not found: ${diagramFile} — skipping`));
1442
+ continue;
1443
+ }
1444
+ diagramPayload.push({
1445
+ name: diagramEntry.name,
1446
+ type: diagramEntry.type || "architecture",
1447
+ content: fs.readFileSync(diagramFilePath, "utf-8"),
1448
+ });
1449
+ }
1450
+ // Auto-discover .mmd/.mermaid files from diagrams/ subdirectory if manifest listed none
1451
+ if (diagramPayload.length === 0) {
1452
+ const diagramsDir = path.join(resolvedPath, "diagrams");
1453
+ if (fs.existsSync(diagramsDir)) {
1454
+ for (const file of fs.readdirSync(diagramsDir).sort()) {
1455
+ if (/\.(mmd|mermaid)$/i.test(file)) {
1456
+ const name = file.replace(/\.(mmd|mermaid)$/i, "");
1457
+ diagramPayload.push({
1458
+ name,
1459
+ type: "architecture",
1460
+ content: fs.readFileSync(path.join(diagramsDir, file), "utf-8"),
1461
+ });
1462
+ }
1463
+ }
1464
+ if (diagramPayload.length > 0) {
1465
+ console.log(c("dim", ` (auto-discovered ${diagramPayload.length} diagram(s) from diagrams/)`))
1466
+ }
1467
+ }
1468
+ }
1469
+ console.log(`Diagrams: ${c("green", diagramPayload.length.toString())} file(s)`);
1470
+
1471
+ // ── Read report files ──
1472
+ const reportsPayload = {};
1473
+ const reportFileMap = {
1474
+ overview: ["OVERVIEW.md", "overview.md"],
1475
+ api_registry: ["API_REGISTRY.md", "api_registry.md"],
1476
+ business_rules: ["BUSINESS_RULES.md", "business_rules.md"],
1477
+ data_models: ["DATA_MODELS.md", "data_models.md"],
1478
+ dependencies: ["DEPENDENCIES.md", "dependencies.md"],
1479
+ env_config: ["ENV_CONFIG.md", "env_config.md"],
1480
+ risk_report: ["RISK_REPORT.md", "risk_report.md"],
1481
+ glossary: ["GLOSSARY.md", "glossary.md"],
1482
+ reproduction_guide: ["REPRODUCTION_GUIDE.md", "reproduction_guide.md"],
1483
+ };
1484
+
1485
+ for (const [key, candidates] of Object.entries(reportFileMap)) {
1486
+ for (const candidate of candidates) {
1487
+ const reportPaths = [
1488
+ path.join(resolvedPath, candidate),
1489
+ path.join(resolvedPath, "reports", candidate),
1490
+ ];
1491
+ const found = reportPaths.find((p) => fs.existsSync(p));
1492
+ if (found) {
1493
+ reportsPayload[key] = fs.readFileSync(found, "utf-8");
1494
+ break;
1495
+ }
1496
+ }
1497
+ }
1498
+
1499
+ const reportCount = Object.keys(reportsPayload).length;
1500
+ console.log(`Reports: ${c("green", reportCount.toString())} file(s)\n`);
1501
+
1502
+ // ── Dry run: print summary and exit ──
1503
+ if (dryRun) {
1504
+ console.log(c("yellow", "Dry run — payload summary:"));
1505
+ const stats = manifest.statistics || {};
1506
+ console.log(` Project name: ${projectName}`);
1507
+ console.log(` Modules: ${skillPayload.length}`);
1508
+ console.log(` Diagrams: ${diagramPayload.length}`);
1509
+ console.log(` Reports: ${reportCount}`);
1510
+ if (stats.completeness_score !== undefined) {
1511
+ console.log(` Completeness: ${stats.completeness_score}%`);
1512
+ }
1513
+ console.log(c("dim", "\nNo data was uploaded. Remove --dry-run to upload.\n"));
1514
+ return;
1515
+ }
1516
+
1517
+ // ── Resolve auth ──
1518
+ let email = null;
1519
+ if (!authToken) {
1520
+ const config = loadConfig();
1521
+ if (config?.email) {
1522
+ email = config.email;
1523
+ console.log(c("dim", `Using saved email: ${email}`));
1524
+ } else {
1525
+ console.log(c("yellow", "No saved config found. Enter your email to authenticate."));
1526
+ email = await ask(`${c("bold", "Work email:")} `);
1527
+ const otpResult = await requestOtpForEmail(email);
1528
+ email = otpResult.email;
1529
+ await verifyOtp(email); // we only need the OTP verification, not the toolkit
1530
+ }
1531
+ }
1532
+
1533
+ // ── Upload ──
1534
+ console.log(c("dim", `Uploading to Valentia (${projectName})...\n`));
1535
+ process.stdout.write(` ${c("dim", "→")} Sending ${skillPayload.length} skills, ${diagramPayload.length} diagrams, ${reportCount} reports...`);
1536
+
1537
+ let result;
1538
+ try {
1539
+ result = await fetchJSONWithAuth(
1540
+ UPLOAD_LEGACY_SCAN_URL,
1541
+ {
1542
+ project_name: projectName,
1543
+ manifest,
1544
+ skills: skillPayload,
1545
+ diagrams: diagramPayload,
1546
+ reports: reportsPayload,
1547
+ email,
1548
+ },
1549
+ authToken
1550
+ );
1551
+ console.log(c("green", " done!\n"));
1552
+ } catch (err) {
1553
+ console.log(c("red", " failed!"));
1554
+ console.log(c("red", `\n✗ Upload failed: ${err.message}`));
1555
+ console.log(c("dim", " Retry with --dry-run to validate your payload without uploading.\n"));
1556
+ process.exit(1);
1557
+ }
1558
+
1559
+ console.log(c("green", "✓ Upload complete!\n"));
1560
+ console.log(` Project ID: ${c("bold", result.project_id)}`);
1561
+ console.log(` Skills created: ${c("bold", (result.skills_created || []).length.toString())}`);
1562
+ if (result.storage_urls?.diagrams?.length) {
1563
+ console.log(` Diagrams stored: ${c("dim", result.storage_urls.diagrams.length.toString())}`);
1564
+ }
1565
+ if (result.storage_urls?.reports?.length) {
1566
+ console.log(` Reports stored: ${c("dim", result.storage_urls.reports.length.toString())}`);
1567
+ }
1568
+ if (result.console_url) {
1569
+ console.log(`\n View in console: ${c("bold", result.console_url)}`);
1570
+ }
1571
+ console.log("");
1572
+ }
1573
+
1282
1574
  // ── Main ──
1283
1575
 
1284
1576
  const command = process.argv[2] || "setup";
@@ -1290,6 +1582,7 @@ switch (command) {
1290
1582
  case "list": cmdList(); break;
1291
1583
  case "doctor": cmdDoctor(); break;
1292
1584
  case "analyze": cmdAnalyze(); break;
1585
+ case "upload-legacy-scan": cmdUploadLegacyScan(); break;
1293
1586
  case "help": case "--help": case "-h":
1294
1587
  console.log(`
1295
1588
  ${c("blue", "AI Skills Framework")} — @valentia-ai-skills/framework
@@ -1300,10 +1593,14 @@ Usage:
1300
1593
  npx ai-skills status Show installed toolkit, team, and tools
1301
1594
  npx ai-skills list List locally bundled skills
1302
1595
  npx ai-skills analyze Analyze last commit against active skills
1596
+ npx ai-skills upload-legacy-scan Upload a legacy codebase intelligence package
1303
1597
  npx ai-skills doctor Health check (config + API + tools)
1304
1598
 
1305
1599
  Flags:
1306
1600
  analyze --last Analyze the most recent commit
1601
+ upload-legacy-scan --path <dir> Path to intelligence folder
1602
+ upload-legacy-scan --dry-run Validate payload without uploading
1603
+ upload-legacy-scan --token <tok> Explicit auth token
1307
1604
 
1308
1605
  Toolkit includes: skills, agents, commands, hooks, rules, MCP configs.
1309
1606
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@valentia-ai-skills/framework",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
4
4
  "description": "AI development skills framework centralized coding standards, security patterns, and SOPs for AI-assisted development. Works with Claude Code, Cursor, Copilot, Windsurf, and any AI coding tool.",
5
5
  "keywords": [
6
6
  "ai-skills",
@@ -3,7 +3,7 @@ name: aisupportapp/project-architecture
3
3
  description: Auto-generated project-architecture for aisupportapp. Created by project-scanner.
4
4
  version: 1.0.0
5
5
  scope: project
6
- last_reviewed: 2026-03-27
6
+ last_reviewed: 2026-03-28
7
7
  ---
8
8
 
9
9
  ---
@@ -3,7 +3,7 @@ name: aisupportapp/project-conventions
3
3
  description: Auto-generated project-conventions for aisupportapp. Created by project-scanner.
4
4
  version: 1.0.0
5
5
  scope: project
6
- last_reviewed: 2026-03-27
6
+ last_reviewed: 2026-03-28
7
7
  ---
8
8
 
9
9
  ---
@@ -3,7 +3,7 @@ name: aisupportapp/project-workflows
3
3
  description: Auto-generated project-workflows for aisupportapp. Created by project-scanner.
4
4
  version: 1.0.0
5
5
  scope: project
6
- last_reviewed: 2026-03-27
6
+ last_reviewed: 2026-03-28
7
7
  ---
8
8
 
9
9
  ---
@@ -3,7 +3,7 @@ name: aisupportapp/test-installation
3
3
  description: No description
4
4
  version: 1.0.0
5
5
  scope: project
6
- last_reviewed: 2026-03-27
6
+ last_reviewed: 2026-03-28
7
7
  ---
8
8
 
9
9
  # Skill Name
@@ -3,7 +3,7 @@ name: api-design
3
3
  description: No description
4
4
  version: 1.0.0
5
5
  scope: global
6
- last_reviewed: 2026-03-27
6
+ last_reviewed: 2026-03-28
7
7
  ---
8
8
 
9
9
  ---
@@ -3,7 +3,7 @@ name: appointment-oas-app
3
3
  description: No description
4
4
  version: 1.0.1
5
5
  scope: global
6
- last_reviewed: 2026-03-27
6
+ last_reviewed: 2026-03-28
7
7
  ---
8
8
 
9
9
  ---
@@ -3,7 +3,7 @@ name: code-standards
3
3
  description: No description
4
4
  version: 1.0.0
5
5
  scope: global
6
- last_reviewed: 2026-03-27
6
+ last_reviewed: 2026-03-28
7
7
  ---
8
8
 
9
9
  ---