Skip to content

Add minute-aware scheduling durations for Event and Meeting flows - #264

Merged
unbraind merged 6 commits into
mainfrom
feat/pm-zoe4-duration-minutes
Jun 17, 2026
Merged

unbraind merged 6 commits into
mainfrom
feat/pm-zoe4-duration-minutes

Conversation

@unbraind

Copy link
Copy Markdown
Owner

Summary

  • implement pm-zoe4 by extending event duration parsing to accept explicit minute forms (30min|mins|minute|minutes) and ISO-8601 durations (PT30M, PT1H, etc.) in create/update + scheduling shortcuts
  • preserve backward compatibility where bare relative m remains months for duration tokens (45m still means 45 months)
  • sync command/help/SDK contract docs and add regression tests across parser, create/update commands, and CLI command registration

Test plan

  • pnpm build
  • node scripts/run-tests.mjs test -- tests/unit/core/item/parse-utils.spec.ts tests/unit/commands/create-command.spec.ts tests/unit/commands/update-command.spec.ts tests/unit/commands/scheduling-shortcuts.spec.ts tests/unit/commands/register-commands.spec.ts
  • node scripts/run-tests.mjs coverage (100/100/100/100)
  • Manual temp-dir validation via node dist/cli.js --path <temp>/.agents/pm:
    • meet --duration 30min -> +30 minutes
    • event --duration PT30M -> +30 minutes
    • event --duration 45m -> month-compat behavior retained
    • update --event ...duration=30min parity check
    • validate --check-resolution --check-history-drift and health --check-only

Implement pm-zoe4 by accepting `30min` and ISO-8601 `PT...` duration forms in event duration parsing for create/update and meet/event shortcuts, while preserving legacy bare `m` month semantics for backward compatibility. Sync help/contracts/docs and expand regression coverage so the project remains at 100/100/100/100.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry @unbraind, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented Jun 17, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@unbraind, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 14 minutes and 20 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 1a8ce5a6-a57e-46f0-8269-26d6f79aa88e

📥 Commits

Reviewing files that changed from the base of the PR and between 41c1bc7 and b407425.

📒 Files selected for processing (14)
  • .agents/pm/features/pm-zoe4.toon
  • .agents/pm/history/pm-zoe4.jsonl
  • docs/COMMANDS.md
  • scripts/contracts-snapshot.mjs
  • src/cli/commands/event-validation-messages.ts
  • src/cli/help-content.ts
  • src/sdk/cli-contracts/commander-mutation-options.ts
  • src/sdk/cli-contracts/tool-parameter-tables.ts
  • tests/fixtures/contracts/full.json
  • tests/unit/commands/create-command.spec.ts
  • tests/unit/commands/register-commands.spec.ts
  • tests/unit/commands/scheduling-shortcuts.spec.ts
  • tests/unit/commands/update-command.spec.ts
  • tests/unit/core/item/parse-utils.spec.ts
📝 Walkthrough

Walkthrough

Adds sub-hour duration parsing (30min, PT30M) to resolveEventEndAt via a new resolveDurationAgainstStart helper, while preserving bare m as months. Updates CLI help text, SDK option descriptions, PM_TOOL_PARAMETER_METADATA, and docs/COMMANDS.md to reflect the new formats. Extends unit tests across five spec files.

Changes

Sub-hour event duration parsing

Layer / File(s) Summary
Core duration parsing logic
src/cli/commands/event-validation-messages.ts
Adds resolveDurationAgainstStart with regexes for minute tokens (min/mins/minute/minutes) and ISO-8601 duration strings, computing millisecond offsets from startAt. Falls back to resolveIsoOrRelative for other inputs. resolveEventEndAt is updated to call this helper instead of the prior direct resolveIsoOrRelative path.
Help text, CLI contracts, SDK metadata, and docs
src/cli/help-content.ts, src/sdk/cli-contracts/commander-mutation-options.ts, src/sdk/cli-contracts/tool-parameter-tables.ts, docs/COMMANDS.md
--duration help tips for meet and event updated to document 30min/PT30M; create and update option descriptions include duration=<relative|30min|PT30M>; PM_TOOL_PARAMETER_METADATA gains reminder and event entries; COMMANDS.md replaces the "sub-hour requires --end" note.
Unit tests for new and legacy duration formats
tests/unit/core/item/parse-utils.spec.ts, tests/unit/commands/scheduling-shortcuts.spec.ts, tests/unit/commands/create-command.spec.ts, tests/unit/commands/update-command.spec.ts, tests/unit/commands/register-commands.spec.ts
New tests assert 30min and PT30M produce a 30-minute end_at, PT1H produces a 1-hour end_at, negative durations throw an end-before-start error, invalid input (e.g. PT) throws an invalid-duration error, and bare 45m continues to be treated as months.
Agent feature tracking and history
.agents/pm/features/pm-zoe4.toon, .agents/pm/history/pm-zoe4.jsonl
Feature spec moved to in_progress with enumerated file changes and verification evidence; JSONL history extended with assignment, file plan, test run records, and a consolidation comment.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • unbraind/pm-cli#263: Introduced scheduling-shortcuts behavior including the same tests/unit/commands/scheduling-shortcuts.spec.ts file that this PR extends with minute-form duration assertions.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding minute-aware scheduling durations for Event and Meeting flows, which is the primary objective documented in the PR.
Description check ✅ Passed The description is clearly related to the changeset, providing a comprehensive summary of the feature implementation, backward compatibility preservation, test coverage, and validation steps.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Update the contracts fixture after adding reminder/event parameter metadata so contracts:check stays green and the PR remains CI-ready.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request adds support for sub-hour duration granularities, such as explicit minute forms (e.g., '30min') and ISO-8601 durations (e.g., 'PT30M'), in scheduling commands and event options. It ensures backward compatibility by keeping bare 'm' as months. The implementation updates the duration resolution logic, CLI help content, and parameter contracts, and is supported by comprehensive unit and integration tests. As there are no review comments, I have no feedback to provide.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@unbraind

