@traits-dev/cli 0.4.0 → 0.6.0
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/LICENSE +1 -1
- package/README.md +3 -1
- package/dist/traits.js +390 -19
- package/package.json +2 -2
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @traits-dev/cli
|
|
2
2
|
|
|
3
|
-
CLI for traits.dev voice profile workflows: init, validate, compile, eval, and
|
|
3
|
+
CLI for traits.dev voice profile workflows: init, validate, compile, eval, import, and migrate.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -13,7 +13,9 @@ pnpm add -D @traits-dev/cli
|
|
|
13
13
|
```bash
|
|
14
14
|
traits init --template resolve profiles/resolve.yaml
|
|
15
15
|
traits validate profiles/resolve.yaml
|
|
16
|
+
traits validate profiles/resolve.yaml --format sarif
|
|
16
17
|
traits compile profiles/resolve.yaml --model gpt-4o
|
|
18
|
+
traits migrate profiles/legacy-v1-5.yaml --to v1.6 --normalize-extends
|
|
17
19
|
traits eval profiles/resolve.yaml --tier 1
|
|
18
20
|
```
|
|
19
21
|
|
package/dist/traits.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/bin/traits.ts
|
|
4
|
-
import
|
|
5
|
-
import
|
|
4
|
+
import fs5 from "fs";
|
|
5
|
+
import path7 from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
|
|
8
8
|
// src/commands/compile.ts
|
|
@@ -267,7 +267,7 @@ function printEvalUsage(out = process.stderr) {
|
|
|
267
267
|
"Options:",
|
|
268
268
|
" --model <model> Model target (required)",
|
|
269
269
|
" --tier <1|2|3> Highest tier to run (default: highest available)",
|
|
270
|
-
" --suite <name> Built-in baseline suite: support|healthcare|developer",
|
|
270
|
+
" --suite <name> Built-in baseline suite: support|healthcare|developer|educator|advisor",
|
|
271
271
|
" --provider <name> Judge provider for Tier 3: auto|openai|anthropic",
|
|
272
272
|
" --embedding-model <name> Embedding model for Tier 2 (OpenAI)",
|
|
273
273
|
" --judge-model <name> Judge model for Tier 3 provider",
|
|
@@ -402,10 +402,12 @@ function parseEvalArgs(args) {
|
|
|
402
402
|
if (!["auto", "openai", "anthropic"].includes(result.provider)) {
|
|
403
403
|
return { error: 'Invalid "--provider" value. Expected auto, openai, or anthropic.' };
|
|
404
404
|
}
|
|
405
|
-
if (result.suite != null && !["support", "healthcare", "developer"].includes(
|
|
405
|
+
if (result.suite != null && !["support", "healthcare", "developer", "educator", "advisor"].includes(
|
|
406
406
|
result.suite
|
|
407
407
|
)) {
|
|
408
|
-
return {
|
|
408
|
+
return {
|
|
409
|
+
error: 'Invalid "--suite" value. Expected support, healthcare, developer, educator, or advisor.'
|
|
410
|
+
};
|
|
409
411
|
}
|
|
410
412
|
if (result.suite != null && result.samplesPath != null) {
|
|
411
413
|
return { error: 'Use either "--suite" or "--samples/--scenarios", not both.' };
|
|
@@ -443,7 +445,7 @@ function loadSamples(options, cwd) {
|
|
|
443
445
|
const suite = loadBuiltInEvalSuite(options.suite);
|
|
444
446
|
if (!suite) {
|
|
445
447
|
throw new Error(
|
|
446
|
-
`Unknown suite "${options.suite}". Expected support, healthcare, or
|
|
448
|
+
`Unknown suite "${options.suite}". Expected support, healthcare, developer, educator, or advisor.`
|
|
447
449
|
);
|
|
448
450
|
}
|
|
449
451
|
return suite.scenarios.map((scenario) => ({
|
|
@@ -1446,21 +1448,314 @@ function importHelp(out = process.stdout) {
|
|
|
1446
1448
|
printImportUsage(out);
|
|
1447
1449
|
}
|
|
1448
1450
|
|
|
1449
|
-
// src/commands/
|
|
1451
|
+
// src/commands/migrate.ts
|
|
1452
|
+
import fs4 from "fs";
|
|
1450
1453
|
import path5 from "path";
|
|
1454
|
+
import { loadProfileFile, validateResolvedProfile as validateResolvedProfile2 } from "@traits-dev/core";
|
|
1455
|
+
import { renderImportedProfileYAML, toValidationResultObject as toValidationResultObject4 } from "@traits-dev/core/internal";
|
|
1456
|
+
var SCHEMA_ORDER = {
|
|
1457
|
+
"v1.4": 0,
|
|
1458
|
+
"v1.5": 1,
|
|
1459
|
+
"v1.6": 2
|
|
1460
|
+
};
|
|
1461
|
+
function printMigrateUsage(out = process.stderr) {
|
|
1462
|
+
out.write(
|
|
1463
|
+
[
|
|
1464
|
+
"Usage:",
|
|
1465
|
+
" traits migrate <profile-path> [options]",
|
|
1466
|
+
"",
|
|
1467
|
+
"Options:",
|
|
1468
|
+
" --to <version> Target schema version (default: v1.6; supported: v1.5, v1.6)",
|
|
1469
|
+
" --output <path> Output file path (default: <name>.<target>.yaml)",
|
|
1470
|
+
" --in-place Overwrite the source file (requires --force if file exists)",
|
|
1471
|
+
" --force Overwrite existing destination file",
|
|
1472
|
+
" --normalize-extends Convert single-string extends to array form (v1.6 target only)",
|
|
1473
|
+
" --json Output structured JSON summary",
|
|
1474
|
+
" --verbose Include additional command metadata",
|
|
1475
|
+
" --no-color Disable colorized output",
|
|
1476
|
+
""
|
|
1477
|
+
].join("\n")
|
|
1478
|
+
);
|
|
1479
|
+
}
|
|
1480
|
+
function parseMigrateArgs(args) {
|
|
1481
|
+
const result = {
|
|
1482
|
+
profilePath: null,
|
|
1483
|
+
to: "v1.6",
|
|
1484
|
+
outputPath: null,
|
|
1485
|
+
inPlace: false,
|
|
1486
|
+
force: false,
|
|
1487
|
+
normalizeExtends: false,
|
|
1488
|
+
json: false,
|
|
1489
|
+
verbose: false,
|
|
1490
|
+
noColor: false
|
|
1491
|
+
};
|
|
1492
|
+
const positionals = [];
|
|
1493
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1494
|
+
const arg = args[index];
|
|
1495
|
+
if (arg === "--in-place") {
|
|
1496
|
+
result.inPlace = true;
|
|
1497
|
+
continue;
|
|
1498
|
+
}
|
|
1499
|
+
if (arg === "--force") {
|
|
1500
|
+
result.force = true;
|
|
1501
|
+
continue;
|
|
1502
|
+
}
|
|
1503
|
+
if (arg === "--normalize-extends") {
|
|
1504
|
+
result.normalizeExtends = true;
|
|
1505
|
+
continue;
|
|
1506
|
+
}
|
|
1507
|
+
if (arg === "--json") {
|
|
1508
|
+
result.json = true;
|
|
1509
|
+
continue;
|
|
1510
|
+
}
|
|
1511
|
+
if (arg === "--verbose") {
|
|
1512
|
+
result.verbose = true;
|
|
1513
|
+
continue;
|
|
1514
|
+
}
|
|
1515
|
+
if (arg === "--no-color") {
|
|
1516
|
+
result.noColor = true;
|
|
1517
|
+
continue;
|
|
1518
|
+
}
|
|
1519
|
+
if (arg === "--to" || arg === "--output") {
|
|
1520
|
+
const value = args[index + 1];
|
|
1521
|
+
if (!value) return { error: `Missing value for "${arg}"` };
|
|
1522
|
+
if (arg === "--to") {
|
|
1523
|
+
if (value !== "v1.5" && value !== "v1.6") {
|
|
1524
|
+
return {
|
|
1525
|
+
error: `Unsupported "--to" value "${value}". Currently supported: v1.5, v1.6`
|
|
1526
|
+
};
|
|
1527
|
+
}
|
|
1528
|
+
result.to = value;
|
|
1529
|
+
} else {
|
|
1530
|
+
result.outputPath = value;
|
|
1531
|
+
}
|
|
1532
|
+
index += 1;
|
|
1533
|
+
continue;
|
|
1534
|
+
}
|
|
1535
|
+
if (arg.startsWith("--")) {
|
|
1536
|
+
return { error: `Unknown option "${arg}"` };
|
|
1537
|
+
}
|
|
1538
|
+
positionals.push(arg);
|
|
1539
|
+
}
|
|
1540
|
+
if (positionals.length !== 1) {
|
|
1541
|
+
return { error: "Expected exactly one profile path argument" };
|
|
1542
|
+
}
|
|
1543
|
+
result.profilePath = positionals[0];
|
|
1544
|
+
if (result.inPlace && result.outputPath) {
|
|
1545
|
+
return { error: 'Use either "--in-place" or "--output", not both.' };
|
|
1546
|
+
}
|
|
1547
|
+
if (result.normalizeExtends && result.to !== "v1.6") {
|
|
1548
|
+
return { error: '"--normalize-extends" requires "--to v1.6".' };
|
|
1549
|
+
}
|
|
1550
|
+
return { value: result };
|
|
1551
|
+
}
|
|
1552
|
+
function resolveDefaultOutputPath(sourcePath, toVersion) {
|
|
1553
|
+
const ext = path5.extname(sourcePath) || ".yaml";
|
|
1554
|
+
const base = sourcePath.slice(0, sourcePath.length - ext.length);
|
|
1555
|
+
return `${base}.${toVersion}${ext}`;
|
|
1556
|
+
}
|
|
1557
|
+
function writeFileAtomic2(filePath, contents) {
|
|
1558
|
+
fs4.mkdirSync(path5.dirname(filePath), { recursive: true });
|
|
1559
|
+
const tempFile = path5.join(
|
|
1560
|
+
path5.dirname(filePath),
|
|
1561
|
+
`.${path5.basename(filePath)}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`
|
|
1562
|
+
);
|
|
1563
|
+
fs4.writeFileSync(tempFile, contents, "utf8");
|
|
1564
|
+
fs4.renameSync(tempFile, filePath);
|
|
1565
|
+
}
|
|
1566
|
+
function migrateV14ToV15(profile) {
|
|
1567
|
+
const migrated = JSON.parse(JSON.stringify(profile));
|
|
1568
|
+
migrated.schema = "v1.5";
|
|
1569
|
+
if (!migrated.capabilities) {
|
|
1570
|
+
migrated.capabilities = {
|
|
1571
|
+
tools: [],
|
|
1572
|
+
constraints: [],
|
|
1573
|
+
handoff: {
|
|
1574
|
+
trigger: "Request requires unavailable operations.",
|
|
1575
|
+
action: "Offer handoff to a human operator."
|
|
1576
|
+
}
|
|
1577
|
+
};
|
|
1578
|
+
return { migrated, capabilitiesAdded: true };
|
|
1579
|
+
}
|
|
1580
|
+
return { migrated, capabilitiesAdded: false };
|
|
1581
|
+
}
|
|
1582
|
+
function migrateV15ToV16(profile, options) {
|
|
1583
|
+
const migrated = JSON.parse(JSON.stringify(profile));
|
|
1584
|
+
migrated.schema = "v1.6";
|
|
1585
|
+
if (options.normalizeExtends && typeof migrated.extends === "string") {
|
|
1586
|
+
const normalized = migrated.extends.trim();
|
|
1587
|
+
if (normalized.length > 0) {
|
|
1588
|
+
migrated.extends = [normalized];
|
|
1589
|
+
return { migrated, extendsNormalized: true };
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
return { migrated, extendsNormalized: false };
|
|
1593
|
+
}
|
|
1594
|
+
function asSupportedSourceSchema(value) {
|
|
1595
|
+
if (value === "v1.4" || value === "v1.5") return value;
|
|
1596
|
+
return null;
|
|
1597
|
+
}
|
|
1598
|
+
function runMigrate(args, io = process) {
|
|
1599
|
+
const parsed = parseMigrateArgs(args);
|
|
1600
|
+
if ("error" in parsed) {
|
|
1601
|
+
io.stderr.write(`Error: ${parsed.error}
|
|
1602
|
+
|
|
1603
|
+
`);
|
|
1604
|
+
printMigrateUsage(io.stderr);
|
|
1605
|
+
return 1;
|
|
1606
|
+
}
|
|
1607
|
+
const options = parsed.value;
|
|
1608
|
+
if (!options.profilePath) {
|
|
1609
|
+
io.stderr.write("Error: Missing profile path\n\n");
|
|
1610
|
+
printMigrateUsage(io.stderr);
|
|
1611
|
+
return 1;
|
|
1612
|
+
}
|
|
1613
|
+
const sourcePath = path5.resolve(io.cwd(), options.profilePath);
|
|
1614
|
+
const destinationPath = options.inPlace ? sourcePath : options.outputPath ? path5.resolve(io.cwd(), options.outputPath) : resolveDefaultOutputPath(sourcePath, options.to);
|
|
1615
|
+
if (!fs4.existsSync(sourcePath)) {
|
|
1616
|
+
io.stderr.write(`Error: Source profile does not exist: ${sourcePath}
|
|
1617
|
+
`);
|
|
1618
|
+
return 1;
|
|
1619
|
+
}
|
|
1620
|
+
if (fs4.existsSync(destinationPath) && !options.force) {
|
|
1621
|
+
io.stderr.write(
|
|
1622
|
+
`Error: Destination file already exists: ${destinationPath}. Use --force to overwrite.
|
|
1623
|
+
`
|
|
1624
|
+
);
|
|
1625
|
+
return 1;
|
|
1626
|
+
}
|
|
1627
|
+
let loaded;
|
|
1628
|
+
try {
|
|
1629
|
+
loaded = loadProfileFile(sourcePath);
|
|
1630
|
+
} catch (error) {
|
|
1631
|
+
io.stderr.write(
|
|
1632
|
+
`Error: Failed to read profile: ${error instanceof Error ? error.message : String(error)}
|
|
1633
|
+
`
|
|
1634
|
+
);
|
|
1635
|
+
return 1;
|
|
1636
|
+
}
|
|
1637
|
+
if (loaded.schema === "v1.6") {
|
|
1638
|
+
io.stderr.write(
|
|
1639
|
+
'Error: Source profile is already at "v1.6". Nothing to migrate.\n'
|
|
1640
|
+
);
|
|
1641
|
+
return 1;
|
|
1642
|
+
}
|
|
1643
|
+
const sourceSchema = asSupportedSourceSchema(loaded.schema);
|
|
1644
|
+
if (!sourceSchema) {
|
|
1645
|
+
io.stderr.write(
|
|
1646
|
+
`Error: Migration source schema must be "v1.4" or "v1.5". Found "${loaded.schema ?? "unknown"}".
|
|
1647
|
+
`
|
|
1648
|
+
);
|
|
1649
|
+
return 1;
|
|
1650
|
+
}
|
|
1651
|
+
if (SCHEMA_ORDER[options.to] < SCHEMA_ORDER[sourceSchema]) {
|
|
1652
|
+
io.stderr.write(
|
|
1653
|
+
`Error: Downgrade is not supported (source "${sourceSchema}" -> target "${options.to}").
|
|
1654
|
+
`
|
|
1655
|
+
);
|
|
1656
|
+
return 1;
|
|
1657
|
+
}
|
|
1658
|
+
if (SCHEMA_ORDER[options.to] === SCHEMA_ORDER[sourceSchema]) {
|
|
1659
|
+
io.stderr.write(`Error: Source profile already uses target schema "${options.to}".
|
|
1660
|
+
`);
|
|
1661
|
+
return 1;
|
|
1662
|
+
}
|
|
1663
|
+
let migrated = loaded;
|
|
1664
|
+
let capabilitiesAdded = false;
|
|
1665
|
+
let extendsNormalized = false;
|
|
1666
|
+
let currentSchema = sourceSchema;
|
|
1667
|
+
if (currentSchema === "v1.4") {
|
|
1668
|
+
const step = migrateV14ToV15(migrated);
|
|
1669
|
+
migrated = step.migrated;
|
|
1670
|
+
capabilitiesAdded = step.capabilitiesAdded;
|
|
1671
|
+
currentSchema = "v1.5";
|
|
1672
|
+
}
|
|
1673
|
+
if (options.to === "v1.6" && currentSchema === "v1.5") {
|
|
1674
|
+
const step = migrateV15ToV16(migrated, {
|
|
1675
|
+
normalizeExtends: options.normalizeExtends
|
|
1676
|
+
});
|
|
1677
|
+
migrated = step.migrated;
|
|
1678
|
+
extendsNormalized = step.extendsNormalized;
|
|
1679
|
+
currentSchema = "v1.6";
|
|
1680
|
+
}
|
|
1681
|
+
if (currentSchema !== options.to) {
|
|
1682
|
+
io.stderr.write(
|
|
1683
|
+
`Error: Failed to migrate profile to target schema "${options.to}". Final schema: "${currentSchema}".
|
|
1684
|
+
`
|
|
1685
|
+
);
|
|
1686
|
+
return 2;
|
|
1687
|
+
}
|
|
1688
|
+
const validation = validateResolvedProfile2(migrated, {
|
|
1689
|
+
strict: false
|
|
1690
|
+
});
|
|
1691
|
+
if (validation.errors.length > 0) {
|
|
1692
|
+
io.stderr.write(
|
|
1693
|
+
`Error: Migrated profile is invalid and was not written (${validation.errors.length} error(s)).
|
|
1694
|
+
`
|
|
1695
|
+
);
|
|
1696
|
+
return 2;
|
|
1697
|
+
}
|
|
1698
|
+
const yaml = renderImportedProfileYAML(migrated);
|
|
1699
|
+
writeFileAtomic2(destinationPath, yaml);
|
|
1700
|
+
if (options.json) {
|
|
1701
|
+
io.stdout.write(
|
|
1702
|
+
`${JSON.stringify(
|
|
1703
|
+
{
|
|
1704
|
+
migrated: true,
|
|
1705
|
+
sourcePath,
|
|
1706
|
+
outputPath: destinationPath,
|
|
1707
|
+
fromSchema: sourceSchema,
|
|
1708
|
+
toSchema: options.to,
|
|
1709
|
+
capabilitiesAdded,
|
|
1710
|
+
extendsNormalized,
|
|
1711
|
+
validation: toValidationResultObject4(validation)
|
|
1712
|
+
},
|
|
1713
|
+
null,
|
|
1714
|
+
2
|
|
1715
|
+
)}
|
|
1716
|
+
`
|
|
1717
|
+
);
|
|
1718
|
+
return 0;
|
|
1719
|
+
}
|
|
1720
|
+
if (options.verbose) {
|
|
1721
|
+
io.stdout.write(`Source: ${sourcePath}
|
|
1722
|
+
`);
|
|
1723
|
+
io.stdout.write(`Output: ${destinationPath}
|
|
1724
|
+
`);
|
|
1725
|
+
io.stdout.write(`Capabilities added: ${capabilitiesAdded ? "yes" : "no"}
|
|
1726
|
+
`);
|
|
1727
|
+
io.stdout.write(`Extends normalized: ${extendsNormalized ? "yes" : "no"}
|
|
1728
|
+
`);
|
|
1729
|
+
io.stdout.write(`Validation warnings: ${validation.warnings.length}
|
|
1730
|
+
|
|
1731
|
+
`);
|
|
1732
|
+
}
|
|
1733
|
+
io.stdout.write(`Migrated profile schema ${sourceSchema} -> ${options.to}
|
|
1734
|
+
`);
|
|
1735
|
+
io.stdout.write(`Wrote: ${destinationPath}
|
|
1736
|
+
`);
|
|
1737
|
+
return 0;
|
|
1738
|
+
}
|
|
1739
|
+
function migrateHelp(out = process.stdout) {
|
|
1740
|
+
printMigrateUsage(out);
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
// src/commands/validate.ts
|
|
1744
|
+
import path6 from "path";
|
|
1451
1745
|
import { validateProfile as validateProfile2 } from "@traits-dev/core";
|
|
1452
1746
|
import {
|
|
1453
1747
|
formatValidationResult as formatValidationResult4,
|
|
1454
|
-
toValidationResultObject as
|
|
1748
|
+
toValidationResultObject as toValidationResultObject5
|
|
1455
1749
|
} from "@traits-dev/core/internal";
|
|
1456
1750
|
function printValidateUsage(out = process.stderr) {
|
|
1457
1751
|
out.write(
|
|
1458
1752
|
[
|
|
1459
1753
|
"Usage:",
|
|
1460
|
-
" traits validate <profile-path> [
|
|
1754
|
+
" traits validate <profile-path> [options]",
|
|
1461
1755
|
"",
|
|
1462
1756
|
"Options:",
|
|
1463
1757
|
" --json Output structured JSON",
|
|
1758
|
+
" --format <text|json|sarif> Output format (default: text)",
|
|
1464
1759
|
" --strict Promote warnings to errors",
|
|
1465
1760
|
" --verbose Include additional command metadata",
|
|
1466
1761
|
" --no-color Disable colorized output",
|
|
@@ -1474,6 +1769,7 @@ function parseValidateArgs(args) {
|
|
|
1474
1769
|
profilePath: null,
|
|
1475
1770
|
strict: false,
|
|
1476
1771
|
json: false,
|
|
1772
|
+
format: "text",
|
|
1477
1773
|
verbose: false,
|
|
1478
1774
|
noColor: false,
|
|
1479
1775
|
bundledProfilesDir: null
|
|
@@ -1487,6 +1783,7 @@ function parseValidateArgs(args) {
|
|
|
1487
1783
|
}
|
|
1488
1784
|
if (arg === "--json") {
|
|
1489
1785
|
result.json = true;
|
|
1786
|
+
result.format = "json";
|
|
1490
1787
|
continue;
|
|
1491
1788
|
}
|
|
1492
1789
|
if (arg === "--verbose") {
|
|
@@ -1506,6 +1803,20 @@ function parseValidateArgs(args) {
|
|
|
1506
1803
|
index += 1;
|
|
1507
1804
|
continue;
|
|
1508
1805
|
}
|
|
1806
|
+
if (arg === "--format") {
|
|
1807
|
+
const nextValue = args[index + 1];
|
|
1808
|
+
if (!nextValue) {
|
|
1809
|
+
return { error: 'Missing value for "--format"' };
|
|
1810
|
+
}
|
|
1811
|
+
const normalized = String(nextValue).toLowerCase();
|
|
1812
|
+
if (normalized !== "text" && normalized !== "json" && normalized !== "sarif") {
|
|
1813
|
+
return { error: 'Invalid "--format" value. Expected text, json, or sarif.' };
|
|
1814
|
+
}
|
|
1815
|
+
result.format = normalized;
|
|
1816
|
+
result.json = normalized === "json";
|
|
1817
|
+
index += 1;
|
|
1818
|
+
continue;
|
|
1819
|
+
}
|
|
1509
1820
|
if (arg.startsWith("--")) {
|
|
1510
1821
|
return { error: `Unknown option "${arg}"` };
|
|
1511
1822
|
}
|
|
@@ -1517,6 +1828,54 @@ function parseValidateArgs(args) {
|
|
|
1517
1828
|
result.profilePath = positionals[0];
|
|
1518
1829
|
return { value: result };
|
|
1519
1830
|
}
|
|
1831
|
+
function toRelativePath(cwd, filePath) {
|
|
1832
|
+
const relative = path6.relative(cwd, filePath);
|
|
1833
|
+
if (relative && !relative.startsWith("..")) return relative;
|
|
1834
|
+
return filePath;
|
|
1835
|
+
}
|
|
1836
|
+
function buildSarifReport(validation, profilePath, cwd) {
|
|
1837
|
+
const diagnostics = [...validation.errors, ...validation.warnings];
|
|
1838
|
+
const uniqueRuleIds = [...new Set(diagnostics.map((diagnostic) => diagnostic.code))];
|
|
1839
|
+
const artifactUri = toRelativePath(cwd, profilePath);
|
|
1840
|
+
const rules = uniqueRuleIds.map((ruleId) => ({
|
|
1841
|
+
id: ruleId,
|
|
1842
|
+
shortDescription: {
|
|
1843
|
+
text: `traits.dev ${ruleId}`
|
|
1844
|
+
}
|
|
1845
|
+
}));
|
|
1846
|
+
const results = diagnostics.map((diagnostic) => ({
|
|
1847
|
+
ruleId: diagnostic.code,
|
|
1848
|
+
level: diagnostic.severity === "error" ? "error" : "warning",
|
|
1849
|
+
message: {
|
|
1850
|
+
text: diagnostic.message
|
|
1851
|
+
},
|
|
1852
|
+
locations: [
|
|
1853
|
+
{
|
|
1854
|
+
physicalLocation: {
|
|
1855
|
+
artifactLocation: {
|
|
1856
|
+
uri: artifactUri
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
]
|
|
1861
|
+
}));
|
|
1862
|
+
return {
|
|
1863
|
+
version: "2.1.0",
|
|
1864
|
+
$schema: "https://json.schemastore.org/sarif-2.1.0.json",
|
|
1865
|
+
runs: [
|
|
1866
|
+
{
|
|
1867
|
+
tool: {
|
|
1868
|
+
driver: {
|
|
1869
|
+
name: "traits.dev",
|
|
1870
|
+
informationUri: "https://github.com/justinhambleton/traits",
|
|
1871
|
+
rules
|
|
1872
|
+
}
|
|
1873
|
+
},
|
|
1874
|
+
results
|
|
1875
|
+
}
|
|
1876
|
+
]
|
|
1877
|
+
};
|
|
1878
|
+
}
|
|
1520
1879
|
function runValidate(args, io = process) {
|
|
1521
1880
|
const parsed = parseValidateArgs(args);
|
|
1522
1881
|
if ("error" in parsed) {
|
|
@@ -1532,14 +1891,18 @@ function runValidate(args, io = process) {
|
|
|
1532
1891
|
printValidateUsage(io.stderr);
|
|
1533
1892
|
return 1;
|
|
1534
1893
|
}
|
|
1535
|
-
const bundledProfilesDir = options.bundledProfilesDir ?
|
|
1536
|
-
const profilePath =
|
|
1894
|
+
const bundledProfilesDir = options.bundledProfilesDir ? path6.resolve(io.cwd(), options.bundledProfilesDir) : path6.resolve(io.cwd(), "profiles");
|
|
1895
|
+
const profilePath = path6.resolve(io.cwd(), options.profilePath);
|
|
1537
1896
|
const result = validateProfile2(profilePath, {
|
|
1538
1897
|
strict: options.strict,
|
|
1539
1898
|
bundledProfilesDir
|
|
1540
1899
|
});
|
|
1541
|
-
if (options.json) {
|
|
1542
|
-
io.stdout.write(`${JSON.stringify(
|
|
1900
|
+
if (options.format === "json") {
|
|
1901
|
+
io.stdout.write(`${JSON.stringify(toValidationResultObject5(result), null, 2)}
|
|
1902
|
+
`);
|
|
1903
|
+
} else if (options.format === "sarif") {
|
|
1904
|
+
const sarif = buildSarifReport(result, profilePath, io.cwd());
|
|
1905
|
+
io.stdout.write(`${JSON.stringify(sarif, null, 2)}
|
|
1543
1906
|
`);
|
|
1544
1907
|
} else {
|
|
1545
1908
|
if (options.verbose) {
|
|
@@ -1561,14 +1924,14 @@ function validateHelp(out = process.stdout) {
|
|
|
1561
1924
|
}
|
|
1562
1925
|
|
|
1563
1926
|
// src/bin/traits.ts
|
|
1564
|
-
var CLI_DIR =
|
|
1927
|
+
var CLI_DIR = path7.dirname(fileURLToPath(import.meta.url));
|
|
1565
1928
|
var PACKAGE_JSON_CANDIDATES = [
|
|
1566
|
-
|
|
1567
|
-
|
|
1929
|
+
path7.resolve(CLI_DIR, "../package.json"),
|
|
1930
|
+
path7.resolve(CLI_DIR, "../../package.json")
|
|
1568
1931
|
];
|
|
1569
1932
|
function resolvePackageJsonPath() {
|
|
1570
1933
|
for (const candidate of PACKAGE_JSON_CANDIDATES) {
|
|
1571
|
-
if (
|
|
1934
|
+
if (fs5.existsSync(candidate)) return candidate;
|
|
1572
1935
|
}
|
|
1573
1936
|
return PACKAGE_JSON_CANDIDATES[0];
|
|
1574
1937
|
}
|
|
@@ -1586,6 +1949,7 @@ function printRootUsage(out = process.stdout) {
|
|
|
1586
1949
|
" compile <profile-path> Compile a profile for a target model",
|
|
1587
1950
|
" eval <profile-path> Evaluate profile responses (Tier 1 scaffold)",
|
|
1588
1951
|
" import [prompt-path] Import a profile from an existing system prompt",
|
|
1952
|
+
" migrate <profile-path> Migrate profile schema (up to v1.6)",
|
|
1589
1953
|
" validate <profile-path> Validate a voice profile",
|
|
1590
1954
|
"",
|
|
1591
1955
|
"Global flags:",
|
|
@@ -1605,7 +1969,7 @@ function printRootUsage(out = process.stdout) {
|
|
|
1605
1969
|
}
|
|
1606
1970
|
function readCliVersion() {
|
|
1607
1971
|
try {
|
|
1608
|
-
const raw =
|
|
1972
|
+
const raw = fs5.readFileSync(PACKAGE_JSON_PATH, "utf8");
|
|
1609
1973
|
const pkg = JSON.parse(raw);
|
|
1610
1974
|
return String(pkg.version ?? "0.0.0");
|
|
1611
1975
|
} catch {
|
|
@@ -1651,7 +2015,7 @@ function withGlobalFlags(command, commandArgs, flags) {
|
|
|
1651
2015
|
if (flags.noColor && !args.includes("--no-color")) {
|
|
1652
2016
|
args.push("--no-color");
|
|
1653
2017
|
}
|
|
1654
|
-
if ((command === "validate" || command === "compile" || command === "eval" || command === "import") && flags.json && !args.includes("--json")) {
|
|
2018
|
+
if ((command === "validate" || command === "compile" || command === "eval" || command === "import" || command === "migrate") && flags.json && !args.includes("--json")) {
|
|
1655
2019
|
args.push("--json");
|
|
1656
2020
|
}
|
|
1657
2021
|
return args;
|
|
@@ -1717,6 +2081,13 @@ async function run(argv, io = process) {
|
|
|
1717
2081
|
}
|
|
1718
2082
|
return runImport(commandArgs, io);
|
|
1719
2083
|
}
|
|
2084
|
+
if (command === "migrate") {
|
|
2085
|
+
if (commandArgs.includes("--help") || commandArgs.includes("-h")) {
|
|
2086
|
+
migrateHelp(io.stdout);
|
|
2087
|
+
return 0;
|
|
2088
|
+
}
|
|
2089
|
+
return runMigrate(commandArgs, io);
|
|
2090
|
+
}
|
|
1720
2091
|
io.stderr.write(`Error: Unknown command "${command}"
|
|
1721
2092
|
|
|
1722
2093
|
`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@traits-dev/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "traits.dev command-line interface for voice profile init, validate, compile, eval, and import workflows.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"traits-dev",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"provenance": true
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@traits-dev/core": "^0.
|
|
44
|
+
"@traits-dev/core": "^0.6.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/node": "^25.2.3",
|