app/demo/greenfield-run.ts
zzmq.dev@gmail.com/zz / app / demo / greenfield-run.ts
@ 153aad69
Download
Ladder Summary
- file The script defines two asynchronous helper functions—**`step1_clamp`** and **`step2_average`**—that each build a set of source files (package.json, implementation files, tests, and an index) and submit them as a single “increment” via the `submitIncrement` API, logging the resulting commit OID. It also contains an internal `fileRung` factory used to describe each file’s ladder‑rung metadata, but the module does not export any functions or classes (the only exported symbols are the imported `submitIncrement` and `AUTHOR`).
// Runs one real increment at a time. Usage: tsx demo/greenfield-run.ts <step-name>
import { submitIncrement, AUTHOR } from "./greenfield.js";
import type { Rung } from "../engine/ladder.js";
const SLUG = "zz-demo/numkit";
const CLAMP_JS = `export function clamp(value, min, max) {
if (typeof value !== "number" || Number.isNaN(value)) return NaN;
if (min > max) throw new RangeError("min must be <= max");
return Math.min(Math.max(value, min), max);
}
`;
const CLAMP_TEST_JS = `import { test } from "node:test";
import assert from "node:assert/strict";
import { clamp } from "./clamp.js";
test("clamp keeps in-range values unchanged", () => {
assert.equal(clamp(5, 0, 10), 5);
});
test("clamp pulls values up to min", () => {
assert.equal(clamp(-5, 0, 10), 0);
});
test("clamp pulls values down to max", () => {
assert.equal(clamp(15, 0, 10), 10);
});
test("clamp returns NaN for NaN input", () => {
assert.ok(Number.isNaN(clamp(NaN, 0, 10)));
});
test("clamp returns NaN for non-number input", () => {
assert.ok(Number.isNaN(clamp("x", 0, 10)));
});
test("clamp throws when min > max", () => {
assert.throws(() => clamp(5, 10, 0), RangeError);
});
`;
const INDEX_JS = `export { clamp } from "./clamp.js";
`;
const PACKAGE_JSON = JSON.stringify(
{
name: "numkit",
version: "0.1.0",
description: "Small, dependency-free numeric utilities.",
type: "module",
main: "index.js",
scripts: { test: "node --test" },
},
null,
2,
) + "\n";
async function step1_clamp() {
const fileRung = (path: string, summary: string, commitOid: string): Rung => ({
path,
kind: "file",
summary,
sources: [{ kind: "commit", ref: commitOid, title: "Implement clamp(value, min, max)", author: AUTHOR.name, at: new Date().toISOString() }],
children: [],
});
const { commitOid } = await submitIncrement(SLUG, {
laneId: "implement-clamp",
name: "implement clamp",
objective: "Add a clamp(value, min, max) function: constrains a number to a range, returns NaN for non-numeric input.",
scope: ["clamp.js", "clamp.test.js", "index.js", "package.json"],
files: [
{ path: "package.json", content: PACKAGE_JSON },
{ path: "clamp.js", content: CLAMP_JS },
{ path: "clamp.test.js", content: CLAMP_TEST_JS },
{ path: "index.js", content: INDEX_JS },
],
message: "Implement clamp(value, min, max)",
buildLadderRungs: (commitOid) => [
fileRung("package.json", "The npm manifest for numkit, a small dependency-free numeric utilities package. Entry point is index.js; tests run via `node --test`.", commitOid),
fileRung("clamp.js", "Exports clamp(value, min, max), which constrains value to the [min, max] range. Returns NaN if value isn't a finite number (including NaN itself or a non-number); throws RangeError if min > max.", commitOid),
fileRung("clamp.test.js", "Tests for clamp: in-range values pass through unchanged, out-of-range values are pulled to the nearest bound, non-numeric/NaN input returns NaN, and min > max throws.", commitOid),
fileRung("index.js", "The package's entry point. Re-exports clamp from clamp.js.", commitOid),
],
});
console.log("step1_clamp committed:", commitOid);
}
const AVERAGE_JS = `export function average(numbers) {
const sum = numbers.reduce((a, b) => a + b, 0);
return sum / numbers.length;
}
`;
const AVERAGE_TEST_JS = `import { test } from "node:test";
import assert from "node:assert/strict";
import { average } from "./average.js";
test("average of a few numbers", () => {
assert.equal(average([1, 2, 3]), 2);
});
test("average of a single number is itself", () => {
assert.equal(average([5]), 5);
});
test("average handles negative numbers", () => {
assert.equal(average([-2, 2]), 0);
});
`;
const INDEX_JS_V2 = `export { clamp } from "./clamp.js";
export { average } from "./average.js";
`;
const PACKAGE_JSON_V2 = JSON.stringify(
{ name: "numkit", version: "0.2.0", description: "Small, dependency-free numeric utilities.", type: "module", main: "index.js", scripts: { test: "node --test" } },
null,
2,
) + "\n";
async function step2_average() {
const fileRung = (path: string, summary: string, commitOid: string): Rung => ({
path,
kind: "file",
summary,
sources: [{ kind: "commit", ref: commitOid, title: "Add average(numbers)", author: AUTHOR.name, at: new Date().toISOString() }],
children: [],
});
const { commitOid } = await submitIncrement(SLUG, {
laneId: "implement-average",
name: "implement average",
objective: "Add an average(numbers) function: the arithmetic mean of an array of numbers.",
scope: ["average.js", "average.test.js", "index.js", "package.json"],
files: [
{ path: "package.json", content: PACKAGE_JSON_V2 },
{ path: "average.js", content: AVERAGE_JS },
{ path: "average.test.js", content: AVERAGE_TEST_JS },
{ path: "index.js", content: INDEX_JS_V2 },
],
message: "Add average(numbers)",
buildLadderRungs: (commitOid) => [
fileRung("package.json", "The npm manifest for numkit, a small dependency-free numeric utilities package, now at 0.2.0 with the addition of average(). Entry point is index.js; tests run via `node --test`.", commitOid),
fileRung(
"average.js",
"Exports average(numbers), the arithmetic mean of an array: sums with reduce and divides by length. Not yet guarded against an empty array -- that divides 0 by 0 and silently returns NaN rather than a clear error.",
commitOid,
),
fileRung("average.test.js", "Tests for average: a few positive numbers, a single-element array, and negative numbers summing to zero. No empty-array case yet.", commitOid),
fileRung("index.js", "The package's entry point. Re-exports clamp from clamp.js and average from average.js.", commitOid),
],
});
console.log("step2_average committed:", commitOid);
}
const AVERAGE_JS_V2 = `export function average(numbers) {
if (numbers.length === 0) throw new RangeError("average: numbers must not be empty");
const sum = numbers.reduce((a, b) => a + b, 0);
return sum / numbers.length;
}
`;
const AVERAGE_TEST_JS_V2 = `import { test } from "node:test";
import assert from "node:assert/strict";
import { average } from "./average.js";
test("average of a few numbers", () => {
assert.equal(average([1, 2, 3]), 2);
});
test("average of a single number is itself", () => {
assert.equal(average([5]), 5);
});
test("average handles negative numbers", () => {
assert.equal(average([-2, 2]), 0);
});
test("average throws on an empty array instead of silently returning NaN", () => {
assert.throws(() => average([]), RangeError);
});
`;
async function step3_averageEmptyFix() {
const fileRung = (path: string, summary: string, commitOid: string): Rung => ({
path,
kind: "file",
summary,
sources: [{ kind: "commit", ref: commitOid, title: "Fix average([]) silently returning NaN", author: AUTHOR.name, at: new Date().toISOString() }],
children: [],
});
const { commitOid } = await submitIncrement(SLUG, {
laneId: "fix-average-empty",
name: "fix average([])",
objective: "average([]) currently divides 0 by 0 and silently returns NaN. Throw a clear RangeError instead.",
scope: ["average.js", "average.test.js"],
files: [
{ path: "average.js", content: AVERAGE_JS_V2 },
{ path: "average.test.js", content: AVERAGE_TEST_JS_V2 },
],
message: "Fix average([]) silently returning NaN",
buildLadderRungs: (commitOid) => [
fileRung("average.js", "Exports average(numbers), the arithmetic mean of an array. Throws RangeError for an empty array instead of silently dividing 0 by 0 into NaN; otherwise sums with reduce and divides by length.", commitOid),
fileRung("average.test.js", "Tests for average: a few positive numbers, a single-element array, negative numbers summing to zero, and (added in this fix) that an empty array throws RangeError rather than returning NaN.", commitOid),
],
});
console.log("step3_averageEmptyFix committed:", commitOid);
}
const ISPRIME_JS = `export function isPrime(n) {
if (!Number.isInteger(n) || n < 2) return false;
if (n === 2) return true;
if (n % 2 === 0) return false;
for (let i = 3; i * i <= n; i += 2) {
if (n % i === 0) return false;
}
return true;
}
`;
const ISPRIME_TEST_JS = `import { test } from "node:test";
import assert from "node:assert/strict";
import { isPrime } from "./isPrime.js";
test("small primes", () => {
assert.equal(isPrime(2), true);
assert.equal(isPrime(3), true);
assert.equal(isPrime(17), true);
assert.equal(isPrime(97), true);
});
test("small non-primes", () => {
assert.equal(isPrime(4), false);
assert.equal(isPrime(1), false);
assert.equal(isPrime(100), false);
});
test("non-positive and non-integer input is not prime", () => {
assert.equal(isPrime(0), false);
assert.equal(isPrime(-5), false);
assert.equal(isPrime(2.5), false);
});
`;
const INDEX_JS_V3 = `export { clamp } from "./clamp.js";
export { average } from "./average.js";
export { isPrime } from "./isPrime.js";
`;
const PACKAGE_JSON_V3 = JSON.stringify(
{ name: "numkit", version: "0.3.0", description: "Small, dependency-free numeric utilities.", type: "module", main: "index.js", scripts: { test: "node --test" } },
null,
2,
) + "\n";
async function step4_isPrime() {
const fileRung = (path: string, summary: string, commitOid: string): Rung => ({
path,
kind: "file",
summary,
sources: [{ kind: "commit", ref: commitOid, title: "Add isPrime(n)", author: AUTHOR.name, at: new Date().toISOString() }],
children: [],
});
const { commitOid } = await submitIncrement(SLUG, {
laneId: "implement-isprime",
name: "implement isPrime",
objective: "Add an isPrime(n) function: true for integers >= 2 with no divisors other than 1 and themselves.",
scope: ["isPrime.js", "isPrime.test.js", "index.js", "package.json"],
files: [
{ path: "package.json", content: PACKAGE_JSON_V3 },
{ path: "isPrime.js", content: ISPRIME_JS },
{ path: "isPrime.test.js", content: ISPRIME_TEST_JS },
{ path: "index.js", content: INDEX_JS_V3 },
],
message: "Add isPrime(n)",
buildLadderRungs: (commitOid) => [
fileRung("package.json", "The npm manifest for numkit, now at 0.3.0 with clamp, average, and isPrime. Entry point is index.js; tests run via `node --test`.", commitOid),
fileRung("isPrime.js", "Exports isPrime(n): true for integers >= 2 with no divisors other than 1 and itself, checked by trial division up to sqrt(n), skipping even divisors after 2. False for non-integers, numbers below 2, and negatives.", commitOid),
fileRung("isPrime.test.js", "Tests for isPrime: small primes, small non-primes, and non-positive/non-integer input (0, negatives, non-integers all false).", commitOid),
fileRung("index.js", "The package's entry point. Re-exports clamp, average, and isPrime.", commitOid),
],
});
console.log("step4_isPrime committed:", commitOid);
}
const step = process.argv[2];
const steps: Record<string, () => Promise<void>> = { clamp: step1_clamp, average: step2_average, "fix-average-empty": step3_averageEmptyFix, isprime: step4_isPrime };
if (!step || !steps[step]) {
console.error("usage: tsx demo/greenfield-run.ts <" + Object.keys(steps).join("|") + ">");
process.exit(1);
}
await steps[step]();