Copy link
Copy Markdown
Owner Author

@codex review
/gemini review
@gemini-code-assist
@coderabbitai full review

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@coderabbitai

coderabbitai Bot commented Jun 17, 2026 •

Copy link
Copy Markdown

@unbraind I’ll run a full review of this PR now.

✅ Action performed

Full review finished.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces sub-hour duration granularity for scheduling by supporting explicit minute forms (such as '30min' or 'minutes') and ISO-8601 durations (such as 'PT30M') for event durations, while preserving legacy behavior where bare 'm' represents months. It also updates CLI help documentation, SDK option contracts, and adds extensive unit tests. The review feedback correctly points out that using a fixed millisecond value for days in ISO-8601 durations can fail during Daylight Saving Time (DST) transitions, and suggests using calendar-based date arithmetic to resolve this issue.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

const MILLIS_PER_SECOND = 1000;
const MILLIS_PER_MINUTE = 60 * MILLIS_PER_SECOND;
const MILLIS_PER_HOUR = 60 * MILLIS_PER_MINUTE;
const MILLIS_PER_DAY = 24 * MILLIS_PER_HOUR;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This constant is no longer needed if we use calendar-based date arithmetic for the days component.

Comment on lines +35 to +41
const days = Number.parseInt(daysToken ?? "0", 10);
const hours = Number.parseInt(hoursToken ?? "0", 10);
const minutes = Number.parseInt(minutesToken ?? "0", 10);
const seconds = Number.parseInt(secondsToken ?? "0", 10);
const totalMillis =
days * MILLIS_PER_DAY + hours * MILLIS_PER_HOUR + minutes * MILLIS_PER_MINUTE + seconds * MILLIS_PER_SECOND;
return new Date(start.getTime() + sign * totalMillis).toISOString();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

When parsing ISO-8601 durations that include days (D), calculating the end date by multiplying the number of days by a fixed millisecond value (MILLIS_PER_DAY) can lead to incorrect results during Daylight Saving Time (DST) transitions (where a day may be 23 or 25 hours instead of 24).

Using calendar-based date arithmetic for the days component, and then adding the remaining time components (hours, minutes, seconds) as milliseconds, ensures correctness across DST boundaries.

      const days = Number.parseInt(daysToken ?? "0", 10);
      const hours = Number.parseInt(hoursToken ?? "0", 10);
      const minutes = Number.parseInt(minutesToken ?? "0", 10);
      const seconds = Number.parseInt(secondsToken ?? "0", 10);
      const end = new Date(start.getTime());
      end.setDate(end.getDate() + sign * days);
      const totalMillis =
        hours * MILLIS_PER_HOUR + minutes * MILLIS_PER_MINUTE + seconds * MILLIS_PER_SECOND;
      return new Date(end.getTime() + sign * totalMillis).toISOString();

Address review feedback by constraining ISO duration parsing for event.duration to PT time components (H/M/S only), keeping day semantics and DST-sensitive expectations out of this feature scope. Add regression coverage that P1D is rejected and keep full coverage at 100/100/100/100.
@unbraind

Copy link
Copy Markdown
Owner Author

Follow-up push applied (ISO PT duration scope refinement per review feedback).

@codex review
/gemini review
@gemini-code-assist
@coderabbitai full review

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@coderabbitai

coderabbitai Bot commented Jun 17, 2026 •

Copy link
Copy Markdown

