Repository navigation
Align service deadline checks with tracking window #17
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,46 +1,38 @@ | ||
| #!/usr/bin/env node | ||
| // Reads every paid client's Day 0 record and reports how long is left before | ||
| // the internal target and before the refund line. Nothing here mutates state. | ||
| // Reads every paid client's Day 0 record and reports the internal attention | ||
| // status for the Website Correction's 14-day implementation-tracking window. | ||
| // Nothing here mutates state. | ||
| // | ||
| // Two clocks, deliberately: | ||
| // internal target serviceDeadlineAt() — 7 business days, the "7-Day Sprint" promise | ||
| // refund line 14 working days from Day 0, the public delivery guarantee | ||
| // The gap between them is the week of warning before money has to go back. | ||
| // The only contract deadline in the product is the 14-day implementation- | ||
| // tracking window that starts when a human accepts the implementation pass; | ||
| // client delay pauses the clock. The service makes no delivery, revenue, or | ||
| // outcome promise, so this check never reports a customer-facing deadline: | ||
| // clients whose tracking window has not started simply have no deadline | ||
| // applicable. | ||
| // | ||
| // Exit codes: 0 clear · 1 something needs attention today · 2 the check itself broke. | ||
|
|
||
| import {existsSync, readdirSync} from "node:fs" | ||
| import {join} from "node:path" | ||
| import {readJson, resolveRepoPath} from "./lib/service-contract.mjs" | ||
| import {serviceDeadlineAt} from "./lib/service-artifacts.mjs" | ||
| import {addBusinessDaysToTimestamp, businessMillisecondsBetween} from "./date-utils.mjs" | ||
| import {serviceTrackingWindowEndAt} from "./lib/service-artifacts.mjs" | ||
| import {businessMillisecondsBetween} from "./date-utils.mjs" | ||
|
|
||
| const REFUND_BUSINESS_DAYS = 14 | ||
| const WARN_WITHIN_DAYS = 2 | ||
| const DAY_MS = 86400000 | ||
|
|
||
| const repoRoot = process.cwd() | ||
| const asOf = process.env.SERVICE_DEADLINE_NOW || new Date().toISOString() | ||
|
|
||
| // businessMillisecondsBetween refuses a reversed range, so measure the gap in | ||
| // whichever direction is valid and carry the sign ourselves. A past deadline is | ||
| // the whole point of this check — it must not throw. | ||
| // whichever direction is valid and carry the sign ourselves. A past tracking | ||
| // window is the whole point of this check — it must not throw. | ||
| function businessDaysBetween(from, to) { | ||
| const overdue = Date.parse(to) < Date.parse(from) | ||
| const ms = overdue ? -businessMillisecondsBetween(to, from) : businessMillisecondsBetween(from, to) | ||
| return Math.round((ms / DAY_MS) * 10) / 10 | ||
| } | ||
|
|
||
| function refundLineAt(day0StartedAt, pauseHistory) { | ||
| const pausedMs = (pauseHistory || []).reduce( | ||
| (total, pause) => total + businessMillisecondsBetween(pause.startedAt, pause.endedAt), | ||
| 0, | ||
| ) | ||
| const base = addBusinessDaysToTimestamp(day0StartedAt, REFUND_BUSINESS_DAYS) | ||
| // Pauses push the refund line out by the same business time the clock was stopped. | ||
| return new Date(Date.parse(base) + pausedMs).toISOString() | ||
| } | ||
|
|
||
| function loadClients() { | ||
| const dir = resolveRepoPath(repoRoot, "clients") | ||
| if (!existsSync(dir)) return [] | ||
|
|
@@ -49,36 +41,49 @@ function loadClients() { | |
| .map((entry) => { | ||
| const path = join(dir, entry.name, "service-day0.json") | ||
| if (!existsSync(path)) return null | ||
| return {id: entry.name, day0: readJson(path)} | ||
| return {id: entry.name, folder: join(dir, entry.name), day0: readJson(path)} | ||
| }) | ||
| .filter(Boolean) | ||
| } | ||
|
|
||
| function loadState(folder) { | ||
| const path = join(folder, "service-state.json") | ||
| if (!existsSync(path)) return null | ||
| const state = readJson(path) | ||
| return {state: state?.state || "", implementationAcceptedAt: state?.implementationAcceptedAt || ""} | ||
| } | ||
|
|
||
| function assess(client) { | ||
| const {id, day0} = client | ||
| const startedAt = day0.day0StartedAt | ||
| if (!startedAt) return {id, status: "no-day0", detail: "Day 0 not recorded"} | ||
|
|
||
| const paused = Boolean(day0.activePause && day0.activePause.startedAt && !day0.activePause.endedAt) | ||
| const target = day0.deadlineAt || serviceDeadlineAt(startedAt, day0.pauseHistory || []) | ||
| const refund = refundLineAt(startedAt, day0.pauseHistory || []) | ||
|
|
||
| if (paused) { | ||
| return { | ||
| id, paused: true, status: "paused", target, refund, | ||
| id, paused: true, status: "paused", | ||
| detail: `clock paused since ${day0.activePause.startedAt} — ${day0.activePause.reason || "no reason recorded"}`, | ||
| } | ||
| } | ||
|
|
||
| const toTarget = businessDaysBetween(asOf, target) | ||
| const toRefund = businessDaysBetween(asOf, refund) | ||
| const state = loadState(client.folder) | ||
| if (!state) { | ||
| return {id, paused: false, status: "state-missing", detail: "service state not recorded — cannot assess the 14-day tracking window"} | ||
| } | ||
|
|
||
| let status = "on-track" | ||
| if (toRefund <= 0) status = "REFUND-DUE" | ||
| else if (toTarget <= 0) status = "TARGET-MISSED" | ||
| else if (toTarget <= WARN_WITHIN_DAYS) status = "due-soon" | ||
| if (state.state === "tracking-14-day" && state.implementationAcceptedAt) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a tracking review returns Useful? React with 👍 / 👎. |
||
| const windowEnd = serviceTrackingWindowEndAt(state.implementationAcceptedAt, day0.pauseHistory || []) | ||
| const toEnd = businessDaysBetween(asOf, windowEnd) | ||
| let status = "on-track" | ||
| if (toEnd <= 0) status = "TRACKING-OVERDUE" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If implementation is accepted on a weekend, the 14-calendar-day window also ends on a weekend; before that end instant, Useful? React with 👍 / 👎. |
||
| else if (toEnd <= WARN_WITHIN_DAYS) status = "tracking-due-soon" | ||
| return {id, paused: false, status, windowEnd, toEnd} | ||
| } | ||
|
|
||
| return {id, paused: false, status, target, refund, toTarget, toRefund} | ||
| return { | ||
| id, paused: false, status: "tracking-not-started", | ||
| detail: `14-day implementation tracking has not started (current state: ${state.state || "unknown"}); no delivery deadline applies`, | ||
| } | ||
| } | ||
|
|
||
| function main() { | ||
|
|
@@ -88,24 +93,21 @@ function main() { | |
| return 0 | ||
| } | ||
|
|
||
| const rank = {"REFUND-DUE": 0, "TARGET-MISSED": 1, "due-soon": 2, "no-day0": 3, paused: 4, "on-track": 5} | ||
| const rank = {"TRACKING-OVERDUE": 0, "tracking-due-soon": 1, "state-missing": 2, "no-day0": 3, "tracking-not-started": 4, paused: 5, "on-track": 6} | ||
| rows.sort((a, b) => rank[a.status] - rank[b.status]) | ||
|
|
||
| console.log(`TinyStudio service deadlines — as of ${asOf}\n`) | ||
| console.log(`TinyStudio service tracking — as of ${asOf}\n`) | ||
| for (const row of rows) { | ||
| const head = `${row.status.padEnd(14)} ${row.id}` | ||
| if (row.status === "no-day0" || row.paused) { | ||
| const head = `${row.status.padEnd(20)} ${row.id}` | ||
| if (row.status === "no-day0" || row.status === "state-missing" || row.status === "tracking-not-started" || row.paused) { | ||
| console.log(`${head} ${row.detail}`) | ||
| continue | ||
| } | ||
| console.log( | ||
| `${head} ${row.toTarget} business days to internal target, ` + | ||
| `${row.toRefund} to the refund line (${row.refund.slice(0, 10)})`, | ||
| ) | ||
| console.log(`${head} ${row.toEnd} business days to the 14-day tracking-window end (${row.windowEnd.slice(0, 10)})`) | ||
| } | ||
|
|
||
| const needsAttention = rows.filter((row) => | ||
| row.status === "REFUND-DUE" || row.status === "TARGET-MISSED" || row.status === "due-soon" || row.status === "no-day0", | ||
| row.status === "TRACKING-OVERDUE" || row.status === "tracking-due-soon" || row.status === "state-missing" || row.status === "no-day0", | ||
| ) | ||
| if (needsAttention.length > 0) { | ||
| console.log(`\n${needsAttention.length} client(s) need attention today.`) | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -90,6 +90,30 @@ export function serviceDeadlineAt(day0StartedAt, pauseHistory = []) { | |||||||||||||||||||
| const pausedBusinessMs = pauseHistory.reduce((total, pause) => total + businessMillisecondsBetween(pause.startedAt, pause.endedAt), 0) | ||||||||||||||||||||
| return addBusinessMillisecondsToTimestamp(addBusinessDaysToTimestamp(day0StartedAt, 7), pausedBusinessMs) | ||||||||||||||||||||
| } | ||||||||||||||||||||
| const TRACKING_WINDOW_MS = 14 * 86400000 | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // The Website Correction contract tracks 14 active days after human acceptance | ||||||||||||||||||||
| // of the implementation pass; client-delay pauses push the window out by the | ||||||||||||||||||||
| // same wall time the clock was stopped (tracking stage evidence requires at | ||||||||||||||||||||
| // least 14 active days between acceptance and the tracked-through instant). | ||||||||||||||||||||
| // Pauses before acceptance or after the window end do not move it. | ||||||||||||||||||||
| export function serviceTrackingWindowEndAt(implementationAcceptedAt, pauseHistory = []) { | ||||||||||||||||||||
| const anchor = Date.parse(implementationAcceptedAt) | ||||||||||||||||||||
| if (Number.isNaN(anchor)) throw new Error("implementation acceptance timestamp is invalid") | ||||||||||||||||||||
| let end = anchor + TRACKING_WINDOW_MS | ||||||||||||||||||||
| for (let pass = 0; pass <= pauseHistory.length + 2; pass += 1) { | ||||||||||||||||||||
| const pausedWithin = pauseHistory.reduce((total, pause) => { | ||||||||||||||||||||
| const start = Date.parse(pause.startedAt) | ||||||||||||||||||||
| const stop = Date.parse(pause.endedAt) | ||||||||||||||||||||
| if (Number.isNaN(start) || Number.isNaN(stop)) throw new Error("tracking pause interval is invalid") | ||||||||||||||||||||
| return total + Math.max(0, Math.min(end, stop) - Math.max(anchor, start)) | ||||||||||||||||||||
|
Comment on lines
+106
to
+109
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Reject pause intervals that end before they start.
Proposed fix const start = Date.parse(pause.startedAt)
const stop = Date.parse(pause.endedAt)
if (Number.isNaN(start) || Number.isNaN(stop)) throw new Error("tracking pause interval is invalid")
+ if (stop < start) throw new Error("tracking pause interval is invalid")
return total + Math.max(0, Math.min(end, stop) - Math.max(anchor, start))📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||
| }, 0) | ||||||||||||||||||||
| const next = anchor + TRACKING_WINDOW_MS + pausedWithin | ||||||||||||||||||||
| if (next === end) break | ||||||||||||||||||||
| end = next | ||||||||||||||||||||
| } | ||||||||||||||||||||
| return new Date(end).toISOString() | ||||||||||||||||||||
| } | ||||||||||||||||||||
| export function assertExactKeys(value, expected, name) { | ||||||||||||||||||||
| assert(value && typeof value === "object" && !Array.isArray(value), `${name} must be an object`) | ||||||||||||||||||||
| const actual = Object.keys(value).sort() | ||||||||||||||||||||
|
|
@@ -457,7 +481,7 @@ export function validateDay0Record(value, applicationId) { | |||||||||||||||||||
| const {total} = checkedPauseIntervals(value.pauseHistory, value.activePause, Date.parse(value.day0StartedAt), "Day 0") | ||||||||||||||||||||
| assert(Number.isInteger(value.totalPausedMs) && value.totalPausedMs === total, "Day 0 totalPausedMs mismatch") | ||||||||||||||||||||
| const expectedDeadline = serviceDeadlineAt(value.day0StartedAt, value.pauseHistory) | ||||||||||||||||||||
| assert(value.deadlineAt === expectedDeadline, "Day 0 deadline must preserve seven working days and exclude paused time") | ||||||||||||||||||||
| assert(value.deadlineAt === expectedDeadline, "Day 0 deadline must match the recorded deadline and exclude paused time") | ||||||||||||||||||||
| return value | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,6 +27,7 @@ import { | |
| validateStageEvidence | ||
| } from "./lib/review-queue.mjs" | ||
| import {assertCanonicalFounderPilotCohort, assertClientScaffold, FOUNDER_PILOT} from "./lib/client-scaffold.mjs" | ||
| import {serviceTrackingWindowEndAt} from "./lib/service-artifacts.mjs" | ||
| import {createPromotionJournal, promotionMarkerPath, validatePromotionJournal} from "./lib/service-promotion-journal.mjs" | ||
| import {commitJournaledTransition, transitionJournalRecord} from "./lib/service-transition-journal.mjs" | ||
| import {addBusinessDaysToTimestamp, businessMillisecondsBetween, localEndOfIsoDate, localIsoDate, timestampIsOnOrBeforeLocalDate, timestampIsOnOrBeforeTrustedNow, trustedNow} from "./date-utils.mjs" | ||
|
|
@@ -443,6 +444,11 @@ try { | |
| eq(businessMillisecondsBetween("2026-07-11T10:00:00.000Z", "2026-07-12T10:00:00.000Z"), 0) | ||
| eq(serviceDeadlineAt("2026-07-10T10:00:00.000+05:30", [{reason: "Weekend client delay", startedAt: "2026-07-11T10:00:00.000+05:30", endedAt: "2026-07-12T10:00:00.000+05:30", durationMs: 86400000}]), "2026-07-21T10:00:00.000+05:30") | ||
| eq(serviceDeadlineAt("2026-07-16T00:15:00.000+05:30"), "2026-07-27T00:15:00.000+05:30") | ||
| eq(serviceTrackingWindowEndAt("2026-07-13T10:00:00.000Z"), "2026-07-27T10:00:00.000Z") | ||
| eq(serviceTrackingWindowEndAt("2026-07-13T10:00:00.000Z", [{reason: "Client access delay", startedAt: "2026-07-15T10:00:00.000Z", endedAt: "2026-07-17T10:00:00.000Z", durationMs: 172800000}]), "2026-07-29T10:00:00.000Z") | ||
| eq(serviceTrackingWindowEndAt("2026-07-13T10:00:00.000Z", [{reason: "Pre-acceptance delay", startedAt: "2026-07-11T10:00:00.000Z", endedAt: "2026-07-12T10:00:00.000Z", durationMs: 86400000}]), "2026-07-27T10:00:00.000Z") | ||
| eq(serviceTrackingWindowEndAt("2026-07-13T10:00:00.000Z", [{reason: "Straddling delay", startedAt: "2026-07-25T10:00:00.000Z", endedAt: "2026-07-28T10:00:00.000Z", durationMs: 259200000}]), "2026-07-30T10:00:00.000Z") | ||
| thr(() => serviceTrackingWindowEndAt("not-a-date"), /implementation acceptance timestamp is invalid/) | ||
|
Comment on lines
+447
to
+451
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Add coverage for an inverted pause interval. The new tests reject an invalid acceptance timestamp. They do not reject a pause where 🤖 Prompt for AI Agents |
||
|
|
||
| const futureApplication = {...application, applicationId: "018f5a54-84aa-7ae0-a1fd-4da350490779", submittedAt: nextLocalMidnight} | ||
| const futureApplicationPath = rp("future-application.json") | ||
|
|
@@ -2126,6 +2132,81 @@ try { | |
| rm(backupParent, {recursive: true, force: true}) | ||
| } | ||
|
|
||
| // ——— check-service-deadlines.mjs: 14-day tracking attention status ——— | ||
| const deadlineScenarios = [] | ||
| try { | ||
| const scenario = () => { | ||
| const dir = mkdtempSync(join(tmpdir(), "tinystudio-deadline-scenario-")) | ||
| md(join(dir, "clients"), {recursive: true}) | ||
| deadlineScenarios.push(dir) | ||
| return dir | ||
| } | ||
| const runDeadlineCheck = (dir, now) => spawnSync(process.execPath, [join(process.cwd(), "scripts/check-service-deadlines.mjs")], {cwd: dir, env: {...process.env, SERVICE_DEADLINE_NOW: now}, encoding: "utf8"}) | ||
| const day0Fixture = (id, startedAt, pauseHistory = [], activePause = null) => ({ | ||
| applicationId: id, paymentEvidence: `paid: invoice ${id}`, requiredContext: "approved context", | ||
| approvalOwner: "Founder", implementationOwner: "TinyStudio", offerName: "The Website Correction", | ||
| offerPriceUsd: 1000, pricingCohort: "founder-pilot", pilotSequence: 1, ready: true, | ||
| day0StartedAt: startedAt, updatedAt: startedAt, paused: Boolean(activePause), activePause, | ||
| pauseHistory, totalPausedMs: pauseHistory.reduce((total, pause) => total + pause.durationMs, 0), | ||
| deadlineAt: addBusinessDaysToTimestamp(startedAt, 7), resumeState: activePause ? "tracking-14-day" : "" | ||
| }) | ||
| const writeClient = (dir, id, day0, state) => { | ||
| const folder = join(dir, "clients", id) | ||
| md(folder, {recursive: true}) | ||
| aw(join(folder, "service-day0.json"), day0) | ||
| if (state) aw(join(folder, "service-state.json"), state) | ||
| } | ||
| const retiredPromiseLanguage = /\b(?:seven[- ]day|7[- ]day)\b|refund|guarantee|sprint/i | ||
|
|
||
| const trackingDir = scenario() | ||
| const trackingId = "110f5a54-84aa-7ae0-a1fd-4da350490001" | ||
| writeClient(trackingDir, trackingId, day0Fixture(trackingId, "2026-07-01T10:00:00.000Z"), {state: "tracking-14-day", implementationAcceptedAt: "2026-07-13T10:00:00.000Z"}) | ||
| const onTrack = runDeadlineCheck(trackingDir, "2026-07-20T10:00:00.000Z") | ||
| eq(onTrack.status, 0, onTrack.stdout) | ||
| mat(onTrack.stdout, new RegExp(`on-track\\s+${trackingId}\\s+5 business days to the 14-day tracking-window end \\(2026-07-27\\)`)) | ||
| mat(onTrack.stdout, /All clients on track\./) | ||
| const dueSoon = runDeadlineCheck(trackingDir, "2026-07-24T10:00:00.000Z") | ||
| eq(dueSoon.status, 1) | ||
| mat(dueSoon.stdout, new RegExp(`tracking-due-soon\\s+${trackingId}`)) | ||
| const overdue = runDeadlineCheck(trackingDir, "2026-07-28T10:00:00.000Z") | ||
| eq(overdue.status, 1) | ||
| mat(overdue.stdout, new RegExp(`TRACKING-OVERDUE\\s+${trackingId}\\s+-1 business days to the 14-day tracking-window end`)) | ||
|
|
||
| const pausedDir = scenario() | ||
| const pausedId = "220f5a54-84aa-7ae0-a1fd-4da350490002" | ||
| const clientDelay = [{reason: "Client access delay", startedAt: "2026-07-15T10:00:00.000Z", endedAt: "2026-07-17T10:00:00.000Z", durationMs: 172800000}] | ||
| writeClient(pausedDir, pausedId, day0Fixture(pausedId, "2026-07-01T10:00:00.000Z", clientDelay), {state: "tracking-14-day", implementationAcceptedAt: "2026-07-13T10:00:00.000Z"}) | ||
| const extended = runDeadlineCheck(pausedDir, "2026-07-24T10:00:00.000Z") | ||
| eq(extended.status, 0, extended.stdout) | ||
| mat(extended.stdout, new RegExp(`on-track\\s+${pausedId}\\s+3 business days to the 14-day tracking-window end \\(2026-07-29\\)`)) | ||
| eq(runDeadlineCheck(pausedDir, "2026-07-30T10:00:00.000Z").status, 1) | ||
|
|
||
| const mixedDir = scenario() | ||
| const preTrackingId = "330f5a54-84aa-7ae0-a1fd-4da350490003" | ||
| writeClient(mixedDir, preTrackingId, day0Fixture(preTrackingId, "2026-07-01T10:00:00.000Z"), {state: "implementation"}) | ||
| const clockPausedId = "440f5a54-84aa-7ae0-a1fd-4da350490004" | ||
| writeClient(mixedDir, clockPausedId, day0Fixture(clockPausedId, "2026-07-01T10:00:00.000Z", [], {reason: "Client access pending", startedAt: "2026-07-05T10:00:00.000Z"}), {state: "tracking-14-day", implementationAcceptedAt: "2026-07-13T10:00:00.000Z"}) | ||
| const noDay0Id = "550f5a54-84aa-7ae0-a1fd-4da350490005" | ||
| md(join(mixedDir, "clients", noDay0Id), {recursive: true}) | ||
| aw(join(mixedDir, "clients", noDay0Id, "service-day0.json"), {}) | ||
|
Comment on lines
+2189
to
+2191
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Test a missing Day 0 file. This fixture creates Create the client directory without 🤖 Prompt for AI Agents |
||
| const stateMissingId = "660f5a54-84aa-7ae0-a1fd-4da350490006" | ||
| md(join(mixedDir, "clients", stateMissingId), {recursive: true}) | ||
| aw(join(mixedDir, "clients", stateMissingId, "service-day0.json"), day0Fixture(stateMissingId, "2026-07-01T10:00:00.000Z")) | ||
| const mixed = runDeadlineCheck(mixedDir, "2026-07-24T10:00:00.000Z") | ||
| eq(mixed.status, 1) | ||
| mat(mixed.stdout, new RegExp(`tracking-not-started\\s+${preTrackingId}.*14-day implementation tracking has not started \\(current state: implementation\\); no delivery deadline applies`)) | ||
| mat(mixed.stdout, new RegExp(`paused\\s+${clockPausedId}.*clock paused since 2026-07-05T10:00:00.000Z — Client access pending`)) | ||
| mat(mixed.stdout, new RegExp(`no-day0\\s+${noDay0Id}.*Day 0 not recorded`)) | ||
| mat(mixed.stdout, new RegExp(`state-missing\\s+${stateMissingId}.*service state not recorded`)) | ||
| mat(mixed.stdout, /2 client\(s\) need attention today\./) | ||
|
|
||
| for (const output of [onTrack.stdout, dueSoon.stdout, overdue.stdout, extended.stdout, mixed.stdout]) { | ||
| assert(!retiredPromiseLanguage.test(output), "check-service-deadlines output revived retired seven-day/refund language") | ||
| } | ||
| } finally { | ||
| for (const dir of deadlineScenarios) rm(dir, {recursive: true, force: true}) | ||
| } | ||
|
|
||
| assert(ALLOWED_COMMANDS.every(argv => Array.isArray(argv) && argv.every(part => typeof part === "string"))) | ||
| const engineSource = [rf(join(process.cwd(), "scripts/lib/review-queue.mjs"), "utf8"), rf(join(process.cwd(), QUEUE), "utf8")].join("\n") | ||
| assert(!/node:child_process|\bfetch\s*\(|\bexec(?:File)?\s*\(|\bspawn\s*\(/.test(engineSource)) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Retain clients that have no Day 0 file.
When
service-day0.jsonis absent, this code returnsnulland removes the client. The checker then cannot emitno-day0or return exit code1for that client. Keep the directory inloadClientsand use an empty Day 0 value soassessreports the missing record.Proposed fix
.map((entry) => { const path = join(dir, entry.name, "service-day0.json") - if (!existsSync(path)) return null - return {id: entry.name, folder: join(dir, entry.name), day0: readJson(path)} + return { + id: entry.name, + folder: join(dir, entry.name), + day0: existsSync(path) ? readJson(path) : {}, + } }) - .filter(Boolean)📝 Committable suggestion
🤖 Prompt for AI Agents