@tuned-tensor/cli 0.18.0 → 0.20.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/README.md +5 -2
- package/dist/{chunk-U5HNS367.js → chunk-4Q54NHKR.js} +117 -1
- package/dist/chunk-4Q54NHKR.js.map +1 -0
- package/dist/index.js +544 -410
- package/dist/index.js.map +1 -1
- package/dist/local-runtime.js +1 -1
- package/docs/local-runtime/foundation-long-runs.md +2 -2
- package/docs/local-runtime/serving.md +1 -1
- package/docs/spec-architecture.html +1 -1
- package/docs/spec-workflow.md +11 -6
- package/package.json +1 -1
- package/dist/chunk-U5HNS367.js.map +0 -1
package/README.md
CHANGED
|
@@ -96,7 +96,8 @@ npm link
|
|
|
96
96
|
`tunedtensor.json` is the source of truth for behavior and training settings.
|
|
97
97
|
Use `/spec`, `/spec diff`, `/spec validate`, and `/spec history` in the shell to
|
|
98
98
|
review it without a model call. Ask the agent for changes, inspect its proposed
|
|
99
|
-
diff, then `/approve` to save a validated edit
|
|
99
|
+
diff, then press `y` when tt asks (or `/approve` later) to save a validated edit
|
|
100
|
+
with revision history. Pipeline
|
|
100
101
|
runs use the saved spec. See [the spec workflow](docs/spec-workflow.md).
|
|
101
102
|
|
|
102
103
|
## Conversational terminal
|
|
@@ -173,7 +174,9 @@ Ordinary sentences go through the locally orchestrated model session. The
|
|
|
173
174
|
model has no shell or general filesystem tool. It can read and propose reviewed
|
|
174
175
|
edits to an existing spec, or prepare one new folder directly beneath the shell's current working directory with a validated
|
|
175
176
|
`tunedtensor.json`, or prepare a local pipeline dry-run sealed to the reviewed
|
|
176
|
-
workspace and spec contents. Writes and pipeline previews wait for
|
|
177
|
+
workspace and spec contents. Writes and pipeline previews wait for your approval: after the
|
|
178
|
+
answer, tt shows the proposal and asks inline, where `y` approves, `n` rejects and `Esc`
|
|
179
|
+
leaves it pending for a later `/approve` or `/reject`. Any other key is ignored. The tools refuse path
|
|
177
180
|
traversal, symlinked workspace roots, overwriting a new-project target, unsupported spec
|
|
178
181
|
fields, cloud pipeline targets, and spec changes after review. Known CLI
|
|
179
182
|
commands such as `runs list`, `doctor`, and `models list` still execute directly.
|
|
@@ -1227,6 +1227,116 @@ import { readFile as readFile2, stat as stat2, writeFile as writeFile2 } from "f
|
|
|
1227
1227
|
import { dirname as dirname2, isAbsolute as isAbsolute3, relative as relative2, resolve as resolve4, sep as sep2 } from "path";
|
|
1228
1228
|
import { homedir as homedir2 } from "os";
|
|
1229
1229
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
1230
|
+
|
|
1231
|
+
// src/eval/rules.ts
|
|
1232
|
+
function validateEvalCases(spec) {
|
|
1233
|
+
const errors = [];
|
|
1234
|
+
const cases = spec.eval_cases ?? [];
|
|
1235
|
+
for (const [caseIndex, evalCase] of cases.entries()) {
|
|
1236
|
+
const label = `eval_cases[${caseIndex}]`;
|
|
1237
|
+
if (!evalCase || typeof evalCase !== "object") {
|
|
1238
|
+
errors.push(`${label} must be an object`);
|
|
1239
|
+
continue;
|
|
1240
|
+
}
|
|
1241
|
+
if (typeof evalCase.input !== "string" || evalCase.input.trim().length === 0) {
|
|
1242
|
+
errors.push(`${label}.input must be a non-empty string`);
|
|
1243
|
+
}
|
|
1244
|
+
if (evalCase.runtime !== "python") {
|
|
1245
|
+
errors.push(`${label}.runtime must be "python"`);
|
|
1246
|
+
}
|
|
1247
|
+
if (!Array.isArray(evalCase.tests) || evalCase.tests.length === 0) {
|
|
1248
|
+
errors.push(`${label}.tests must contain at least one test`);
|
|
1249
|
+
continue;
|
|
1250
|
+
}
|
|
1251
|
+
for (const [testIndex, test] of evalCase.tests.entries()) {
|
|
1252
|
+
const testLabel = `${label}.tests[${testIndex}]`;
|
|
1253
|
+
if (!test || typeof test !== "object") {
|
|
1254
|
+
errors.push(`${testLabel} must be an object`);
|
|
1255
|
+
continue;
|
|
1256
|
+
}
|
|
1257
|
+
if (test.name !== void 0 && (typeof test.name !== "string" || test.name.trim().length === 0)) {
|
|
1258
|
+
errors.push(`${testLabel}.name must be a non-empty string when provided`);
|
|
1259
|
+
}
|
|
1260
|
+
if (test.args !== void 0 && (!Array.isArray(test.args) || !test.args.every((arg) => typeof arg === "string"))) {
|
|
1261
|
+
errors.push(`${testLabel}.args must be an array of strings`);
|
|
1262
|
+
}
|
|
1263
|
+
if (test.stdin !== void 0 && typeof test.stdin !== "string") {
|
|
1264
|
+
errors.push(`${testLabel}.stdin must be a string`);
|
|
1265
|
+
}
|
|
1266
|
+
if (test.expected_exit_code !== void 0 && (typeof test.expected_exit_code !== "number" || !Number.isInteger(test.expected_exit_code) || test.expected_exit_code < 0 || test.expected_exit_code > 255)) {
|
|
1267
|
+
errors.push(`${testLabel}.expected_exit_code must be an integer between 0 and 255`);
|
|
1268
|
+
}
|
|
1269
|
+
if (test.expected_stdout !== void 0 && typeof test.expected_stdout !== "string") {
|
|
1270
|
+
errors.push(`${testLabel}.expected_stdout must be a string`);
|
|
1271
|
+
}
|
|
1272
|
+
validateFiles(test.files, `${testLabel}.files`, errors);
|
|
1273
|
+
validateFiles(test.expected_files, `${testLabel}.expected_files`, errors);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
return errors;
|
|
1277
|
+
}
|
|
1278
|
+
function validateFiles(files, label, errors) {
|
|
1279
|
+
if (files === void 0) return;
|
|
1280
|
+
if (!Array.isArray(files)) {
|
|
1281
|
+
errors.push(`${label} must be an array`);
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
for (const [index, file] of files.entries()) {
|
|
1285
|
+
const fileLabel = `${label}[${index}]`;
|
|
1286
|
+
if (!file || typeof file !== "object") {
|
|
1287
|
+
errors.push(`${fileLabel} must be an object`);
|
|
1288
|
+
continue;
|
|
1289
|
+
}
|
|
1290
|
+
const row = file;
|
|
1291
|
+
if (typeof row.path !== "string" || !isSafeRelativePath(row.path)) {
|
|
1292
|
+
errors.push(`${fileLabel}.path must be a safe relative path`);
|
|
1293
|
+
}
|
|
1294
|
+
if (row.content !== void 0 && typeof row.content !== "string") {
|
|
1295
|
+
errors.push(`${fileLabel}.content must be a string`);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
function isSafeRelativePath(value) {
|
|
1300
|
+
if (value.length === 0 || value.length > 240) return false;
|
|
1301
|
+
if (value.includes("\0") || value.startsWith("/")) return false;
|
|
1302
|
+
const normalized = value.replace(/\\/g, "/");
|
|
1303
|
+
return !normalized.split("/").some((part) => part === "" || part === "." || part === "..");
|
|
1304
|
+
}
|
|
1305
|
+
function evaluateConstraint(text, constraint) {
|
|
1306
|
+
const lower = constraint.toLowerCase();
|
|
1307
|
+
const neverMatch = lower.match(/^never (?:share|mention|include|reveal|use|say|output|return|give|provide|show|disclose|expose)\s+(.+)/);
|
|
1308
|
+
if (neverMatch) {
|
|
1309
|
+
const forbidden = neverMatch[1].replace(/['"]/g, "").trim();
|
|
1310
|
+
if (text.toLowerCase().includes(forbidden.toLowerCase())) {
|
|
1311
|
+
return { passed: false, message: `Output contains "${forbidden}"` };
|
|
1312
|
+
}
|
|
1313
|
+
return { passed: true };
|
|
1314
|
+
}
|
|
1315
|
+
const alwaysMatch = lower.match(/^always (?:include|use|start with|end with|contain|respond in|reply in)\s+(.+)/);
|
|
1316
|
+
if (alwaysMatch) {
|
|
1317
|
+
const required = alwaysMatch[1].replace(/['"]/g, "").trim();
|
|
1318
|
+
if (!text.toLowerCase().includes(required.toLowerCase())) {
|
|
1319
|
+
return { passed: false, message: `Output missing "${required}"` };
|
|
1320
|
+
}
|
|
1321
|
+
return { passed: true };
|
|
1322
|
+
}
|
|
1323
|
+
return {
|
|
1324
|
+
passed: true,
|
|
1325
|
+
message: `Constraint not enforceable by rules: "${constraint}" (will be checked by LLM judge if a provider is configured)`
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
function checkConstraints(text, constraints) {
|
|
1329
|
+
return constraints.map((constraint) => {
|
|
1330
|
+
const result = evaluateConstraint(text, constraint);
|
|
1331
|
+
return {
|
|
1332
|
+
assertion: `constraint: ${constraint}`,
|
|
1333
|
+
passed: result.passed,
|
|
1334
|
+
message: result.message
|
|
1335
|
+
};
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
// src/local-runtime/local-project.ts
|
|
1230
1340
|
var DEFAULT_LOCAL_SPEC_PATH = "tunedtensor.json";
|
|
1231
1341
|
function resolveLocalReference(value, baseDirectory) {
|
|
1232
1342
|
if (typeof value !== "string" || !value || isAbsolute3(value) || /^[a-z][a-z0-9+.-]*:/i.test(value)) {
|
|
@@ -1516,6 +1626,11 @@ function validateBehaviorSpec(input) {
|
|
|
1516
1626
|
}
|
|
1517
1627
|
seen.add(key);
|
|
1518
1628
|
}
|
|
1629
|
+
for (const [index, example] of spec.examples.entries()) {
|
|
1630
|
+
for (const result of checkConstraints(example.output, spec.constraints ?? [])) {
|
|
1631
|
+
if (!result.passed) warnings.push(`examples[${index}] may violate ${result.assertion} (${result.message})`);
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1519
1634
|
if (!spec.guidelines.length) {
|
|
1520
1635
|
warnings.push("Add guidelines to make expected behavior explicit.");
|
|
1521
1636
|
}
|
|
@@ -6382,6 +6497,7 @@ export {
|
|
|
6382
6497
|
localAdapterSpecFileSchema,
|
|
6383
6498
|
localBehaviorSpecFileSchema,
|
|
6384
6499
|
isFoundationSpecFile,
|
|
6500
|
+
validateEvalCases,
|
|
6385
6501
|
DEFAULT_LOCAL_SPEC_PATH,
|
|
6386
6502
|
localPathsOverlap,
|
|
6387
6503
|
resolveLocalRunInputPaths,
|
|
@@ -6457,4 +6573,4 @@ export {
|
|
|
6457
6573
|
formatAgentHostBlock,
|
|
6458
6574
|
assessHardware
|
|
6459
6575
|
};
|
|
6460
|
-
//# sourceMappingURL=chunk-
|
|
6576
|
+
//# sourceMappingURL=chunk-4Q54NHKR.js.map
|