diff --git a/packages/cli/src/__tests__/task-list-board-columns.test.ts b/packages/cli/src/__tests__/task-list-board-columns.test.ts index 7cd12f1a39..5f6b35d8b5 100644 --- a/packages/cli/src/__tests__/task-list-board-columns.test.ts +++ b/packages/cli/src/__tests__/task-list-board-columns.test.ts @@ -1,5 +1,5 @@ /* -FNXC:CliBoardVocabulary 2026-07-30-24:40: +FNXC:CliBoardVocabulary 2026-07-30-23:40: THE INVARIANT: `fn task list` prints every card, whatever its board calls the lane. `runTaskList` iterated the six-id `COLUMNS` constant and filtered `t.column === col`, so a card in a diff --git a/packages/cli/src/commands/task.ts b/packages/cli/src/commands/task.ts index 0910150670..d449ca0bf4 100644 --- a/packages/cli/src/commands/task.ts +++ b/packages/cli/src/commands/task.ts @@ -26,7 +26,7 @@ function columnLabel(column: ColumnId): string { } /* -FNXC:CliBoardVocabulary 2026-07-30-24:40: +FNXC:CliBoardVocabulary 2026-07-30-23:40: The lanes `fn task list` prints, derived from the CARDS rather than from the legacy enum. `runTaskList` iterated the six-id `COLUMNS` constant and filtered `t.column === col`, so a task in a @@ -633,7 +633,7 @@ export async function runTaskList(projectName?: string) { } /* - FNXC:CliBoardVocabulary 2026-07-30-24:40: + FNXC:CliBoardVocabulary 2026-07-30-23:40: Iterate the columns the BOARD has, not the legacy six — a renamed card was not printed AT ALL. This loop ran `for (const col of COLUMNS)` and filtered `t.column === col`, so any task in a diff --git a/scripts/__tests__/recover-stale-blocked-by.test.mjs b/scripts/__tests__/recover-stale-blocked-by.test.mjs index fdbb4b57e3..7c3e85c125 100644 --- a/scripts/__tests__/recover-stale-blocked-by.test.mjs +++ b/scripts/__tests__/recover-stale-blocked-by.test.mjs @@ -119,7 +119,7 @@ test("treats soft-deleted blockers as missing and never plans for deleted depend }); /* -FNXC:OperatorScriptLaneAssumptions 2026-07-30-25:30: +FNXC:OperatorScriptLaneAssumptions 2026-07-30-23:30: THE INVARIANT: a board this script cannot reason about is REPORTED, never silently skipped. Every lane test in the planner is a legacy id, and the candidate gate is `row.column !== "todo"`, so a diff --git a/scripts/check-fnxc-future-dates.mjs b/scripts/check-fnxc-future-dates.mjs index 3cd1710b60..52e38a2ae6 100644 --- a/scripts/check-fnxc-future-dates.mjs +++ b/scripts/check-fnxc-future-dates.mjs @@ -41,6 +41,32 @@ shape the rule actually prescribes, which is the worst possible subset to miss. */ const STAMP = /FNXC:[A-Za-z0-9_-]+\s+(\d{4}-\d{2}-\d{2})/g; +/* +FNXC:FnxcStampHygiene 2026-07-30-21:40: +THE HOUR WAS NEVER VALIDATED, so `2026-07-30-25:30` passed this gate. + +`STAMP` captures only the date, and the future check compares that capture alone — a stamp could +carry any `hh:mm` at all. Four stamps on `main` already read `-24:40` or `-24:00`, and a fifth +`-25:30` arrived with the next PR. AGENTS.md specifies `yyyy-MM-dd-hh:mm`, where `hh` is a clock +hour, and the whole point of the stamp is to make the FNXC record a readable chronology; a time that +cannot exist quietly costs it that. + +Counted per file alongside the future-dated population rather than as a separate gate, because it is +the same defect class — a stamp that does not describe a real moment — and one ratchet is cheaper to +keep honest than two. +*/ +const STAMP_TIME = /FNXC:[A-Za-z0-9_-]+\s+\d{4}-\d{2}-\d{2}-(\d{2}):(\d{2})/g; + +/** Hours 00-23, minutes 00-59. Returns the count of stamps whose clock time cannot exist. */ +function impossibleClockTimes(source) { + let bad = 0; + STAMP_TIME.lastIndex = 0; + for (const match of source.matchAll(STAMP_TIME)) { + if (Number(match[1]) > 23 || Number(match[2]) > 59) bad += 1; + } + return bad; +} + function* walk(dir) { for (const entry of readdirSync(dir)) { if (SKIP_DIRS.has(entry)) continue; @@ -87,6 +113,7 @@ function scan() { STAMP.lastIndex = 0; let hits = 0; for (const match of source.matchAll(STAMP)) if (match[1] > today) hits += 1; + hits += impossibleClockTimes(source); if (hits > 0) counts[relative(REPO, file).split("\\").join("/")] = hits; } } diff --git a/scripts/check-lane-wiring.mjs b/scripts/check-lane-wiring.mjs index 6491e2a737..af4921df1d 100644 --- a/scripts/check-lane-wiring.mjs +++ b/scripts/check-lane-wiring.mjs @@ -15,7 +15,7 @@ import { findLaneAcceptingFunctions, findUnwiredCallSites } from "./lib/lane-wir const ROOT = process.cwd(); const BASELINE = join(ROOT, "scripts/lib/lane-wiring-baseline.json"); /* -FNXC:WorkflowLifecycleColumns 2026-07-30-24:00: +FNXC:WorkflowLifecycleColumns 2026-07-30-23:00: `packages/dashboard/app` and `plugins` are scanned, and `.tsx` counts — their absence was the blind spot the OLDER guard already learned about and this one re-opened. diff --git a/scripts/recover-stale-blocked-by.mjs b/scripts/recover-stale-blocked-by.mjs index 068496ecda..5e1678e098 100644 --- a/scripts/recover-stale-blocked-by.mjs +++ b/scripts/recover-stale-blocked-by.mjs @@ -66,7 +66,7 @@ function isTerminalColumn(column) { } /* -FNXC:OperatorScriptLaneAssumptions 2026-07-30-25:30: +FNXC:OperatorScriptLaneAssumptions 2026-07-30-23:30: Refuse to look healthy on a board this script cannot reason about. Every lane test here is a legacy id: `isTerminalColumn` is done/archived, "active" is