Skip to content

Commit 7e2e196

Browse files
Add s.date schema helper for ISO 8601 date strings
Motivated by sample-run analysis (samples 36–40): sample 37 (git-author-stats) uses plain s.string for firstCommit/lastCommit date fields. A dedicated s.date helper (format:"date", validated as YYYY-MM-DD) makes the intent explicit and catches non-conforming strings before they reach repair turns. Changes: - Add s.date to the s.* helpers: string schema with format:"date" - Validate format:"date" in validateSchema with a clear YYYY-MM-DD error message - Update @file header comment to document the new helper - Add 5 unit tests in src/rig.test.ts covering serialization, acceptance, and rejection cases Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent fbc275d commit 7e2e196

2 files changed

Lines changed: 42 additions & 1 deletion

File tree

skills/rig/rig.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
* T:LauncherIo type {stdin,stdout,stderr} override for launcher subprocess
3030
* T:JsonSchemaObject type {[key:string]:unknown} plain JSON Schema object
3131
* s.string/number/integer/boolean/null SchemaHelperFactory primitives; call as value or fn(desc)
32-
* s.int alias for s.integer; s.nonEmptyString string with minLength:1; s.url string with format:"uri"; s.path string with format:"path"
32+
* s.int alias for s.integer; s.nonEmptyString string with minLength:1; s.url string with format:"uri"; s.path string with format:"path"; s.date string with format:"date" validated as YYYY-MM-DD
3333
* s.positiveInt integer with minimum:1; s.nonNegativeInt integer with minimum:0; NumberSchema/IntegerSchema support minimum/maximum constraints
3434
* s.array(items,desc?) ArraySchema; use for homogeneous lists, e.g. s.array(s.string)
3535
* s.nonEmptyArray(items,desc?) ArraySchema with minItems:1; validates array has at least one element
@@ -241,6 +241,8 @@ export const s = {
241241
url: createConstrainedStringSchema({ format: "uri" }),
242242
/** Schema for a file system path string (format: "path"). Use instead of `s.string` when the value is a file or directory path; improves readability and hints to the runtime about path-based context resolution. Call as `s.path` or `s.path("description")`. */
243243
path: createConstrainedStringSchema({ format: "path" }),
244+
/** Schema for an ISO 8601 calendar date string (format: "date", pattern `YYYY-MM-DD`). Use when the value is a date-only value (no time component). Validated at runtime: non-conforming strings fail with a clear error. Call as `s.date` or `s.date("description")`. */
245+
date: createConstrainedStringSchema({ format: "date" }),
244246
/** Schema for a `number` value. Call as `s.number` or `s.number("description")`. */
245247
number: createTypedPrimitiveSchema<NumberSchema>("number"),
246248
/** Schema for an integer value. Serializes to `{"type":"integer"}` in JSON Schema. Call as `s.integer` or `s.integer("description")`. */
@@ -2059,6 +2061,11 @@ function validateSchema(value: unknown, schema: Schema, path: string, optional:
20592061
if (format === "uri") {
20602062
try { new URL(value); } catch { return { ok: false, error: `${path}: expected a valid URL, got ${JSON.stringify(value)}` }; }
20612063
}
2064+
if (format === "date") {
2065+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
2066+
return { ok: false, error: `${path}: expected a date string in YYYY-MM-DD format, got ${JSON.stringify(value)}` };
2067+
}
2068+
}
20622069
return ok();
20632070
}
20642071
if (schema.type === "number") {

src/rig.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1466,6 +1466,40 @@ describe("s.path", () => {
14661466
});
14671467
});
14681468

1469+
describe("s.date", () => {
1470+
it("serializes to {type:'string', format:'date'}", () => {
1471+
expect(toJsonSchema(s.date)).toEqual({ type: "string", format: "date" });
1472+
expect(toJsonSchema(s.date("release date"))).toEqual({ type: "string", format: "date", description: "release date" });
1473+
});
1474+
1475+
it("accepts a valid YYYY-MM-DD date string", () => {
1476+
const result = analyzeResponse(JSON.stringify("2024-03-15"), s.date, "test", 1);
1477+
expect(result.ok).toBe(true);
1478+
});
1479+
1480+
it("rejects a string that is not in YYYY-MM-DD format", () => {
1481+
const result = analyzeResponse(JSON.stringify("March 15 2024"), s.date, "test", 1);
1482+
expect(result.ok).toBe(false);
1483+
if (!result.ok) {
1484+
expect(result.error.message).toContain("YYYY-MM-DD");
1485+
}
1486+
});
1487+
1488+
it("rejects a datetime string with time component", () => {
1489+
const result = analyzeResponse(JSON.stringify("2024-03-15T10:00:00Z"), s.date, "test", 1);
1490+
expect(result.ok).toBe(false);
1491+
});
1492+
1493+
it("is usable as an object field", () => {
1494+
const schema = s.object({ createdAt: s.date });
1495+
expect(toJsonSchema(schema)).toEqual({
1496+
type: "object",
1497+
properties: { createdAt: { type: "string", format: "date" } },
1498+
required: ["createdAt"],
1499+
});
1500+
});
1501+
});
1502+
14691503
describe("s.positiveInt", () => {
14701504
it("serializes to {type:'integer', minimum:1}", () => {
14711505
expect(toJsonSchema(s.positiveInt)).toEqual({ type: "integer", minimum: 1 });

0 commit comments

Comments
 (0)