@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 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 with revision history. Pipeline
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 `/approve`. The tools refuse path
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-U5HNS367.js.map
6576
+ //# sourceMappingURL=chunk-4Q54NHKR.js.map