@unbraind Thanks for the follow-up — I'll kick off a fresh full review now to cover the ISO PT duration scope refinement.

✅ Action performed

Full review finished.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request implements sub-hour duration granularity for scheduling by adding support for minutes in event durations (e.g., 30min, PT30M) while preserving backward compatibility where a bare m remains interpreted as months. The changes update the duration parsing logic, command-line help content, SDK option descriptions, and parameter tables, and include comprehensive unit test coverage. There are no review comments to address, and I have no additional feedback to provide.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Add rmSync retry options in contracts-snapshot cleanup so transient ENOTEMPTY temp-directory races on GitHub runners do not fail the static gate.
@unbraind

Copy link
Copy Markdown
Owner Author

Follow-up push applied for CI static-gate reliability fix.

@codex review
/gemini review
@gemini-code-assist
@coderabbitai full review

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@coderabbitai

coderabbitai Bot commented Jun 17, 2026 •

Copy link
Copy Markdown

@unbraind Thanks for the CI static-gate reliability fix — I'll kick off a fresh full review now to cover those changes.

✅ Action performed

Full review finished.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces sub-hour duration granularity for scheduling by supporting explicit minute forms (e.g., '30min', 'PT30M') in event durations, while preserving legacy behavior where bare 'm' represents months. It also updates documentation, help content, and CLI contracts, and adds robust test coverage. Feedback on the changes highlights a potential crash when parsing extremely large duration values, where calling .toISOString() on an invalid date throws a RangeError; validating the date object before serialization is recommended to prevent unhandled exceptions.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +17 to +45
function resolveDurationAgainstStart(startAt: string, durationRaw: string): string {
const start = new Date(startAt);
const trimmedDuration = durationRaw.trim();

const minuteDuration = MINUTE_DURATION.exec(trimmedDuration);
if (minuteDuration) {
const sign = minuteDuration[1] === "-" ? -1 : 1;
const amount = Number.parseInt(minuteDuration[2], 10) * sign;
return new Date(start.getTime() + amount * MILLIS_PER_MINUTE).toISOString();
}

const isoDuration = ISO_8601_TIME_DURATION.exec(trimmedDuration);
if (isoDuration) {
const [, signToken, hoursToken, minutesToken, secondsToken] = isoDuration;
const hasComponent = hoursToken || minutesToken || secondsToken;
if (hasComponent) {
const sign = signToken === "-" ? -1 : 1;
const hours = Number.parseInt(hoursToken ?? "0", 10);
const minutes = Number.parseInt(minutesToken ?? "0", 10);
const seconds = Number.parseInt(secondsToken ?? "0", 10);
const totalMillis = hours * MILLIS_PER_HOUR + minutes * MILLIS_PER_MINUTE + seconds * MILLIS_PER_SECOND;
return new Date(start.getTime() + sign * totalMillis).toISOString();
}
}

const normalizedDuration =
trimmedDuration.startsWith("+") || trimmedDuration.startsWith("-") ? trimmedDuration : `+${trimmedDuration}`;
return resolveIsoOrRelative(normalizedDuration, start, "event.duration");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If the user inputs an extremely large duration value (e.g., '99999999999999999999min' or 'PT99999999999999999999H'), parsing it can result in an invalid date. Calling '.toISOString()' on an invalid date throws a 'RangeError: Invalid time value', which causes an unhandled exception and crashes the CLI.

We should validate that the resulting date is valid before calling '.toISOString()', and throw a user-friendly 'PmCliError' instead.

function resolveDurationAgainstStart(startAt: string, durationRaw: string): string {
  const start = new Date(startAt);
  const trimmedDuration = durationRaw.trim();

  let end: Date | undefined;

  const minuteDuration = MINUTE_DURATION.exec(trimmedDuration);
  if (minuteDuration) {
    const sign = minuteDuration[1] === "-" ? -1 : 1;
    const amount = Number.parseInt(minuteDuration[2], 10) * sign;
    end = new Date(start.getTime() + amount * MILLIS_PER_MINUTE);
  } else {
    const isoDuration = ISO_8601_TIME_DURATION.exec(trimmedDuration);
    if (isoDuration) {
      const [, signToken, hoursToken, minutesToken, secondsToken] = isoDuration;
      const hasComponent = hoursToken || minutesToken || secondsToken;
      if (hasComponent) {
        const sign = signToken === "-" ? -1 : 1;
        const hours = Number.parseInt(hoursToken ?? "0", 10);
        const minutes = Number.parseInt(minutesToken ?? "0", 10);
        const seconds = Number.parseInt(secondsToken ?? "0", 10);
        const totalMillis = hours * MILLIS_PER_HOUR + minutes * MILLIS_PER_MINUTE + seconds * MILLIS_PER_SECOND;
        end = new Date(start.getTime() + sign * totalMillis);
      }
    }
  }

  if (end) {
    if (Number.isNaN(end.getTime())) {
      throw new PmCliError("Invalid event.duration value", EXIT_CODE.USAGE);
    }
    return end.toISOString();
  }

  const normalizedDuration =
    trimmedDuration.startsWith("+") || trimmedDuration.startsWith("-") ? trimmedDuration : "+" + trimmedDuration;
  return resolveIsoOrRelative(normalizedDuration, start, "event.duration");
}

Add a safe ISO conversion guard for duration-derived timestamps so out-of-range minute/PT durations fail with a usage error instead of throwing RangeError, and cover the behavior with regression tests.
@unbraind

Copy link
Copy Markdown
Owner Author

Follow-up push applied for duration overflow guard and regression tests.

@codex review
/gemini review
@gemini-code-assist
@coderabbitai full review

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@coderabbitai

coderabbitai Bot commented Jun 17, 2026 •

Copy link
Copy Markdown

@unbraind Thanks for the follow-up — I'll kick off a fresh full review now to cover the duration overflow guard and regression tests.

✅ Action performed

Full review finished.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces sub-hour duration granularity for scheduling by supporting minute-based forms (such as 30min and ISO-8601 PT30M) in --duration and event duration= fields, while preserving backward compatibility where bare m remains interpreted as months. The changes update validation logic, CLI help content, SDK contracts, and add extensive unit tests to cover the new parsing behavior, overflow limits, and edge cases. Additionally, the cleanup process in scripts/contracts-snapshot.mjs is hardened with retries to prevent ENOTEMPTY CI flakes. Feedback is provided to rename toIsoDurationOrThrow to toIsoTimestampOrThrow to better reflect that it returns an ISO timestamp rather than a duration string.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +17 to +26
function toIsoDurationOrThrow(timestampMs: number, durationRaw: string): string {
const resolved = new Date(timestampMs);
if (!Number.isFinite(resolved.getTime())) {
throw new PmCliError(
`Invalid event.duration value "${durationRaw}". Duration is out of supported timestamp range.`,
EXIT_CODE.USAGE,
);
}
return resolved.toISOString();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The function toIsoDurationOrThrow actually returns an ISO 8601 timestamp (date string) representing the resolved end date, not an ISO 8601 duration string (like PT30M). To prevent confusion and improve maintainability, consider renaming it to toIsoTimestampOrThrow or toIsoStringOrThrow. Remember to also update its callers on lines 36 and 49.

Suggested change
function toIsoDurationOrThrow(timestampMs: number, durationRaw: string): string {
const resolved = new Date(timestampMs);
if (!Number.isFinite(resolved.getTime())) {
throw new PmCliError(
`Invalid event.duration value "${durationRaw}". Duration is out of supported timestamp range.`,
EXIT_CODE.USAGE,
);
}
return resolved.toISOString();
}
function toIsoTimestampOrThrow(timestampMs: number, durationRaw: string): string {
const resolved = new Date(timestampMs);
if (!Number.isFinite(resolved.getTime())) {
throw new PmCliError(
"Invalid event.duration value \"" + durationRaw + "\". Duration is out of supported timestamp range.",
EXIT_CODE.USAGE
);
}
return resolved.toISOString();
}

Rename toIsoDurationOrThrow to toIsoTimestampOrThrow so the helper name matches its output, while preserving behavior and tests.
@unbraind

Copy link
Copy Markdown
Owner Author

Follow-up push applied for naming clarity polish.

@codex review
/gemini review
@gemini-code-assist
@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 17, 2026 •

Copy link
Copy Markdown

@unbraind Thanks for the naming clarity polish follow-up — I'll kick off a fresh full review now to cover those changes.

✅ Action performed

Full review finished.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request implements sub-hour duration granularity for scheduling by adding support for minutes in event durations (such as '30min' and 'PT30M') while preserving backward compatibility where a bare 'm' represents months. The changes span CLI commands, help content, SDK contracts, and validation logic, backed by comprehensive unit tests. Additionally, the CI cleanup process in contracts-snapshot.mjs was hardened with retries to prevent ENOTEMPTY flakes. There are no review comments, and I have no further feedback to provide.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@unbraind
unbraind merged commit 3da2442 into main Jun 17, 2026
15 checks passed
@unbraind
unbraind deleted the feat/pm-zoe4-duration-minutes branch June 17, 2026 19:19
unbraind added a commit that referenced this pull request Jun 17, 2026
Record merged evidence by closing pm-zoe4 with the final outcome and releasing assignment ownership after PR #264 landed on main.
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.

1 participant