Skip to content

feat: shared options block on every task type (closes #54)#82

Merged
8bitAlex merged 4 commits into
mainfrom
feat/issue-54-task-options
May 13, 2026
Merged

feat: shared options block on every task type (closes #54)#82
8bitAlex merged 4 commits into
mainfrom
feat/issue-54-task-options

Conversation

@8bitAlex
Copy link
Copy Markdown
Owner

Summary

Resolves #54Shared options: block on every task type.

Adds a composable options: block on the base task definition (via \$defs/taskOptions in the schema) so cross-task fields don't have to be re-declared per task type. Every task type inherits the block; user-defined commands accept the same shape.

Initial field: showExeTime (bool). When true, raid prints a dim line to stderr after the task or command completes:

```
→ task-name (1.2s)
```

Fires on both success and failure. Task-level and command-level flags are independent — set both to time the command end-to-end and see per-task breakdowns.

```yaml
commands:

  • name: build
    options:
    showExeTime: true # one final line for the whole command
    tasks:
    • type: Shell
      name: server-build
      cmd: swift build
      options:
      showExeTime: true # per-task line
      ```

Smoke-tested locally:

```
$ raid hi
hello from hi
→ Print (0ms)
→ sleep-task (116ms)
→ hi (116ms)
```

Implementation

  • Schema: new `taskOptions` `$def` referenced from `taskCommon` and from the `commands` entry. `additionalProperties: false` so future fields (`quiet`, `timeout`) don't land via typo.
  • Types: `TaskProps.Options` and `Command.Options` (both `*TaskOptions`). Nil-safe; omitting the block leaves prior behavior unchanged.
  • Runtime: `ExecuteTask` wraps the per-type dispatch with timing when set; `runCommand` wraps `runCommandTasks`. Timing uses a `timeNowFn` seam for deterministic tests. Output goes through `commandStderr` so MCP captures and CLI tty both behave correctly.
  • `Task.Label()` returns `Name` when set, falling back to the raw task type so unnamed tasks still produce a meaningful line.
  • `formatExeDuration` mirrors the recent-runs formatter in `cmd/context` so durations read consistently across raid's surfaces.

Test plan

  • `go test ./...` — all packages green.
  • New tests:
    • `TestExecuteTask_showExeTime_emitsLine` / `_fallsBackToTaskType` / `_firesOnFailureToo` / `_noOptionsLeavesOutputUntouched`.
    • `TestRunCommand_showExeTime_emitsLine` / `_off_byDefault`.
    • `TestValidateProfile_optionsAccepted_onEveryTaskType` — locks the `taskCommon` inheritance across every task type plus the command-level block.
    • `TestValidateProfile_optionsRejectsUnknownField` — guards future fields landing silently via typo.
    • `TestFormatExeDuration` — bucket boundaries.
  • `cd site && npm run build` — no broken links. Static schema copy re-synced (embedded-vs-published test passes).
  • Manual smoke: profile with task-level and command-level `options.showExeTime: true` emits the expected three lines.

Out of scope (per the issue)

  • Additional option fields (`quiet`, `timeout`, …). The schema's `additionalProperties: false` makes adding them a straightforward additive change.

🤖 Generated with Claude Code

Introduces a composable `options:` block on the base task definition
(via `$defs/taskOptions` in the schema) so cross-task fields stay in
one place. Every task type inherits the block — no per-type
duplication in YAML. User-defined commands accept the same shape so
options compose at both layers.

Initial field: `showExeTime` (bool, default false). When true, raid
prints a dim line to stderr after the task or command completes:

  → task-name (1.2s)

It fires for both success and failure so the user always sees how long
the task ran. Task-level and command-level flags are independent — set
both to time the command end-to-end and see per-task breakdowns:

  commands:
    - name: build
      options: {showExeTime: true}
      tasks:
        - type: Shell
          name: server-build
          cmd: swift build
          options: {showExeTime: true}

  → server-build (164.1s)
  → build (164.1s)

Notes on the implementation:

- TaskProps gains an Options *TaskOptions pointer; Command gains its
  own. ExecuteTask wraps the dispatch with timing; runCommand wraps
  the inner runCommandTasks similarly. timeNowFn is the seam for
  deterministic test output.
- Task.Label() returns Name when set, falling back to the raw task
  type so unnamed tasks still produce a meaningful line.
- formatExeDuration mirrors the recent-runs formatter in cmd/context
  (250ms / 1.5s / 1m15s / 2h00m) so durations read consistently
  across raid's surfaces.
- Schema rejects unknown option fields (additionalProperties: false on
  the taskOptions $def) so the future `quiet`/`timeout` work doesn't
  silently accept typos.
- The static schema copy under site/static/schema/v1/ is re-synced —
  the embedded vs published cross-check test guards this.

Tests:
- TestExecuteTask_showExeTime_* cover the emit line, the type-fallback
  label, the failure-path emission, and the no-options default.
- TestRunCommand_showExeTime_* cover command-level emission and the
  default-off behavior.
- TestValidateProfile_optionsAccepted_onEveryTaskType locks the
  taskCommon inheritance — every task variant accepts `options:` plus
  the command-level block.
- TestValidateProfile_optionsRejectsUnknownField guards against the
  future `quiet` field landing silently.
- TestFormatExeDuration locks the bucket boundaries.

Version 0.13.0-beta → 0.14.0-beta. Docs updated: schema reference adds
an Options section + command-level row, whats-new entry for 0.14.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 13, 2026 03:35
@codecov
Copy link
Copy Markdown

codecov Bot commented May 13, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.24%. Comparing base (cadf5d0) to head (aa1d5f4).

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #82      +/-   ##
==========================================
+ Coverage   92.00%   92.24%   +0.24%     
==========================================
  Files          36       36              
  Lines        3303     3341      +38     
==========================================
+ Hits         3039     3082      +43     
+ Misses        172      167       -5     
  Partials       92       92              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements issue #54 by introducing a shared options: block that applies uniformly to every task type and to user-defined commands, with an initial showExeTime: bool option that prints an elapsed-time line after completion. It updates the JSON Schema, Go runtime/types, documentation, and tests to support and validate the new block.

Changes:

  • Add TaskProps.Options / Command.Options (*TaskOptions) and implement showExeTime timing output for both tasks and commands.
  • Refactor task dispatch to enable timing wrappers (ExecuteTaskdispatchTask) and add duration formatting helpers.
  • Extend the JSON Schema + docs to accept options everywhere and reject unknown option fields; add targeted tests.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/resources/app.properties Bumps app version to 0.14.0-beta.
src/internal/lib/task.go Adds TaskOptions, TaskProps.Options, and Task.Label() fallback behavior.
src/internal/lib/task_runner.go Implements per-task showExeTime timing wrapper and duration formatting helpers.
src/internal/lib/task_runner_test.go Adds deterministic timing + stderr-capture tests for task-level showExeTime and duration formatting.
src/internal/lib/profile_test.go Adds schema validation tests ensuring options is accepted on all task types and rejects unknown fields.
src/internal/lib/command.go Adds command-level options and wraps command execution with showExeTime timing.
src/internal/lib/command_test.go Adds tests for command-level showExeTime default/behavior.
site/docs/whats-new.mdx Documents the upcoming 0.14.0 feature and closes #54.
site/docs/references/schema.mdx Documents the new options block and showExeTime semantics + example output.
schemas/raid-defs.schema.json Adds $defs/taskOptions and references it from task common fields and command entries; disallows unknown option fields.

Comment thread src/internal/lib/command.go Outdated
Comment on lines +223 to +233
func runCommand(cmd Command) error {
// Command-level showExeTime is independent of per-task timing — both
// flags can be set together. The command line is emitted after the
// final task (and after any per-task lines) so the timeline reads
// top-down. Like the task variant, it fires for both success and
// failure so the elapsed time is always visible.
if cmd.Options != nil && cmd.Options.ShowExeTime {
start := timeNowFn()
err := runCommandTasks(cmd)
emitExeTime(cmd.Name, timeNowFn().Sub(start))
return err
… line

Move the command-level showExeTime emission inside runCommand's Out
wrapping. Previously the elapsed-time line was printed by runCommand
*after* runCommandTasks returned, but the deferred restore of
commandStdout/commandStderr had already run — so the line ignored
`out.stderr: false` and was not captured by `out.file`.

Inline the wrapping into runCommand (drops runCommandTasks) and emit
the line in the same defer, before the file is closed and the writers
are restored. Adds a test that exercises stderr suppression + file
capture together.

Co-Authored-By: Copilot <copilot@github.com>
@8bitAlex
Copy link
Copy Markdown
Owner Author

Auto-review by meeseeks

Updates pushed: 1 commit

  • d23d85f fix: address Copilot review — respect Out config for command exe-time line

Copilot comments addressed: 1 of 1

  • src/internal/lib/command.go:233 — moved command-level showExeTime emission inside runCommand's Out wrapping. Previously the elapsed-time line was printed after the deferred writer restore, so it ignored out.stderr: false and missed out.file capture. Now it's emitted in the same defer, before the file closes and writers are restored. Added TestRunCommand_showExeTime_respectsOutSuppressionAndFile covering both behaviors (stderr suppression + file capture) together.

Skipped: 0

Codecov patch: pass (project: pass)
Other CI: green — all build/codeql/docs/version-check passing

8bitAlex and others added 2 commits May 12, 2026 21:10
Drops the '→ <name> (<time>)' prefix in favour of a more readable
sentence form. Same ANSI dimming and stderr emission; only the
string changes.

Updates the runtime emitter, schema description, schema reference
doc, whats-new entry, and all tests that asserted the old format.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The linter-added TestRunCommand_showExeTime_respectsOutSuppressionAndFile
still asserted the old '→ build (750ms)' string; bring it in line with
the new '<name> complete in <time>' format.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@8bitAlex 8bitAlex merged commit 7e7f7a0 into main May 13, 2026
13 checks passed
@8bitAlex 8bitAlex deleted the feat/issue-54-task-options branch May 13, 2026 04:30
8bitAlex added a commit that referenced this pull request May 13, 2026
Adds a second field to the shared task `options:` block (introduced in
#54): `continueOnFailure: bool` (default false). When true, a task's
non-zero exit no longer aborts the parent command — subsequent tasks
still run and the ignored failure is surfaced as a dim warning on
stderr (`warning: <label> failed (continueOnFailure): <err>`).

Useful for cleanup teardown, optional lint/format probes, and
best-effort smoke checks that shouldn't block the rest of the command.
The command's overall exit code is only affected by *non-ignored*
failures, so strict failures elsewhere in the sequence still propagate
through cleanly (verified end-to-end: an ignored exit 1 followed by a
real exit 7 still surfaces exit 7).

Implementation notes:
- TaskOptions.ContinueOnFailure (bool, omitempty). Schema entry on
  taskOptions $def with description + additionalProperties:false
  inherited from #54.
- Sequential branch in ExecuteTasks: on error, check
  isContinueOnFailure(task) — if true, emit warning and `continue`
  the loop instead of unwinding. Otherwise fail-fast as before.
- Concurrent branch in the goroutine: on error, same check —
  emit-and-return vs push-to-errorChan as before.
- emitContinueOnFailureWarning writes the dim warning to commandStderr
  matching the styling used by showExeTime.
- isContinueOnFailure is a nil-safe helper so call sites don't
  re-derive the pointer guard.

Tests:
- Sequential ignored failure: follow-up task runs, command returns nil,
  stderr carries the dim warning.
- Sequential ignored + sequential non-ignored: non-ignored failure
  still surfaces; aborts subsequent tasks.
- Concurrent ignored failure with a sequential follow-up: follow-up
  runs, warning emitted, no error returned. Uses a mutex-wrapped
  syncStderr in tests because bytes.Buffer races between the warning
  goroutine and concurrent subprocess stderr piping.
- Concurrent non-ignored failure still aborts.
- Default behavior (no options) unchanged — regression guard.
- isContinueOnFailure helper covers nil-Options, default-false, and
  true cases.
- Schema validation accepts options.continueOnFailure on every task
  type plus combined with showExeTime.

Docs: schema reference Options table gets the new row plus an example
showing best-effort lint + cleanup. whats-new entry under 0.14.0
(same release as #54 since they're the same options block landing
together).

Stacked on top of #82 (issue #54) — the `options:` block lives there.
Rebases cleanly onto main once #82 merges.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8bitAlex added a commit that referenced this pull request May 13, 2026
* feat: options.continueOnFailure for best-effort tasks (closes #76)

Adds a second field to the shared task `options:` block (introduced in
#54): `continueOnFailure: bool` (default false). When true, a task's
non-zero exit no longer aborts the parent command — subsequent tasks
still run and the ignored failure is surfaced as a dim warning on
stderr (`warning: <label> failed (continueOnFailure): <err>`).

Useful for cleanup teardown, optional lint/format probes, and
best-effort smoke checks that shouldn't block the rest of the command.
The command's overall exit code is only affected by *non-ignored*
failures, so strict failures elsewhere in the sequence still propagate
through cleanly (verified end-to-end: an ignored exit 1 followed by a
real exit 7 still surfaces exit 7).

Implementation notes:
- TaskOptions.ContinueOnFailure (bool, omitempty). Schema entry on
  taskOptions $def with description + additionalProperties:false
  inherited from #54.
- Sequential branch in ExecuteTasks: on error, check
  isContinueOnFailure(task) — if true, emit warning and `continue`
  the loop instead of unwinding. Otherwise fail-fast as before.
- Concurrent branch in the goroutine: on error, same check —
  emit-and-return vs push-to-errorChan as before.
- emitContinueOnFailureWarning writes the dim warning to commandStderr
  matching the styling used by showExeTime.
- isContinueOnFailure is a nil-safe helper so call sites don't
  re-derive the pointer guard.

Tests:
- Sequential ignored failure: follow-up task runs, command returns nil,
  stderr carries the dim warning.
- Sequential ignored + sequential non-ignored: non-ignored failure
  still surfaces; aborts subsequent tasks.
- Concurrent ignored failure with a sequential follow-up: follow-up
  runs, warning emitted, no error returned. Uses a mutex-wrapped
  syncStderr in tests because bytes.Buffer races between the warning
  goroutine and concurrent subprocess stderr piping.
- Concurrent non-ignored failure still aborts.
- Default behavior (no options) unchanged — regression guard.
- isContinueOnFailure helper covers nil-Options, default-false, and
  true cases.
- Schema validation accepts options.continueOnFailure on every task
  type plus combined with showExeTime.

Docs: schema reference Options table gets the new row plus an example
showing best-effort lint + cleanup. whats-new entry under 0.14.0
(same release as #54 since they're the same options block landing
together).

Stacked on top of #82 (issue #54) — the `options:` block lives there.
Rebases cleanly onto main once #82 merges.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test: cover Group task type in continueOnFailure schema test

Address Copilot review: the test name claims coverage of every
task variant but omitted Group. Add a Group task referencing a
minimal task_groups entry so a regression in Group accepting
options would be caught.

Co-Authored-By: Copilot <copilot@github.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Mr. Meeseeks <meeseeks@alexsalerno.dev>
Co-authored-by: Copilot <copilot@github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Shared options: block on every task type

3 participants