@traits-dev/cli 0.3.0 → 0.5.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 +351 -16
- 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-4.yaml
|
|
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
|
|
@@ -22,6 +22,8 @@ function printCompileUsage(out = process.stderr) {
|
|
|
22
22
|
" --model <model> Model target (required)",
|
|
23
23
|
" --json Output structured JSON",
|
|
24
24
|
" --strict Treat warnings as compile-blocking",
|
|
25
|
+
" --budget Print estimated token count (chars/4)",
|
|
26
|
+
" --budget-limit <tokens> Warn to stderr if estimate exceeds limit",
|
|
25
27
|
" --explain Include compilation trace output",
|
|
26
28
|
" --context key=value Activate context adaptation (repeatable)",
|
|
27
29
|
" --knowledge-base-dir Directory containing compiler pattern files",
|
|
@@ -52,6 +54,8 @@ function parseCompileArgs(args) {
|
|
|
52
54
|
model: null,
|
|
53
55
|
strict: false,
|
|
54
56
|
json: false,
|
|
57
|
+
budget: false,
|
|
58
|
+
budgetLimit: null,
|
|
55
59
|
explain: false,
|
|
56
60
|
verbose: false,
|
|
57
61
|
noColor: false,
|
|
@@ -70,6 +74,10 @@ function parseCompileArgs(args) {
|
|
|
70
74
|
result.json = true;
|
|
71
75
|
continue;
|
|
72
76
|
}
|
|
77
|
+
if (arg === "--budget") {
|
|
78
|
+
result.budget = true;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
73
81
|
if (arg === "--explain") {
|
|
74
82
|
result.explain = true;
|
|
75
83
|
continue;
|
|
@@ -82,7 +90,7 @@ function parseCompileArgs(args) {
|
|
|
82
90
|
result.noColor = true;
|
|
83
91
|
continue;
|
|
84
92
|
}
|
|
85
|
-
if (arg === "--model" || arg === "--bundled-profiles-dir" || arg === "--context" || arg === "--knowledge-base-dir") {
|
|
93
|
+
if (arg === "--model" || arg === "--bundled-profiles-dir" || arg === "--context" || arg === "--knowledge-base-dir" || arg === "--budget-limit") {
|
|
86
94
|
const value = args[index + 1];
|
|
87
95
|
if (!value) return { error: `Missing value for "${arg}"` };
|
|
88
96
|
if (arg === "--model") {
|
|
@@ -91,6 +99,12 @@ function parseCompileArgs(args) {
|
|
|
91
99
|
result.bundledProfilesDir = value;
|
|
92
100
|
} else if (arg === "--knowledge-base-dir") {
|
|
93
101
|
result.knowledgeBaseDir = value;
|
|
102
|
+
} else if (arg === "--budget-limit") {
|
|
103
|
+
const parsedBudgetLimit = Number(value);
|
|
104
|
+
if (!Number.isFinite(parsedBudgetLimit) || parsedBudgetLimit <= 0) {
|
|
105
|
+
return { error: `Invalid value for "--budget-limit": "${value}"` };
|
|
106
|
+
}
|
|
107
|
+
result.budgetLimit = Math.round(parsedBudgetLimit);
|
|
94
108
|
} else {
|
|
95
109
|
const parsedContext = parseContextArg(value);
|
|
96
110
|
if ("error" in parsedContext) return { error: parsedContext.error };
|
|
@@ -111,8 +125,14 @@ function parseCompileArgs(args) {
|
|
|
111
125
|
if (!result.model) {
|
|
112
126
|
return { error: 'Missing required option "--model"' };
|
|
113
127
|
}
|
|
128
|
+
if (result.budgetLimit != null) {
|
|
129
|
+
result.budget = true;
|
|
130
|
+
}
|
|
114
131
|
return { value: result };
|
|
115
132
|
}
|
|
133
|
+
function estimateBudgetTokens(text) {
|
|
134
|
+
return Math.ceil(String(text ?? "").length / 4);
|
|
135
|
+
}
|
|
116
136
|
function runCompile(args, io = process) {
|
|
117
137
|
const parsed = parseCompileArgs(args);
|
|
118
138
|
if ("error" in parsed) {
|
|
@@ -156,10 +176,32 @@ function runCompile(args, io = process) {
|
|
|
156
176
|
if (options.json) {
|
|
157
177
|
io.stdout.write(`${JSON.stringify(compiled, null, 2)}
|
|
158
178
|
`);
|
|
179
|
+
if (options.budget) {
|
|
180
|
+
const budgetEstimate = estimateBudgetTokens(compiled.text);
|
|
181
|
+
io.stderr.write(`Estimated token count: ${budgetEstimate}
|
|
182
|
+
`);
|
|
183
|
+
if (options.budgetLimit != null && budgetEstimate > options.budgetLimit) {
|
|
184
|
+
io.stderr.write(
|
|
185
|
+
`Warning: Estimated token count ${budgetEstimate} exceeds budget limit ${options.budgetLimit}
|
|
186
|
+
`
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
159
190
|
return 0;
|
|
160
191
|
}
|
|
161
192
|
io.stdout.write(`${compiled.text}
|
|
162
193
|
`);
|
|
194
|
+
if (options.budget) {
|
|
195
|
+
const budgetEstimate = estimateBudgetTokens(compiled.text);
|
|
196
|
+
io.stderr.write(`Estimated token count: ${budgetEstimate}
|
|
197
|
+
`);
|
|
198
|
+
if (options.budgetLimit != null && budgetEstimate > options.budgetLimit) {
|
|
199
|
+
io.stderr.write(
|
|
200
|
+
`Warning: Estimated token count ${budgetEstimate} exceeds budget limit ${options.budgetLimit}
|
|
201
|
+
`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
163
205
|
if (options.explain && compiled.trace) {
|
|
164
206
|
io.stdout.write(`
|
|
165
207
|
[TRACE]
|
|
@@ -1404,21 +1446,238 @@ function importHelp(out = process.stdout) {
|
|
|
1404
1446
|
printImportUsage(out);
|
|
1405
1447
|
}
|
|
1406
1448
|
|
|
1407
|
-
// src/commands/
|
|
1449
|
+
// src/commands/migrate.ts
|
|
1450
|
+
import fs4 from "fs";
|
|
1408
1451
|
import path5 from "path";
|
|
1452
|
+
import { loadProfileFile, validateResolvedProfile as validateResolvedProfile2 } from "@traits-dev/core";
|
|
1453
|
+
import { renderImportedProfileYAML, toValidationResultObject as toValidationResultObject4 } from "@traits-dev/core/internal";
|
|
1454
|
+
function printMigrateUsage(out = process.stderr) {
|
|
1455
|
+
out.write(
|
|
1456
|
+
[
|
|
1457
|
+
"Usage:",
|
|
1458
|
+
" traits migrate <profile-path> [options]",
|
|
1459
|
+
"",
|
|
1460
|
+
"Options:",
|
|
1461
|
+
" --to <version> Target schema version (default: v1.5)",
|
|
1462
|
+
" --output <path> Output file path (default: <name>.v1.5.yaml)",
|
|
1463
|
+
" --in-place Overwrite the source file (requires --force if file exists)",
|
|
1464
|
+
" --force Overwrite existing destination file",
|
|
1465
|
+
" --json Output structured JSON summary",
|
|
1466
|
+
" --verbose Include additional command metadata",
|
|
1467
|
+
" --no-color Disable colorized output",
|
|
1468
|
+
""
|
|
1469
|
+
].join("\n")
|
|
1470
|
+
);
|
|
1471
|
+
}
|
|
1472
|
+
function parseMigrateArgs(args) {
|
|
1473
|
+
const result = {
|
|
1474
|
+
profilePath: null,
|
|
1475
|
+
to: "v1.5",
|
|
1476
|
+
outputPath: null,
|
|
1477
|
+
inPlace: false,
|
|
1478
|
+
force: false,
|
|
1479
|
+
json: false,
|
|
1480
|
+
verbose: false,
|
|
1481
|
+
noColor: false
|
|
1482
|
+
};
|
|
1483
|
+
const positionals = [];
|
|
1484
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1485
|
+
const arg = args[index];
|
|
1486
|
+
if (arg === "--in-place") {
|
|
1487
|
+
result.inPlace = true;
|
|
1488
|
+
continue;
|
|
1489
|
+
}
|
|
1490
|
+
if (arg === "--force") {
|
|
1491
|
+
result.force = true;
|
|
1492
|
+
continue;
|
|
1493
|
+
}
|
|
1494
|
+
if (arg === "--json") {
|
|
1495
|
+
result.json = true;
|
|
1496
|
+
continue;
|
|
1497
|
+
}
|
|
1498
|
+
if (arg === "--verbose") {
|
|
1499
|
+
result.verbose = true;
|
|
1500
|
+
continue;
|
|
1501
|
+
}
|
|
1502
|
+
if (arg === "--no-color") {
|
|
1503
|
+
result.noColor = true;
|
|
1504
|
+
continue;
|
|
1505
|
+
}
|
|
1506
|
+
if (arg === "--to" || arg === "--output") {
|
|
1507
|
+
const value = args[index + 1];
|
|
1508
|
+
if (!value) return { error: `Missing value for "${arg}"` };
|
|
1509
|
+
if (arg === "--to") {
|
|
1510
|
+
if (value !== "v1.5") {
|
|
1511
|
+
return {
|
|
1512
|
+
error: `Unsupported "--to" value "${value}". Currently supported: v1.5`
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1515
|
+
result.to = "v1.5";
|
|
1516
|
+
} else {
|
|
1517
|
+
result.outputPath = value;
|
|
1518
|
+
}
|
|
1519
|
+
index += 1;
|
|
1520
|
+
continue;
|
|
1521
|
+
}
|
|
1522
|
+
if (arg.startsWith("--")) {
|
|
1523
|
+
return { error: `Unknown option "${arg}"` };
|
|
1524
|
+
}
|
|
1525
|
+
positionals.push(arg);
|
|
1526
|
+
}
|
|
1527
|
+
if (positionals.length !== 1) {
|
|
1528
|
+
return { error: "Expected exactly one profile path argument" };
|
|
1529
|
+
}
|
|
1530
|
+
result.profilePath = positionals[0];
|
|
1531
|
+
if (result.inPlace && result.outputPath) {
|
|
1532
|
+
return { error: 'Use either "--in-place" or "--output", not both.' };
|
|
1533
|
+
}
|
|
1534
|
+
return { value: result };
|
|
1535
|
+
}
|
|
1536
|
+
function resolveDefaultOutputPath(sourcePath, toVersion) {
|
|
1537
|
+
const ext = path5.extname(sourcePath) || ".yaml";
|
|
1538
|
+
const base = sourcePath.slice(0, sourcePath.length - ext.length);
|
|
1539
|
+
return `${base}.${toVersion}${ext}`;
|
|
1540
|
+
}
|
|
1541
|
+
function writeFileAtomic2(filePath, contents) {
|
|
1542
|
+
fs4.mkdirSync(path5.dirname(filePath), { recursive: true });
|
|
1543
|
+
const tempFile = path5.join(
|
|
1544
|
+
path5.dirname(filePath),
|
|
1545
|
+
`.${path5.basename(filePath)}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`
|
|
1546
|
+
);
|
|
1547
|
+
fs4.writeFileSync(tempFile, contents, "utf8");
|
|
1548
|
+
fs4.renameSync(tempFile, filePath);
|
|
1549
|
+
}
|
|
1550
|
+
function migrateV14ToV15(profile) {
|
|
1551
|
+
const migrated = JSON.parse(JSON.stringify(profile));
|
|
1552
|
+
migrated.schema = "v1.5";
|
|
1553
|
+
if (!migrated.capabilities) {
|
|
1554
|
+
migrated.capabilities = {
|
|
1555
|
+
tools: [],
|
|
1556
|
+
constraints: [],
|
|
1557
|
+
handoff: {
|
|
1558
|
+
trigger: "Request requires unavailable operations.",
|
|
1559
|
+
action: "Offer handoff to a human operator."
|
|
1560
|
+
}
|
|
1561
|
+
};
|
|
1562
|
+
return { migrated, capabilitiesAdded: true };
|
|
1563
|
+
}
|
|
1564
|
+
return { migrated, capabilitiesAdded: false };
|
|
1565
|
+
}
|
|
1566
|
+
function runMigrate(args, io = process) {
|
|
1567
|
+
const parsed = parseMigrateArgs(args);
|
|
1568
|
+
if ("error" in parsed) {
|
|
1569
|
+
io.stderr.write(`Error: ${parsed.error}
|
|
1570
|
+
|
|
1571
|
+
`);
|
|
1572
|
+
printMigrateUsage(io.stderr);
|
|
1573
|
+
return 1;
|
|
1574
|
+
}
|
|
1575
|
+
const options = parsed.value;
|
|
1576
|
+
if (!options.profilePath) {
|
|
1577
|
+
io.stderr.write("Error: Missing profile path\n\n");
|
|
1578
|
+
printMigrateUsage(io.stderr);
|
|
1579
|
+
return 1;
|
|
1580
|
+
}
|
|
1581
|
+
const sourcePath = path5.resolve(io.cwd(), options.profilePath);
|
|
1582
|
+
const destinationPath = options.inPlace ? sourcePath : options.outputPath ? path5.resolve(io.cwd(), options.outputPath) : resolveDefaultOutputPath(sourcePath, options.to);
|
|
1583
|
+
if (!fs4.existsSync(sourcePath)) {
|
|
1584
|
+
io.stderr.write(`Error: Source profile does not exist: ${sourcePath}
|
|
1585
|
+
`);
|
|
1586
|
+
return 1;
|
|
1587
|
+
}
|
|
1588
|
+
if (fs4.existsSync(destinationPath) && !options.force) {
|
|
1589
|
+
io.stderr.write(
|
|
1590
|
+
`Error: Destination file already exists: ${destinationPath}. Use --force to overwrite.
|
|
1591
|
+
`
|
|
1592
|
+
);
|
|
1593
|
+
return 1;
|
|
1594
|
+
}
|
|
1595
|
+
let loaded;
|
|
1596
|
+
try {
|
|
1597
|
+
loaded = loadProfileFile(sourcePath);
|
|
1598
|
+
} catch (error) {
|
|
1599
|
+
io.stderr.write(
|
|
1600
|
+
`Error: Failed to read profile: ${error instanceof Error ? error.message : String(error)}
|
|
1601
|
+
`
|
|
1602
|
+
);
|
|
1603
|
+
return 1;
|
|
1604
|
+
}
|
|
1605
|
+
if (loaded.schema !== "v1.4") {
|
|
1606
|
+
io.stderr.write(
|
|
1607
|
+
`Error: Migration source schema must be "v1.4". Found "${loaded.schema ?? "unknown"}".
|
|
1608
|
+
`
|
|
1609
|
+
);
|
|
1610
|
+
return 1;
|
|
1611
|
+
}
|
|
1612
|
+
const migration = migrateV14ToV15(loaded);
|
|
1613
|
+
const validation = validateResolvedProfile2(migration.migrated, {
|
|
1614
|
+
strict: false
|
|
1615
|
+
});
|
|
1616
|
+
if (validation.errors.length > 0) {
|
|
1617
|
+
io.stderr.write(
|
|
1618
|
+
`Error: Migrated profile is invalid and was not written (${validation.errors.length} error(s)).
|
|
1619
|
+
`
|
|
1620
|
+
);
|
|
1621
|
+
return 2;
|
|
1622
|
+
}
|
|
1623
|
+
const yaml = renderImportedProfileYAML(migration.migrated);
|
|
1624
|
+
writeFileAtomic2(destinationPath, yaml);
|
|
1625
|
+
if (options.json) {
|
|
1626
|
+
io.stdout.write(
|
|
1627
|
+
`${JSON.stringify(
|
|
1628
|
+
{
|
|
1629
|
+
migrated: true,
|
|
1630
|
+
sourcePath,
|
|
1631
|
+
outputPath: destinationPath,
|
|
1632
|
+
fromSchema: "v1.4",
|
|
1633
|
+
toSchema: "v1.5",
|
|
1634
|
+
capabilitiesAdded: migration.capabilitiesAdded,
|
|
1635
|
+
validation: toValidationResultObject4(validation)
|
|
1636
|
+
},
|
|
1637
|
+
null,
|
|
1638
|
+
2
|
|
1639
|
+
)}
|
|
1640
|
+
`
|
|
1641
|
+
);
|
|
1642
|
+
return 0;
|
|
1643
|
+
}
|
|
1644
|
+
if (options.verbose) {
|
|
1645
|
+
io.stdout.write(`Source: ${sourcePath}
|
|
1646
|
+
`);
|
|
1647
|
+
io.stdout.write(`Output: ${destinationPath}
|
|
1648
|
+
`);
|
|
1649
|
+
io.stdout.write(`Capabilities added: ${migration.capabilitiesAdded ? "yes" : "no"}
|
|
1650
|
+
`);
|
|
1651
|
+
io.stdout.write(`Validation warnings: ${validation.warnings.length}
|
|
1652
|
+
|
|
1653
|
+
`);
|
|
1654
|
+
}
|
|
1655
|
+
io.stdout.write(`Migrated profile schema v1.4 -> v1.5
|
|
1656
|
+
`);
|
|
1657
|
+
io.stdout.write(`Wrote: ${destinationPath}
|
|
1658
|
+
`);
|
|
1659
|
+
return 0;
|
|
1660
|
+
}
|
|
1661
|
+
function migrateHelp(out = process.stdout) {
|
|
1662
|
+
printMigrateUsage(out);
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
// src/commands/validate.ts
|
|
1666
|
+
import path6 from "path";
|
|
1409
1667
|
import { validateProfile as validateProfile2 } from "@traits-dev/core";
|
|
1410
1668
|
import {
|
|
1411
1669
|
formatValidationResult as formatValidationResult4,
|
|
1412
|
-
toValidationResultObject as
|
|
1670
|
+
toValidationResultObject as toValidationResultObject5
|
|
1413
1671
|
} from "@traits-dev/core/internal";
|
|
1414
1672
|
function printValidateUsage(out = process.stderr) {
|
|
1415
1673
|
out.write(
|
|
1416
1674
|
[
|
|
1417
1675
|
"Usage:",
|
|
1418
|
-
" traits validate <profile-path> [
|
|
1676
|
+
" traits validate <profile-path> [options]",
|
|
1419
1677
|
"",
|
|
1420
1678
|
"Options:",
|
|
1421
1679
|
" --json Output structured JSON",
|
|
1680
|
+
" --format <text|json|sarif> Output format (default: text)",
|
|
1422
1681
|
" --strict Promote warnings to errors",
|
|
1423
1682
|
" --verbose Include additional command metadata",
|
|
1424
1683
|
" --no-color Disable colorized output",
|
|
@@ -1432,6 +1691,7 @@ function parseValidateArgs(args) {
|
|
|
1432
1691
|
profilePath: null,
|
|
1433
1692
|
strict: false,
|
|
1434
1693
|
json: false,
|
|
1694
|
+
format: "text",
|
|
1435
1695
|
verbose: false,
|
|
1436
1696
|
noColor: false,
|
|
1437
1697
|
bundledProfilesDir: null
|
|
@@ -1445,6 +1705,7 @@ function parseValidateArgs(args) {
|
|
|
1445
1705
|
}
|
|
1446
1706
|
if (arg === "--json") {
|
|
1447
1707
|
result.json = true;
|
|
1708
|
+
result.format = "json";
|
|
1448
1709
|
continue;
|
|
1449
1710
|
}
|
|
1450
1711
|
if (arg === "--verbose") {
|
|
@@ -1464,6 +1725,20 @@ function parseValidateArgs(args) {
|
|
|
1464
1725
|
index += 1;
|
|
1465
1726
|
continue;
|
|
1466
1727
|
}
|
|
1728
|
+
if (arg === "--format") {
|
|
1729
|
+
const nextValue = args[index + 1];
|
|
1730
|
+
if (!nextValue) {
|
|
1731
|
+
return { error: 'Missing value for "--format"' };
|
|
1732
|
+
}
|
|
1733
|
+
const normalized = String(nextValue).toLowerCase();
|
|
1734
|
+
if (normalized !== "text" && normalized !== "json" && normalized !== "sarif") {
|
|
1735
|
+
return { error: 'Invalid "--format" value. Expected text, json, or sarif.' };
|
|
1736
|
+
}
|
|
1737
|
+
result.format = normalized;
|
|
1738
|
+
result.json = normalized === "json";
|
|
1739
|
+
index += 1;
|
|
1740
|
+
continue;
|
|
1741
|
+
}
|
|
1467
1742
|
if (arg.startsWith("--")) {
|
|
1468
1743
|
return { error: `Unknown option "${arg}"` };
|
|
1469
1744
|
}
|
|
@@ -1475,6 +1750,54 @@ function parseValidateArgs(args) {
|
|
|
1475
1750
|
result.profilePath = positionals[0];
|
|
1476
1751
|
return { value: result };
|
|
1477
1752
|
}
|
|
1753
|
+
function toRelativePath(cwd, filePath) {
|
|
1754
|
+
const relative = path6.relative(cwd, filePath);
|
|
1755
|
+
if (relative && !relative.startsWith("..")) return relative;
|
|
1756
|
+
return filePath;
|
|
1757
|
+
}
|
|
1758
|
+
function buildSarifReport(validation, profilePath, cwd) {
|
|
1759
|
+
const diagnostics = [...validation.errors, ...validation.warnings];
|
|
1760
|
+
const uniqueRuleIds = [...new Set(diagnostics.map((diagnostic) => diagnostic.code))];
|
|
1761
|
+
const artifactUri = toRelativePath(cwd, profilePath);
|
|
1762
|
+
const rules = uniqueRuleIds.map((ruleId) => ({
|
|
1763
|
+
id: ruleId,
|
|
1764
|
+
shortDescription: {
|
|
1765
|
+
text: `traits.dev ${ruleId}`
|
|
1766
|
+
}
|
|
1767
|
+
}));
|
|
1768
|
+
const results = diagnostics.map((diagnostic) => ({
|
|
1769
|
+
ruleId: diagnostic.code,
|
|
1770
|
+
level: diagnostic.severity === "error" ? "error" : "warning",
|
|
1771
|
+
message: {
|
|
1772
|
+
text: diagnostic.message
|
|
1773
|
+
},
|
|
1774
|
+
locations: [
|
|
1775
|
+
{
|
|
1776
|
+
physicalLocation: {
|
|
1777
|
+
artifactLocation: {
|
|
1778
|
+
uri: artifactUri
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
]
|
|
1783
|
+
}));
|
|
1784
|
+
return {
|
|
1785
|
+
version: "2.1.0",
|
|
1786
|
+
$schema: "https://json.schemastore.org/sarif-2.1.0.json",
|
|
1787
|
+
runs: [
|
|
1788
|
+
{
|
|
1789
|
+
tool: {
|
|
1790
|
+
driver: {
|
|
1791
|
+
name: "traits.dev",
|
|
1792
|
+
informationUri: "https://github.com/justinhambleton/traits",
|
|
1793
|
+
rules
|
|
1794
|
+
}
|
|
1795
|
+
},
|
|
1796
|
+
results
|
|
1797
|
+
}
|
|
1798
|
+
]
|
|
1799
|
+
};
|
|
1800
|
+
}
|
|
1478
1801
|
function runValidate(args, io = process) {
|
|
1479
1802
|
const parsed = parseValidateArgs(args);
|
|
1480
1803
|
if ("error" in parsed) {
|
|
@@ -1490,14 +1813,18 @@ function runValidate(args, io = process) {
|
|
|
1490
1813
|
printValidateUsage(io.stderr);
|
|
1491
1814
|
return 1;
|
|
1492
1815
|
}
|
|
1493
|
-
const bundledProfilesDir = options.bundledProfilesDir ?
|
|
1494
|
-
const profilePath =
|
|
1816
|
+
const bundledProfilesDir = options.bundledProfilesDir ? path6.resolve(io.cwd(), options.bundledProfilesDir) : path6.resolve(io.cwd(), "profiles");
|
|
1817
|
+
const profilePath = path6.resolve(io.cwd(), options.profilePath);
|
|
1495
1818
|
const result = validateProfile2(profilePath, {
|
|
1496
1819
|
strict: options.strict,
|
|
1497
1820
|
bundledProfilesDir
|
|
1498
1821
|
});
|
|
1499
|
-
if (options.json) {
|
|
1500
|
-
io.stdout.write(`${JSON.stringify(
|
|
1822
|
+
if (options.format === "json") {
|
|
1823
|
+
io.stdout.write(`${JSON.stringify(toValidationResultObject5(result), null, 2)}
|
|
1824
|
+
`);
|
|
1825
|
+
} else if (options.format === "sarif") {
|
|
1826
|
+
const sarif = buildSarifReport(result, profilePath, io.cwd());
|
|
1827
|
+
io.stdout.write(`${JSON.stringify(sarif, null, 2)}
|
|
1501
1828
|
`);
|
|
1502
1829
|
} else {
|
|
1503
1830
|
if (options.verbose) {
|
|
@@ -1519,14 +1846,14 @@ function validateHelp(out = process.stdout) {
|
|
|
1519
1846
|
}
|
|
1520
1847
|
|
|
1521
1848
|
// src/bin/traits.ts
|
|
1522
|
-
var CLI_DIR =
|
|
1849
|
+
var CLI_DIR = path7.dirname(fileURLToPath(import.meta.url));
|
|
1523
1850
|
var PACKAGE_JSON_CANDIDATES = [
|
|
1524
|
-
|
|
1525
|
-
|
|
1851
|
+
path7.resolve(CLI_DIR, "../package.json"),
|
|
1852
|
+
path7.resolve(CLI_DIR, "../../package.json")
|
|
1526
1853
|
];
|
|
1527
1854
|
function resolvePackageJsonPath() {
|
|
1528
1855
|
for (const candidate of PACKAGE_JSON_CANDIDATES) {
|
|
1529
|
-
if (
|
|
1856
|
+
if (fs5.existsSync(candidate)) return candidate;
|
|
1530
1857
|
}
|
|
1531
1858
|
return PACKAGE_JSON_CANDIDATES[0];
|
|
1532
1859
|
}
|
|
@@ -1544,6 +1871,7 @@ function printRootUsage(out = process.stdout) {
|
|
|
1544
1871
|
" compile <profile-path> Compile a profile for a target model",
|
|
1545
1872
|
" eval <profile-path> Evaluate profile responses (Tier 1 scaffold)",
|
|
1546
1873
|
" import [prompt-path] Import a profile from an existing system prompt",
|
|
1874
|
+
" migrate <profile-path> Migrate profile schema (v1.4 -> v1.5)",
|
|
1547
1875
|
" validate <profile-path> Validate a voice profile",
|
|
1548
1876
|
"",
|
|
1549
1877
|
"Global flags:",
|
|
@@ -1563,7 +1891,7 @@ function printRootUsage(out = process.stdout) {
|
|
|
1563
1891
|
}
|
|
1564
1892
|
function readCliVersion() {
|
|
1565
1893
|
try {
|
|
1566
|
-
const raw =
|
|
1894
|
+
const raw = fs5.readFileSync(PACKAGE_JSON_PATH, "utf8");
|
|
1567
1895
|
const pkg = JSON.parse(raw);
|
|
1568
1896
|
return String(pkg.version ?? "0.0.0");
|
|
1569
1897
|
} catch {
|
|
@@ -1609,7 +1937,7 @@ function withGlobalFlags(command, commandArgs, flags) {
|
|
|
1609
1937
|
if (flags.noColor && !args.includes("--no-color")) {
|
|
1610
1938
|
args.push("--no-color");
|
|
1611
1939
|
}
|
|
1612
|
-
if ((command === "validate" || command === "compile" || command === "eval" || command === "import") && flags.json && !args.includes("--json")) {
|
|
1940
|
+
if ((command === "validate" || command === "compile" || command === "eval" || command === "import" || command === "migrate") && flags.json && !args.includes("--json")) {
|
|
1613
1941
|
args.push("--json");
|
|
1614
1942
|
}
|
|
1615
1943
|
return args;
|
|
@@ -1675,6 +2003,13 @@ async function run(argv, io = process) {
|
|
|
1675
2003
|
}
|
|
1676
2004
|
return runImport(commandArgs, io);
|
|
1677
2005
|
}
|
|
2006
|
+
if (command === "migrate") {
|
|
2007
|
+
if (commandArgs.includes("--help") || commandArgs.includes("-h")) {
|
|
2008
|
+
migrateHelp(io.stdout);
|
|
2009
|
+
return 0;
|
|
2010
|
+
}
|
|
2011
|
+
return runMigrate(commandArgs, io);
|
|
2012
|
+
}
|
|
1678
2013
|
io.stderr.write(`Error: Unknown command "${command}"
|
|
1679
2014
|
|
|
1680
2015
|
`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@traits-dev/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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.5.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/node": "^25.2.3",
|