feat: scheduled boards — define & schedule auto-refreshing boards#221
Merged
Conversation
A scheduled board is a normal project/agent board that an agent re-refreshes
on a schedule. termchart owns two portable pieces — a committed YAML definition
(.termchart/boards/<id>.yaml) and a host-agnostic `termchart run <id>` primitive
that hands the board's assembled prompt to an agent which gathers data and pushes.
- core: validateBoardDef() — pure, dependency-free structural validator
- cli: `termchart board create|list|show|prompt|scaffold-workflow|delete`
`termchart run <id>` — assemble prompt + spawn agentCommand (prompt on
stdin to avoid E2BIG/shell-quoting), exit-code propagation, disabled skip
- cli: scaffold-workflow generates a GitHub Actions workflow; tz->UTC cron
conversion for the single-hour case with DST/day-cross warnings
- plugin: /termchart:schedule-board command + scheduled-boards skill
(session-cron v1, GH Actions turnkey, system cron compatible)
Build: esbuild ESM bundle now injects a createRequire banner so bundled CJS
deps (yaml) resolve node builtins instead of throwing "Dynamic require".
Version bumps: cli 0.5.0->0.6.0, plugin 0.12.0->0.13.0, marketplace ->0.13.0.
Tests: core validator (10), board CRUD, prompt assembly, run primitive,
scaffold-workflow + tz conversion, and a built-bundle regression guard.
…ns, UX Agent-tester dogfooding (4 fresh "new user" agents) surfaced real gaps; fixes: Correctness / honesty: - Lifecycle is now actually enforced by `termchart run`: skip past `until`; persistent `lifecycle.runs` counter enforces `maxRuns` (written back to YAML). assembleBoardPrompt now always includes a self-unschedule clause for bounded OR frequent (sub-daily) boards — the job-tracker case the docs promised. - `run` pre-flights the viewer env and exits 4 (was: swallowed the failure in the agent subprocess and returned 0). Rejects unknown flags; one-line verbosity. - Version: bump 0.6.0 -> 0.7.0 (0.6.0 was already published to npm without this feature); scaffolded workflow pins `@ivanmkc/termchart@0.7.0`. GitHub Actions turnkey + DST-correct: - DST handled via two seasonal UTC crons + a local-time guard step (GH cron is UTC-only) so the board fires at the intended local time year-round. - Day-of-week is correctly shifted on UTC midnight crossing (was: emitted a knowingly-wrong cron with a warning); restricted day-of-month is warned, not mis-shifted. - Workflow now installs the agent CLI, sets permissions + GH_TOKEN, timeout- minutes, and a concurrency guard. Non-claude agentCommand omits the Anthropic secret + claude install (gets a TODO note). CLI/UX: - Per-subcommand `--help`; `--enabled/--until/--max-runs` flags; create always writes a visible lifecycle block. `board list` warns about skipped invalid files instead of hiding them. Unknown top-level command says so (instead of trying to open it as a file); unknown flag names the flag, not its value; missing-required-flag names which; `board delete` shows the expected path; YAML parse errors carry the board id. Docs: skill + command truthed-up (lifecycle enforcement + GH-Actions maxRuns caveat, turnkey workflow, component payload pointer, run exit-4). Tests: core 11, cli 228 (incl. built-bundle smoke); re-verified live against the remote viewer (pre-flight exit 4, maxRuns stop, DST workflow, full push loop).
…ning
Senior-review pass on the branch found 2 critical defects in the GH Actions
scaffolding plus hardening gaps; all fixed with tests.
Critical:
- DST guard was exact-minute wall-clock equality — GitHub's routinely-late cron
starts (3-20+ min) would skip nearly every run, and workflow_dispatch was
swallowed too. Now keyed on WHICH cron fired (github.event.schedule via an
EVENT_SCHEDULE env) vs the zone's CURRENT UTC offset (date +%z): delay-immune,
fail-open, dispatch always runs. Behavioral test executes the generated shell.
- shiftDow emitted an invalid descending range when a day-of-week shift wrapped
past Saturday ("4-6"+1 -> "5-0", which GitHub rejects — silent dead schedule).
Wrapping ranges now split ("5-6,0").
Hardening:
- Board ids are validated (BOARD_ID_RE, exported from core) before any path
join — no traversal via `board delete ../../x` etc.
- agentCommand containing quotes is rejected at validation (whitespace-split,
no-shell contract documented in the skill); filename stem must match id
(loadBoard error + scanBoards issue); saveBoard writes tmp+rename (atomic).
- Cron field values validated in core (charset, per-field bounds, step>0,
numeric-only) so typos fail at create, not on GitHub.
- scaffold-workflow warns when lifecycle.maxRuns can't persist on an ephemeral
runner (suggests until) and when the board's host isn't github-actions; the
bounded-lifecycle prompt clause no longer overclaims enforcement.
- --enabled accepts only true|false; removed unused cronToUtc export.
Also: regenerate package-lock for 0.7.0; spec truth-ups (drop the never-built
{prompt} placeholder, record run-enforced lifecycle, note next-run deferral);
sessionCronId write-back instruction in the schedule-board command.
Tests: core 13, cli 234, viewer 492 — all green; live run->push->viewer loop
re-verified against the remote viewer.
…redicate narrowing Applied the transferable /dev-coding-standards rules to the branch: - Single source of truth: "claude -p" default was duplicated in run.ts, board.ts (show), and scaffold-workflow.ts — now one DEFAULT_AGENT_COMMAND exported from core alongside the BoardDef domain. - Correct-by-construction: added isBoardDef() type predicate in core; the six `as BoardDef` casts in board-store.ts are gone — narrowing is structural. - Version dual-source (version.ts vs package.json is forced by the standalone bundle): added a tripwire test asserting they match, since scaffolded workflows pin @ivanmkc/termchart@VERSION. - Dead code: removed unused BoardDeps.env field and the unconsumed BOARDS_SUBDIR export. - No function-scoped imports: hoisted a require() in scaffold-workflow.test.ts to a top-level import. Behavior unchanged: core 14, cli 235 green (viewer-stub files re-run serially under load-114 box contention); built binary spot-checked.
…ax-runs parsing Deep second audit against /dev-coding-standards. One real violation found: create() smuggled a non-numeric --max-runs value through `as unknown as number` so the core validator would report it — a deliberate type-system lie (hidden from grep by its own trailing comment). Now validated at the flag edge with an error that names --max-runs (test added, RED→GREEN). Also: JSDoc on BOARD_ID_RE. Everything else audited clean: catch-(e as Error) is idiomatic TS, cron "*" literals are domain vocabulary, host comparison is compile-checked via the BoardHost literal type, planSchedule's early returns are graceful degradation (one strategy), spawn injection is DI not patching.
…pped-field warning Closes the two findings left open by runtime verification: - `termchart run` now kills a hung agent: --timeout <secs> (default 600), SIGTERM then SIGKILL after 5s, exit 124 (GNU timeout convention). A stuck default `claude -p` previously wedged a session-cron/local-cron scheduler indefinitely (only GH Actions had a timeout-minutes backstop). Verified against the built bundle: `sleep 60` agent killed at 1s → exit 124. - `board create --force` REPLACES the definition; it now warns on stderr with the exact previously-set fields that were dropped because they weren't re-passed (description, tz, host, agentCommand, sessionCronId, lifecycle.until/maxRuns). This bit live testing when a --force re-create silently lost --tz. Docs updated (skill CLI reference, top-level usage, verify-skill gotchas). Tests: run 18, board 24, bundle incl. real-process timeout kill — green.
c10b4a7 to
0ae7841
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Scheduled boards: a termchart board that an agent re-refreshes on a schedule — a daily 8am "my tasks" board, or a job tracker refreshing every 2 minutes that stops itself when the job ends.
Design: termchart owns two portable pieces — a committed YAML definition (
.termchart/boards/<id>.yaml) and a host-agnostictermchart run <id>primitive that hands the board's assembled prompt to an agent (stdin, tokenised spawn, no shell) which gathers data and pushes. Any scheduler calls the same primitive: Claude Code session cron (v1 first-class), GitHub Actions (turnkey scaffolding), or system cron.Spec:
docs/superpowers/specs/2026-06-24-scheduled-boards-design.mdSurface
validateBoardDef()— id/cron (charset+bounds)/tz/target/lifecycle/agentCommand validationtermchart board create|list|show|prompt|scaffold-workflow|delete,termchart run <id>(viewer pre-flight exit 4; lifecycle enforcement:enabled,until, persistedmaxRunscounter)permissions:+GH_TOKEN, timeout, concurrency, and DST-correct scheduling (two seasonal UTC crons + an offset-keyed guard ongithub.event.schedule— delay-immune, dispatch always runs; day-of-week shifts correctly across UTC midnight incl. Saturday wrap)/termchart:schedule-boardcommand +scheduled-boardsskillVerification
Notes for release
npx @ivanmkc/termchart@0.7.0is pinned in scaffolded workflows — publish the CLI before advertising the GH Actions path (publish is auth-gated).