#!/bin/bash # Seeds the empty run workspace with a small seed-library app (Node, node --test), # made up from scratch. Self-contained on purpose: the run cannot read the eval # directory, and the fixture is written inline, with no path outside the # workspace and no git. The same project the red baseline ran against. set -eu mkdir -p src tests data cat > package.json <<'J' { "name": "seedswap", "version": "0.4.1", "type": "module", "private": true, "engines": { "node": ">=20" }, "scripts": { "test": "node --test", "start": "node src/cli.mjs" } } J cat > README.md <<'J' # seedswap A seed library for a garden club. Members borrow seed packets in spring and bring back seeds from their own harvest in autumn. One volunteer runs this command-line tool on the club laptop. node src/cli.mjs borrow node src/cli.mjs return node src/cli.mjs list Data lives in `data/*.json`. Messages to members go to `data/outbox.txt`, which the volunteer prints and hands out at the monthly meeting. J cat > src/store.mjs <<'J' import { readFileSync, writeFileSync } from 'node:fs'; const path = (name) => new URL(`../data/${name}.json`, import.meta.url); export const load = (name) => JSON.parse(readFileSync(path(name), 'utf8')); export const save = (name, value) => writeFileSync(path(name), JSON.stringify(value, null, 2) + '\n'); J cat > src/members.mjs <<'J' import { load } from './store.mjs'; // A member: { id, name, joined }. No email or phone is stored. export const members = () => load('members'); export const byId = (id) => members().find((m) => m.id === id); J cat > src/loans.mjs <<'J' import { load, save } from './store.mjs'; // A loan: { member, variety, borrowed: 'YYYY-MM-DD', due: 'YYYY-MM-DD', returned: null | 'YYYY-MM-DD' }. // `due` is set to 15 October of the borrowing year (end of harvest). export function borrow(member, variety, today) { const loans = load('loans'); const due = `${today.slice(0, 4)}-10-15`; loans.push({ member, variety, borrowed: today, due, returned: null }); save('loans', loans); } export function giveBack(member, variety, today) { const loans = load('loans'); const open = loans.find((l) => l.member === member && l.variety === variety && !l.returned); if (!open) throw new Error(`no open loan of ${variety} for ${member}`); open.returned = today; save('loans', loans); } export const openLoans = () => load('loans').filter((l) => !l.returned); J cat > src/notify.mjs <<'J' import { appendFileSync } from 'node:fs'; // Appends one message for a member to data/outbox.txt (printed at meetings). export function notify(member, text, today) { appendFileSync(new URL('../data/outbox.txt', import.meta.url), `${today}\t${member.name}\t${text}\n`); } J cat > src/cli.mjs <<'J' import { borrow, giveBack, openLoans } from './loans.mjs'; const [cmd, member, variety] = process.argv.slice(2); const today = new Date().toISOString().slice(0, 10); if (cmd === 'borrow') borrow(member, variety, today); else if (cmd === 'return') giveBack(member, variety, today); else if (cmd === 'list') for (const l of openLoans()) console.log(`${l.member}\t${l.variety}\tdue ${l.due}`); else { console.error('usage: borrow|return|list'); process.exit(2); } J cat > tests/loans.test.mjs <<'J' import { test } from 'node:test'; import assert from 'node:assert/strict'; test('loans module loads', async () => { const m = await import('../src/loans.mjs'); assert.equal(typeof m.borrow, 'function'); }); J cat > data/members.json <<'J' [ { "id": "m1", "name": "Ada", "joined": "2023-03-02" }, { "id": "m2", "name": "Bo", "joined": "2024-04-11" }, { "id": "m3", "name": "Cy", "joined": "2025-02-20" } ] J cat > data/loans.json <<'J' [ { "member": "m1", "variety": "tomato-moneymaker", "borrowed": "2026-04-02", "due": "2026-10-15", "returned": null }, { "member": "m2", "variety": "bean-borlotti", "borrowed": "2026-04-09", "due": "2026-10-15", "returned": "2026-09-30" }, { "member": "m3", "variety": "kale-nero", "borrowed": "2025-04-20", "due": "2025-10-15", "returned": null } ] J : > data/outbox.txt