Skip to content

Feature/ai api - #68

Open
magmacomputing wants to merge 14 commits into
mainfrom
feature/ai-api
Open

Feature/ai api#68
magmacomputing wants to merge 14 commits into
mainfrom
feature/ai-api

Conversation

@magmacomputing

@magmacomputing magmacomputing commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features
    • Added RRULE parsing, validation, expansion, and next-occurrence utilities.
    • Added AI-powered recurrence parsing and natural-language scheduling with confidence, provider, and alternative metadata.
    • Added asynchronous AI cache adapters, remote provider defaults, configurable timeouts, and rate-limit details.
    • Added LLM documentation bundles and AI/IDE integration guidance.
  • Bug Fixes
    • Improved timezone abbreviation, UTC offset, ISO date, and whitespace parsing.
    • Added cache JSON serialization with expired-entry cleanup.
  • Documentation
    • Updated installation, plugin, architecture, API, and release documentation.
  • Chores
    • Updated releases to Tempo 3.11.1 and AI plugin 0.3.0.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Tempo 3.11.1 adds RRULE utilities, AI recurrence and scheduling APIs, remote provider manifests, asynchronous cache adapters, timezone parsing updates, generated LLM documentation, release checks, and documentation deployment synchronization.

Changes

AI plugin enhancements

Layer / File(s) Summary
AI contracts and initialization
packages/plugins/ai/src/types/*, packages/plugins/ai/src/core/{config,init,manifest}.ts, packages/plugins/ai/src/index.ts
Adds public AI configuration, cache, recurrence, and scheduling types. Initialization supports remote provider manifests, revision tracking, redacted configuration snapshots, and asynchronous cache clearing.
Provider requests and parsing
packages/plugins/ai/src/core/support.ts, packages/plugins/ai/src/functions/parse.ts, packages/plugins/ai/test/{parse,cache,manifest}.test.ts
Adds timeout precedence, endpoint validation, redirect rejection, async cache adapters, TTL resolution, fail-open storage behavior, and rate-limit metadata.
Recurrence and scheduling flows
packages/plugins/ai/src/functions/{recurrence,schedule}.ts, packages/plugins/ai/test/{recurrence,schedule}.test.ts
Implements provider-backed recurrence parsing and scheduling with fallback, race, consensus, confidence validation, batching, iteration, alternatives, and conflict adjustment.
AI documentation and release metadata
packages/plugins/ai/{README.md,CHANGELOG.md,package.json}, packages/plugins/ai/doc/*, packages/plugins/ai/plan/*
Documents the new AI APIs, provider behavior, caching, rate limits, recurrence, scheduling, and release scope. Updates the plugin version to 0.3.0 and its Tempo peer dependency.

Tempo and library functionality

Layer / File(s) Summary
RRULE utilities and public exports
packages/library/src/common/rrule.library.ts, packages/library/src/common.index.ts, packages/library/test/common/rrule_library.test.ts
Adds RRULE validation, parsing, expansion, filtering, finite-rule detection, and next-occurrence lookup. Re-exports the utilities from the library entry point.
Tempo parsing and recurrence extension
packages/tempo/src/engine/*, packages/tempo/src/module/module.parse.ts, packages/tempo/src/plugin/extend/extend.recurrence.ts, packages/tempo/src/support/*, packages/tempo/src/tempo.class.ts, packages/tempo/test/*
Normalizes temporal inputs, supports timezone aliases and numeric offsets, improves ISO-like parsing, and adds Tempo.nextOccurrence.
Cache serialization and package exports
packages/tempo/src/support/support.cache.ts, packages/tempo/test/support/cache.test.ts, packages/tempo/package.json, packages/tempo/src/tempo.version.ts
Adds JSON serialization for active cache entries, default export conditions, and version 3.11.1 metadata.

Documentation and release automation

Layer / File(s) Summary
Documentation generation and context bundles
packages/tempo/bin/{generate-llms-txt,expand-typedoc}.mjs, packages/tempo/public/{llms.txt,llms-full.txt}, packages/tempo/package.json
Generates LLM documentation bundles and expands exported type aliases in TypeDoc output.
AI and IDE integration documentation
packages/tempo/doc/*, packages/tempo/.vitepress/*, packages/tempo/public/providers.v1.json, packages/library/README.md
Adds AI and IDE integration guidance, navigation links, provider configuration data, and updated branding.
Browser examples and published assets
packages/tempo/public/*.html
Adds an esm.sh browser test page and standard background-clip declarations to published pages.
Release checks and deployment synchronization
.github/workflows/deploy-docs.yml, packages/plugins/.bin/*, package.json, packages/tempo/bin/update-version.mjs, changelogs
Adds version comparison scripts, updates release metadata, synchronizes the browser example version, and dispatches an AI-context synchronization event after documentation deployment.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.67% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title identifies the main change: adding the AI API and related functionality.
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.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/ai-api

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/plugins/ai/src/core/init.ts (1)

21-31: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Apply the manifest before provider defaults are resolved.

initAI starts loadRemoteManifest() and immediately calls getResolvedProviderDefaults(). The resolver therefore sees an empty manifest on the first initialization. The manifest result never updates the already stored providers.

Make initialization await manifest resolution, or apply resolved defaults after the load completes through an explicit async API. Add a regression test that does not preload the manifest.

  • packages/plugins/ai/src/core/init.ts#L21-L31: resolve the manifest before storing manifest-derived provider defaults.
  • packages/plugins/ai/test/manifest.test.ts#L94-L121: call the supported initialization flow before manifest resolution, then verify that the remote model is applied.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/ai/src/core/init.ts` around lines 21 - 31, The
initialization flow in packages/plugins/ai/src/core/init.ts lines 21-31 must
resolve loadRemoteManifest before getResolvedProviderDefaults stores provider
values; update initAI or expose an explicit async path that guarantees manifest
completion first. Add a regression test in
packages/plugins/ai/test/manifest.test.ts lines 94-121 that uses the supported
initialization flow without preloading the manifest and verifies the remote
model is applied.
🧹 Nitpick comments (1)
packages/tempo/public/esm_sh.index.html (1)

231-231: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Pin the Tempo URL in packages/tempo/public/esm_sh.index.html to the package version.

This page still loads https://esm.sh/@magmacomputing/tempo@3, but packages/tempo/package.json declares version 3.11.1. Use the exact Tempo version, or generate the URL during release, so the check runs against the same release code.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tempo/public/esm_sh.index.html` at line 231, Update the
`@magmacomputing/tempo` URL in the esm.sh import configuration to use the exact
version declared by the Tempo package, 3.11.1, or wire it to the existing
release-generation mechanism so it stays synchronized with
packages/tempo/package.json.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/library/README.md`:
- Line 1: Update the README heading image source to use a repository-relative
path or absolute public URL that resolves correctly when rendered from
packages/library, while preserving the existing logo and heading presentation.

In `@packages/plugins/.bin/check-branch-diff.sh`:
- Around line 30-35: Update the changed-plugin validation in
check-branch-diff.sh to parse and require branch_version to be strictly greater
than main_version as a valid semantic version, rejecting equal, downgraded, or
otherwise invalid versions. Preserve the success status only for valid forward
bumps, and ensure any failure—including “MODIFIED WITHOUT VERSION BUMP!”—sets a
nonzero exit status for the overall check.

In `@packages/plugins/ai/doc/rate-limits.md`:
- Around line 11-12: Update the “Request-Locked Instance Metadata” section for
parseAI so the dt.ai.limits snapshot is guaranteed only for provider-backed
results where the selected provider returns rate-limit headers; explicitly state
that native and cache results may omit limits.
- Around line 145-146: Update the Redis adapter example’s delete and clear
methods to honor the prefix argument and remove namespaced/salted cache keys,
ensuring clear(prefix) performs the corresponding prefix-scoped deletion.
Replace the current no-op clear implementation with a concrete prefix-aware
example, and make delete use the same namespacing scheme so clearAiCache cannot
leave salted entries readable.

In `@packages/plugins/ai/src/core/init.ts`:
- Around line 47-75: Update clearAiCache in
packages/plugins/ai/src/core/init.ts#L47-L75 to return Promise<void>, clear
Tempo.cache for no-input calls, and await every adapter clear and delete
operation before returning while preserving the existing per-input eviction
behavior. Update packages/plugins/ai/CHANGELOG.md#L21-L21 to state the release
behavior only with the corrected completion contract, qualifying it if eviction
is not synchronous.
- Around line 21-26: Harden the provider-resolution flow around
loadRemoteManifest and getResolvedProviderDefaults so manifest-supplied URLs are
accepted only from signed or trusted manifests and match an approved HTTPS
provider origin. Ensure fetchFromProvider rejects redirects for credentialed
requests while preserving the caller API key behavior, and reject invalid
manifest-derived endpoints before they can be resolved or used.

In `@packages/plugins/ai/src/core/manifest.ts`:
- Around line 31-36: Update the manifest initialization flow around
_cachedManifest and _fetchPromise to key cached results and in-flight requests
by the canonical remoteConfigUrl. Only reuse either value when its stored URL
matches the current canonical URL; otherwise fetch and cache the manifest for
the new origin while preserving existing reuse behavior for matching URLs.
- Around line 43-86: Update the initAI initialization flow to await
loadRemoteManifest() before calling getResolvedProviderDefaults(), ensuring the
first initialization uses the fetched manifest when available. Preserve fallback
behavior when loading fails, and update the manifest documentation to describe
that remote defaults are applied during initialization after the manifest load
completes.

In `@packages/plugins/ai/src/core/types.ts`:
- Around line 130-131: Update initAI’s provider-resolution flow, including
getResolvedProviderDefaults, to invoke the AiConfig.fetchDefaults hook for the
requested provider ID and incorporate its returned options before producing
resolved defaults; otherwise remove fetchDefaults from AiConfig. Ensure the
hook’s null result remains valid and existing manifest-based defaults continue
to work.

In `@packages/plugins/ai/src/functions/parse.ts`:
- Around line 247-260: Apply resolvedTtl to the built-in Tempo.cache write in
the parse flow, or otherwise configure that cache tier to enforce the same TTL
precedence as adapter.set. Update packages/plugins/ai/CHANGELOG.md at line 18 to
describe the precedence only for stores that enforce TTL, and update
packages/plugins/ai/README.md at lines 54-55 to clarify the built-in cache’s
separate TTL behavior if it remains independently configured.

In `@packages/tempo/CHANGELOG.md`:
- Line 13: Update the “AI Context & IDE Integration (llms.txt)” changelog entry
to remove the unsupported “zero-hallucination code generation” guarantee,
replacing it with wording that accurately describes providing project context
and improving code-generation accuracy.

In `@packages/tempo/doc/1-getting-started/ai-integration.md`:
- Around line 56-74: Update the AI integration examples to use the documented
Tempo.init({ registry: { layouts: ... } }) layout-registration contract instead
of Tempo.config. Apply this in
packages/tempo/doc/1-getting-started/ai-integration.md lines 56-74 and
packages/tempo/doc/3-extending-tempo/tempo.layout.md lines 104-113, then
regenerate the corresponding sections in packages/tempo/public/llms-full.txt
lines 65-84 and 2461-2470.

In `@packages/tempo/public/esm_sh.index.html`:
- Around line 107-118: Increase the contrast of the .subtitle text by replacing
its current inherited or dark purple text color with a lighter color/token that
achieves at least a 4.5:1 contrast ratio against the dark card background, while
preserving the existing background and layout styling.
- Around line 21-29: Update the body CSS overflow declaration to allow vertical
scrolling while continuing to clip horizontal overflow, so short or zoomed
viewports can reach the card result and footer.

In `@packages/tempo/public/llms-full.txt`:
- Around line 7101-7112: Update the canonical cache documentation in
ai.rate-limits.md to describe support for asynchronous cache adapter methods,
removing the claim that adapters must implement only synchronous Map operations.
Then regenerate llms-full.txt so its Extensible Caching section reflects the
updated async-capable contract and examples.

In `@packages/tempo/src/support/support.cache.ts`:
- Around line 192-194: Update BoundedCache.toJSON() to prevent lossy key
conversion by constraining cache keys to strings or explicitly rejecting
non-string keys before Object.fromEntries(); preserve all valid string-keyed
entries. Add a regression test covering distinct keys such as 1 and "1" so
serialization cannot silently merge entries.

---

Outside diff comments:
In `@packages/plugins/ai/src/core/init.ts`:
- Around line 21-31: The initialization flow in
packages/plugins/ai/src/core/init.ts lines 21-31 must resolve loadRemoteManifest
before getResolvedProviderDefaults stores provider values; update initAI or
expose an explicit async path that guarantees manifest completion first. Add a
regression test in packages/plugins/ai/test/manifest.test.ts lines 94-121 that
uses the supported initialization flow without preloading the manifest and
verifies the remote model is applied.

---

Nitpick comments:
In `@packages/tempo/public/esm_sh.index.html`:
- Line 231: Update the `@magmacomputing/tempo` URL in the esm.sh import
configuration to use the exact version declared by the Tempo package, 3.11.1, or
wire it to the existing release-generation mechanism so it stays synchronized
with packages/tempo/package.json.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0885cb86-da74-4060-bc0c-9211a082a2be

📥 Commits

Reviewing files that changed from the base of the PR and between 6547c1f and b5863c8.

⛔ Files ignored due to path filters (3)
  • packages/library/img/library-logo.svg is excluded by !**/*.svg
  • packages/tempo/img/library-logo.svg is excluded by !**/*.svg
  • packages/tempo/public/library-logo.svg is excluded by !**/*.svg
📒 Files selected for processing (43)
  • .github/workflows/deploy-docs.yml
  • package.json
  • packages/library/README.md
  • packages/library/package.json
  • packages/plugins/.bin/check-branch-diff.sh
  • packages/plugins/.bin/check-versions.sh
  • packages/plugins/ai/CHANGELOG.md
  • packages/plugins/ai/README.md
  • packages/plugins/ai/doc/architecture.md
  • packages/plugins/ai/doc/index.md
  • packages/plugins/ai/doc/rate-limits.md
  • packages/plugins/ai/package.json
  • packages/plugins/ai/plan/v0.3.0-roadmap.md
  • packages/plugins/ai/src/core/init.ts
  • packages/plugins/ai/src/core/manifest.ts
  • packages/plugins/ai/src/core/support.ts
  • packages/plugins/ai/src/core/types.ts
  • packages/plugins/ai/src/functions/parse.ts
  • packages/plugins/ai/src/index.ts
  • packages/plugins/ai/test/cache.test.ts
  • packages/plugins/ai/test/index.spec.ts
  • packages/plugins/ai/test/manifest.test.ts
  • packages/tempo/.vitepress/config.ts
  • packages/tempo/.vitepress/theme/data/catalog.json
  • packages/tempo/CHANGELOG.md
  • packages/tempo/bin/expand-typedoc.mjs
  • packages/tempo/bin/generate-llms-txt.mjs
  • packages/tempo/doc/1-getting-started/ai-integration.md
  • packages/tempo/doc/1-getting-started/installation.md
  • packages/tempo/doc/3-extending-tempo/tempo.layout.md
  • packages/tempo/doc/6-utility-library/tempo.library.md
  • packages/tempo/package.json
  • packages/tempo/public/bundle.index.html
  • packages/tempo/public/esm_core.index.html
  • packages/tempo/public/esm_full.index.html
  • packages/tempo/public/esm_sh.index.html
  • packages/tempo/public/llms-full.txt
  • packages/tempo/public/llms.txt
  • packages/tempo/public/providers.v1.json
  • packages/tempo/public/script.index.html
  • packages/tempo/src/support/support.cache.ts
  • packages/tempo/src/tempo.version.ts
  • packages/tempo/test/support/cache.test.ts

Comment thread packages/library/README.md Outdated
Comment thread packages/plugins/.bin/check-branch-diff.sh
Comment thread packages/plugins/ai/doc/rate-limits.md Outdated
Comment thread packages/plugins/ai/doc/rate-limits.md Outdated
Comment thread packages/plugins/ai/src/core/init.ts Outdated
Comment thread packages/tempo/doc/1-getting-started/ai-integration.md Outdated
Comment thread packages/tempo/public/esm_sh.index.html Outdated
Comment thread packages/tempo/public/esm_sh.index.html
Comment thread packages/tempo/public/llms-full.txt Outdated
Comment thread packages/tempo/src/support/support.cache.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/plugins/ai/src/core/init.ts`:
- Around line 45-65: The remote manifest flow currently rebuilds providers from
config.providers and discards fetchDefaults results. Update the initialization
logic around the fetchDefaults provider mapping and loadRemoteManifest so the
hook-merged providers are retained through resolveSyncProviders, either by
loading the manifest before applying hookOptions or by passing the merged
provider collection into the final resolution.
- Around line 44-65: Track a configuration revision for each initAI invocation
and capture its value before asynchronous provider/default and remote-manifest
resolution begins. In initAI, guard the assignments to _state.config.providers
around the asyncProviders result and resolveSyncProviders so they apply only
when the captured revision remains current, preventing an older invocation from
overwriting newer provider state.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 85c79ff5-f2e7-462f-9a8a-aa724d51ed91

📥 Commits

Reviewing files that changed from the base of the PR and between b5863c8 and 858dd34.

📒 Files selected for processing (19)
  • packages/library/README.md
  • packages/plugins/.bin/check-branch-diff.sh
  • packages/plugins/ai/CHANGELOG.md
  • packages/plugins/ai/README.md
  • packages/plugins/ai/doc/architecture.md
  • packages/plugins/ai/doc/index.md
  • packages/plugins/ai/doc/rate-limits.md
  • packages/plugins/ai/src/core/init.ts
  • packages/plugins/ai/src/core/manifest.ts
  • packages/plugins/ai/src/core/support.ts
  • packages/plugins/ai/test/manifest.test.ts
  • packages/tempo/CHANGELOG.md
  • packages/tempo/bin/update-version.mjs
  • packages/tempo/doc/1-getting-started/ai-integration.md
  • packages/tempo/doc/3-extending-tempo/tempo.layout.md
  • packages/tempo/public/esm_sh.index.html
  • packages/tempo/public/llms-full.txt
  • packages/tempo/src/support/support.cache.ts
  • packages/tempo/test/support/cache.test.ts
🚧 Files skipped from review as they are similar to previous changes (15)
  • packages/plugins/ai/test/manifest.test.ts
  • packages/plugins/.bin/check-branch-diff.sh
  • packages/tempo/src/support/support.cache.ts
  • packages/tempo/doc/1-getting-started/ai-integration.md
  • packages/plugins/ai/doc/architecture.md
  • packages/library/README.md
  • packages/plugins/ai/CHANGELOG.md
  • packages/plugins/ai/src/core/manifest.ts
  • packages/tempo/test/support/cache.test.ts
  • packages/tempo/doc/3-extending-tempo/tempo.layout.md
  • packages/plugins/ai/README.md
  • packages/plugins/ai/doc/rate-limits.md
  • packages/tempo/CHANGELOG.md
  • packages/tempo/public/llms-full.txt
  • packages/plugins/ai/doc/index.md

Comment thread packages/plugins/ai/src/core/init.ts Outdated
Comment thread packages/plugins/ai/src/core/init.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/tempo/public/llms-full.txt (1)

2923-2933: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Align Tempo documentation with the actual initialization lifecycle.

Tempo.init() is idempotent after the first global initialization, so the refresh claim is misleading. Also, reset hooks do not replay arbitrary side-effect registrations, so re-calling Tempo.init() will not activate late @magmacomputing/tempo-plugin-ticker imports. Update the public examples to use Tempo.init({ plugins: [...] }) or Tempo.extend(...), then regenerate packages/tempo/public/llms-full.txt.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tempo/public/llms-full.txt` around lines 2923 - 2933, Update the
public Tempo initialization examples at packages/tempo/public/llms-full.txt
lines 2923-2933, 2605-2616, and 6552-6577 to use Tempo.init({ plugins: [...] })
or Tempo.extend(...) for plugin registration, and remove the claim that
re-calling Tempo.init() refreshes dynamically imported plugins. Regenerate
packages/tempo/public/llms-full.txt from the updated source documentation so all
affected examples reflect the actual idempotent initialization lifecycle.
🧹 Nitpick comments (1)
packages/tempo/public/llms-full.txt (1)

7145-7148: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Replace KEYS in the production Redis example.

clear() performs a full KEYS scan and then deletes all matching keys. Redis documents KEYS as an O(N) dangerous command and recommends SCAN or an indexed set for application code. Use cursor-based scanning with bounded delete batches, or use namespace versioning. Then update the canonical source document and regenerate this bundle. (redis.io)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tempo/public/llms-full.txt` around lines 7145 - 7148, Replace the
redis.keys call in clear with cursor-based SCAN iteration and bounded delete
batches while preserving the existing prefix pattern and clearing behavior.
Update the canonical source document containing this Redis example, then
regenerate packages/tempo/public/llms-full.txt so the bundled example matches
the source.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/plugins/ai/doc/init.md`:
- Around line 85-87: Update the cache configuration documentation in init.md to
distinguish the cache property, which accepts Map<string, string>, from
cacheAdapter, which accepts AiCacheAdapter. Ensure the example and surrounding
description direct custom distributed-storage adapters to AiConfig.cacheAdapter
rather than AiConfig.cache.

In `@packages/plugins/ai/doc/recurrence.md`:
- Around line 7-14: Update the recurrenceAI example to call initAI before
submitting natural-language input to recurrenceAI, configuring the provider as
required while preserving the existing locale and count options.

In `@packages/plugins/ai/src/functions/recurrence.ts`:
- Around line 111-126: Update fetchFromProvider and both recurrence call sites
in the mode branches to accept and use a recurrence-specific system prompt or
request payload, ensuring recurrenceAI sends only the recurrence schema rather
than the helper’s default date-parser and iso instructions. Extend the
recurrence tests with a request-body assertion verifying the provider receives
the recurrence-specific schema without conflicting requirements.
- Around line 111-126: Update the recurrence request flow around
fetchFromProvider so TempoRecurrenceOptions.mode and minConfidence are honored
like parseAI: apply the configured confidence threshold before accepting
fallback responses, execute providers in parallel for race mode, and aggregate
responses using consensus mode. Preserve provider and raw-content assignment
only from an accepted result, and retain the existing debug logging for failed
providers.
- Around line 53-69: Replace the consecutive-day generation in the recurrence
builder, including take and createIterator, with RFC 5545 evaluation of the
RRULE from the anchor, applying the after and before window. Derive isFinite,
size, take results, and iterator output from the expanded occurrence set so
weekly rules, UNTIL limits, and other RRULE constraints are honored.

---

Outside diff comments:
In `@packages/tempo/public/llms-full.txt`:
- Around line 2923-2933: Update the public Tempo initialization examples at
packages/tempo/public/llms-full.txt lines 2923-2933, 2605-2616, and 6552-6577 to
use Tempo.init({ plugins: [...] }) or Tempo.extend(...) for plugin registration,
and remove the claim that re-calling Tempo.init() refreshes dynamically imported
plugins. Regenerate packages/tempo/public/llms-full.txt from the updated source
documentation so all affected examples reflect the actual idempotent
initialization lifecycle.

---

Nitpick comments:
In `@packages/tempo/public/llms-full.txt`:
- Around line 7145-7148: Replace the redis.keys call in clear with cursor-based
SCAN iteration and bounded delete batches while preserving the existing prefix
pattern and clearing behavior. Update the canonical source document containing
this Redis example, then regenerate packages/tempo/public/llms-full.txt so the
bundled example matches the source.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 40c265af-63e7-49d3-af3b-acb487dbd38e

📥 Commits

Reviewing files that changed from the base of the PR and between 35478c0 and 41ca35f.

📒 Files selected for processing (13)
  • packages/plugins/ai/doc/index.md
  • packages/plugins/ai/doc/init.md
  • packages/plugins/ai/doc/parse.md
  • packages/plugins/ai/doc/recurrence.md
  • packages/plugins/ai/src/core/init.ts
  • packages/plugins/ai/src/core/support.ts
  • packages/plugins/ai/src/core/types.ts
  • packages/plugins/ai/src/functions/parse.ts
  • packages/plugins/ai/src/functions/recurrence.ts
  • packages/plugins/ai/src/index.ts
  • packages/plugins/ai/test/parse.test.ts
  • packages/plugins/ai/test/recurrence.test.ts
  • packages/tempo/public/llms-full.txt
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/plugins/ai/src/index.ts
  • packages/plugins/ai/src/core/support.ts
  • packages/plugins/ai/src/functions/parse.ts

Comment thread packages/plugins/ai/doc/init.md
Comment thread packages/plugins/ai/doc/recurrence.md
Comment thread packages/plugins/ai/src/functions/recurrence.ts Outdated
Comment thread packages/plugins/ai/src/functions/recurrence.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (3)
packages/plugins/ai/test/recurrence.test.ts (2)

12-14: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Restore the fetch spy after each test.

vi.clearAllMocks() clears recorded calls. It does not remove the implementations installed by vi.spyOn(globalThis, 'fetch') at Lines 61, 102, and 136. The mock at Line 103 uses mockImplementation, so it stays active for every later test in the file and for any suite that shares the same global.

Call vi.restoreAllMocks() so each test starts from the real fetch.

♻️ Proposed change
 	afterEach(() => {
-		vi.clearAllMocks();
+		vi.restoreAllMocks();
 	});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/ai/test/recurrence.test.ts` around lines 12 - 14, Update the
afterEach cleanup near vi.clearAllMocks() to call vi.restoreAllMocks(), ensuring
fetch spies and their implementations are removed after every test and the real
global fetch is restored before the next test.

101-133: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the distinguishing behavior of race and consensus modes.

Both providers return the same payload, so resRace.rrule and resConsensus.rrule pass even if the mode branch is wrong. The test cannot separate race from consensus except through Line 132.

Add assertions that identify each mode. For race, give the two providers different latencies and different rrule values, then assert the faster value wins and that the slower request receives an abort signal. For consensus, add a case where the providers disagree and assert that the highest-confidence result is selected and that confidence is not raised to 1.0.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/ai/test/recurrence.test.ts` around lines 101 - 133,
Strengthen the test `should support provider race and consensus execution modes
in recurrenceAI` so each mode has distinguishable behavior: configure race
providers with different delays and rrule values, assert the faster result is
returned, and verify the slower request receives an abort signal. Add a
disagreement case for consensus providers, then assert the highest-confidence
result and its original confidence value are selected rather than being elevated
to 1.0.
packages/plugins/ai/src/functions/recurrence.ts (1)

157-170: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

take() re-expands the whole series on every call.

Line 162 expands offsetCursor + actualCount occurrences and then discards the leading offsetCursor entries. Paging through a series therefore costs O(n²) Tempo constructions. Line 150 adds a further 1000-occurrence expansion for finite rules without COUNT.

Cache the expanded occurrences on the closure and extend the cache only when the cursor passes its end.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/ai/src/functions/recurrence.ts` around lines 157 - 170, The
take function should cache expanded occurrences in its closure instead of
rebuilding and discarding the entire prefix on every call. Add an occurrences
cache, extend it only when offsetCursor + actualCount exceeds the cached length,
and slice the requested batch from that cache while preserving finite size
limits and cursor advancement; also avoid redundant expansion of the finite-rule
1000-occurrence baseline.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/plugins/ai/src/functions/recurrence.ts`:
- Around line 83-117: Update parseRRule and expandOccurrences to support every
BYDAY, BYHOUR, and BYMINUTE value by generating the cartesian product for each
recurrence period rather than using index 0. Add consistent BYMONTH and BYSETPOS
parsing and expansion so generated occurrences match the returned rrule, or
remove those keys from the provider prompt and document the supported subset. In
the monthly BYDAY calculation, correctly resolve negative ordinals such as -1FR
as the last matching weekday instead of treating them as the first.
- Around line 172-181: The createIterator generator currently expands only one
batch and truncates infinite or bounded recurrence series. Refactor
createIterator to lazily request successive occurrence pages, yielding each page
before fetching the next, and continue until the configured size limit or
before/UNTIL boundary is reached. Ensure COUNT rules are not prematurely
truncated by batchSize and preserve the documented lazy-generator behavior of
TempoRecurrenceResult.
- Around line 380-383: Update the rrule handling in the recurrence parsing flow
to require parsedData.rrule to be a non-empty string after trimming; otherwise
throw a TempoAiError instead of defaulting to FREQ=DAILY. Preserve the existing
trimmed rrule value for valid input and leave confidence handling unchanged.
- Around line 46-61: Update the recurrence parser around UNTIL, COUNT, BYHOUR,
and BYMINUTE to support RFC 5545 date-only UNTIL values by constructing a valid
date boundary without empty time components, while preserving full datetime
handling. Validate every parsed numeric field and avoid assigning NaN: use the
existing safe default behavior for INTERVAL and establish appropriate
finite-value handling for COUNT, BYHOUR, and BYMINUTE so expandOccurrences and
createRecurrenceResult never receive NaN.
- Around line 304-325: Add a no-op rejection handler to every promise created in
the Race branch’s availableProviders.map callback before passing the collection
to Promise.race. Keep the existing Promise.race result and abort behavior
unchanged, while ensuring slower provider promises cannot produce unhandled
rejections after parentController.abort().
- Around line 74-128: Update the recurrence loop around maxToFetch,
countProduced, and the afterTempo filter so rule.count limits occurrences
generated from the anchor rather than occurrences returned after filtering.
Increment the COUNT tracking for every valid generated candidate before applying
afterTempo/beforeTempo window filters, while preserving window filtering and
termination behavior; ensure the COUNT-based series ends at the correct
occurrence regardless of after.
- Around line 375-379: Validate the mode before the successfulResult
destructuring in the recurrence execution flow, covering both _state.config.mode
and options.mode inputs. Reject any value outside Fallback, Race, or Consensus
with a clear contextual error, and only destructure successfulResult after that
validation guarantees it is non-null.

In `@packages/plugins/ai/test/recurrence.test.ts`:
- Around line 153-169: Strengthen the recurrenceAI window test by replacing the
non-empty length assertion and redundant bounds loop with an exact expectation
of three occurrences: 2026-08-03, 2026-08-04, and 2026-08-05, each at 09:00.
Keep the isFinite assertion and use the existing items result to verify both
count and dates, covering the COUNT=10 and after-window interaction.

In `@packages/tempo/doc/3-extending-tempo/tempo.modularity.md`:
- Around line 120-123: Document one consistent plugin-registration lifecycle: in
packages/tempo/doc/3-extending-tempo/tempo.modularity.md lines 120-123, align
the Tempo.init() note with the guide’s “initial discovery” wording, clarifying
when automatic discovery and explicit registration occur; in
packages/tempo/doc/3-extending-tempo/tempo.plugin.md lines 107-108, reconcile
the Tempo.extend() guidance with the automatic-registration statement so users
know which operation applies at startup versus runtime.

---

Nitpick comments:
In `@packages/plugins/ai/src/functions/recurrence.ts`:
- Around line 157-170: The take function should cache expanded occurrences in
its closure instead of rebuilding and discarding the entire prefix on every
call. Add an occurrences cache, extend it only when offsetCursor + actualCount
exceeds the cached length, and slice the requested batch from that cache while
preserving finite size limits and cursor advancement; also avoid redundant
expansion of the finite-rule 1000-occurrence baseline.

In `@packages/plugins/ai/test/recurrence.test.ts`:
- Around line 12-14: Update the afterEach cleanup near vi.clearAllMocks() to
call vi.restoreAllMocks(), ensuring fetch spies and their implementations are
removed after every test and the real global fetch is restored before the next
test.
- Around line 101-133: Strengthen the test `should support provider race and
consensus execution modes in recurrenceAI` so each mode has distinguishable
behavior: configure race providers with different delays and rrule values,
assert the faster result is returned, and verify the slower request receives an
abort signal. Add a disagreement case for consensus providers, then assert the
highest-confidence result and its original confidence value are selected rather
than being elevated to 1.0.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3e34fed2-66aa-402f-80c9-3b4c95d09998

📥 Commits

Reviewing files that changed from the base of the PR and between 41ca35f and d5c260e.

📒 Files selected for processing (10)
  • packages/plugins/ai/doc/init.md
  • packages/plugins/ai/doc/rate-limits.md
  • packages/plugins/ai/doc/recurrence.md
  • packages/plugins/ai/src/core/support.ts
  • packages/plugins/ai/src/core/types.ts
  • packages/plugins/ai/src/functions/recurrence.ts
  • packages/plugins/ai/test/recurrence.test.ts
  • packages/tempo/doc/3-extending-tempo/tempo.modularity.md
  • packages/tempo/doc/3-extending-tempo/tempo.plugin.md
  • packages/tempo/public/llms-full.txt
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/plugins/ai/doc/recurrence.md
  • packages/plugins/ai/doc/init.md
  • packages/plugins/ai/doc/rate-limits.md
  • packages/plugins/ai/src/core/support.ts
  • packages/plugins/ai/src/core/types.ts
  • packages/tempo/public/llms-full.txt

Comment thread packages/plugins/ai/src/functions/recurrence.ts Outdated
Comment thread packages/plugins/ai/src/functions/recurrence.ts Outdated
Comment thread packages/plugins/ai/src/functions/recurrence.ts Outdated
Comment thread packages/plugins/ai/src/functions/recurrence.ts Outdated
Comment thread packages/plugins/ai/src/functions/recurrence.ts
Comment thread packages/plugins/ai/src/functions/recurrence.ts Outdated
Comment thread packages/plugins/ai/src/functions/recurrence.ts Outdated
Comment thread packages/plugins/ai/test/recurrence.test.ts
Comment thread packages/tempo/doc/3-extending-tempo/tempo.modularity.md Outdated
@magmacomputing

Copy link
Copy Markdown
Owner Author

@CodeRabbit full review

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 19

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/plugins/ai/test/parse.test.ts (1)

10-24: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Await initAI and disable the remote manifest in the tests.

initAI now returns a promise and starts loadRemoteManifest whenever remoteConfigUrl is neither set nor false (packages/plugins/ai/src/core/init.ts lines 47-52). Two consequences apply here:

  1. The hook does not return the promise, so each test begins while the manifest load is still pending. That pending load can overwrite _state.config.providers mid-test through init.ts lines 70-74. The assertion at line 46 on the compiled default model is exposed to this race.
  2. The manifest load calls fetch. When a test installs a fetch spy with mockResolvedValueOnce, the manifest request can consume the queued response that the test intended for a provider call.

Set remoteConfigUrl: false and return the promise from the hook.

🛠️ Proposed fix
-	beforeEach(() => {
+	beforeEach(async () => {
 		vi.spyOn(console, 'warn').mockImplementation(() => {});
 		vi.spyOn(console, 'error').mockImplementation(() => {});
 		vi.spyOn(console, 'log').mockImplementation(() => {});
 
 		if (isLiveTest) {
-			initAI({
-				providers: [{ id: liveProviderId, key: liveApiKey! }]
-			});
+			await initAI({
+				providers: [{ id: liveProviderId, key: liveApiKey! }],
+				remoteConfigUrl: false
+			});
 		} else {
-			initAI({
-				providers: [{ id: 'groq', key: 'mock-key-for-unit-testing' }]
-			});
+			await initAI({
+				providers: [{ id: 'groq', key: 'mock-key-for-unit-testing' }],
+				remoteConfigUrl: false
+			});
 		}
 	});

Apply the same remoteConfigUrl: false to the initAI call at line 31 and to the other in-test initAI calls.

Also applies to: 30-47

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/ai/test/parse.test.ts` around lines 10 - 24, Update the
beforeEach hook and all other initAI calls in this test to await initialization
by returning its promise, and pass remoteConfigUrl: false in each call. Ensure
no test starts before initAI completes and remote manifest loading is disabled
so fetch mocks remain reserved for provider requests.
packages/plugins/ai/src/index.ts (1)

35-36: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the stale scheduleAI scaffold entry.

Line 15 exports scheduleAI from ./functions/schedule.js. The commented scaffold at Lines 35-36 still lists scheduleAI under "Upcoming AI Function Exports". The comment now contradicts the active export.

🧹 Proposed cleanup
-// /** Resolves natural language scheduling prompts into optimal Tempo intervals */
-// export { scheduleAI, type TempoInterval } from './functions/schedule.js';
-
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/ai/src/index.ts` around lines 35 - 36, Remove the
commented-out scheduleAI scaffold entry under “Upcoming AI Function Exports” in
the package index, while leaving the active scheduleAI export unchanged.
🟡 Minor comments (23)
packages/tempo/public/llms.txt-27-27 (1)

27-27: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use a base-safe link for llms-full.txt.

Line 27 uses a root-relative URL. The documentation site uses base: '/magma/', so this link resolves outside the deployed site path. Use a relative link such as llms-full.txt. (raw.githubusercontent.com)

Proposed fix
-- [Full Documentation Concatenation](/llms-full.txt): Complete raw markdown documentation for RAG ingestion.
+- [Full Documentation Concatenation](llms-full.txt): Complete raw markdown documentation for RAG ingestion.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tempo/public/llms.txt` at line 27, Update the Full Documentation
Concatenation link in llms.txt to use the base-safe relative target
llms-full.txt instead of the root-relative /llms-full.txt path.
packages/plugins/ai/README.md-44-45 (1)

44-45: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Await asynchronous cache eviction.

clearAiCache() returns Promise<void> so a custom AiCacheAdapter can finish eviction asynchronously. This example starts eviction but does not wait for completion. Use await clearAiCache(...) or show explicit promise handling.

Proposed fix
-clearAiCache("The penultimate Tuesday before Thanksgiving in 2026");
+await clearAiCache("The penultimate Tuesday before Thanksgiving in 2026");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/ai/README.md` around lines 44 - 45, Update the README
example around clearAiCache to await its returned Promise, using await
clearAiCache(...) so asynchronous eviction completes before execution continues.
packages/plugins/ai/doc/recurrence.md-81-106 (1)

81-106: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Document reasoning in TempoRecurrenceResult.

The public result type includes optional reasoning, but this interface block omits it. The guide presents the block as the result contract. Add the field or state that the block is partial.

Proposed addition
   /** Provider ID responsible for processing or 'rrule-parser' */
   provider: string;
+
+  /** Reasoning / explanation of the recurrence pattern */
+  reasoning?: string;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/ai/doc/recurrence.md` around lines 81 - 106, Add the
optional reasoning field to the TempoRecurrenceResult interface alongside the
other result metadata, matching the public result type’s existing type and
documentation; do not leave the documented contract incomplete.
packages/plugins/ai/plan/v0.3.0-roadmap.md-7-30 (1)

7-30: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Update the roadmap to match the v0.3.0 contract.

The document describes v0.3.0 work as future implementation, although the changelog marks scheduleAI and recurrenceAI as released. The recurrence signature is also stale: the current contract uses Promise<TempoRecurrenceResult> and .take(count), not Promise<TempoRecurrenceRule> and .next(count). Mark completed handlers or move the remaining requirements to a future roadmap.

Proposed recurrence corrections
-### 1.5 ✅ `recurrenceAI(prompt: string, options?: AiOptions): Promise<TempoRecurrenceRule>`
+### 1.5 ✅ `recurrenceAI(prompt: string, options?: TempoRecurrenceOptions): Promise<TempoRecurrenceResult>`
-* Translates complex natural language repeating schedule descriptions into standard RRULE strings and `Tempo` instance date generators (`rule.next(count)`).
+* Translates complex natural language repeating schedule descriptions into standard RRULE strings and paged `Tempo` batches (`result.take(count)`).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/ai/plan/v0.3.0-roadmap.md` around lines 7 - 30, Update the
v0.3.0 roadmap to reflect the released status of scheduleAI and recurrenceAI,
marking completed handlers accordingly or moving unfinished requirements to a
later roadmap. Correct recurrenceAI to return Promise<TempoRecurrenceResult> and
describe recurrence generation with rule.take(count) instead of
Promise<TempoRecurrenceRule> and rule.next(count), while preserving accurate
entries for remaining handlers.
packages/plugins/ai/doc/init.md-73-90 (1)

73-90: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Complete the AiConfig reference.

This block omits remoteConfigUrl, which initAI() consumes and architecture.md documents. It also omits ttl, which the cache configuration example uses in rate-limits.md. Add these fields and verify the remaining exported options, or label this block as a partial excerpt.

Proposed additions
 export interface AiConfig {
   providers?: AiProvider[];
   mode?: 'fallback' | 'race' | 'consensus';
   timeout?: number;
   debug?: boolean;
   cache?: Map<string, string>;
   cacheAdapter?: AiCacheAdapter;
+  ttl?: number;
+  remoteConfigUrl?: string | false;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/ai/doc/init.md` around lines 73 - 90, Complete the AiConfig
reference by adding the exported remoteConfigUrl and ttl options consumed by
initAI() and used in the cache configuration example. Verify the interface
against the remaining documented/exported AiConfig fields, or explicitly label
the block as a partial excerpt if it is not intended to be exhaustive.
packages/plugins/ai/doc/rate-limits.md-141-144 (1)

141-144: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve a zero Redis TTL in the adapter example.

AiCacheAdapter.set() accepts an optional numeric ttlMs, and the parser calls it with the resolved TTL. if (ttlMs) skips 0, so redis.set() stores the key without px. Use ttlMs !== undefined when 0 means a valid non-expiring value.

Proposed fix
-    if (ttlMs) await redis.set(`tempo:ai:${key}`, value, { px: ttlMs });
+    if (ttlMs !== undefined) await redis.set(`tempo:ai:${key}`, value, { px: ttlMs });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/ai/doc/rate-limits.md` around lines 141 - 144, Update the
AiCacheAdapter.set example to check ttlMs !== undefined instead of relying on
truthiness, so a resolved TTL of 0 is passed to redis.set via the px option
while an omitted TTL still uses the no-options call.
packages/plugins/ai/src/core/init.ts-56-75 (1)

56-75: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Read fetchDefaults from merged state, not only from the current call.

Line 56 tests config.fetchDefaults. The merge at lines 37-41 stores fetchDefaults on _state.config, but no code reads it. A caller that registers the hook once and supplies providers in a later initAI call silently skips the hook:

await initAI({ fetchDefaults: myHook });          // hook stored, no providers
await initAI({ providers: [{ id: 'groq', key }] }); // hook ignored

Resolve the hook from the merged state so the behavior matches the persisted configuration.

🛠️ Proposed fix
-    if (config.fetchDefaults && config.providers) {
+    const fetchDefaults = config.fetchDefaults ?? _state.config.fetchDefaults;
+    if (fetchDefaults && config.providers) {
       const asyncProviders = await Promise.all(config.providers.map(async p => {
         const normalizedId = p.id?.toLowerCase() ?? '';
         const defaults = getResolvedProviderDefaults(normalizedId, remoteUrl, config.debug ?? _state.config.debug);
         let hookOptions: Partial<AiProvider> | null = null;
         try {
-          hookOptions = await config.fetchDefaults!(normalizedId);
+          hookOptions = await fetchDefaults(normalizedId);
         } catch { }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/ai/src/core/init.ts` around lines 56 - 75, Update the
provider initialization branch in initAI to read fetchDefaults from the merged
_state.config rather than only the current config argument. Use that persisted
hook when providers are supplied in a later call, while preserving the existing
defaults resolution, error handling, and revision checks.
packages/plugins/ai/src/core/support.ts-148-153 (1)

148-153: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Bound the provider error body before it is placed in the error message.

await response.text() reads the whole response body with no size limit, and the full text is embedded in the TempoAiError message. A misbehaving or hostile endpoint can return a very large body, which is then buffered into a string and propagated to every caller and log sink. Provider error bodies can also echo submitted prompt content.

Truncate the text before you build the message.

🛠️ Proposed fix
 		if (!response.ok) {
-			const errorText = await response.text();
+			const rawText = await response.text().catch(() => '');
+			const errorText = rawText.length > 512 ? `${rawText.slice(0, 512)}…[truncated]` : rawText;
 			const resetTime = limits?.resetAt ?? undefined;
 			_state.limits = limits;
 			throw new TempoAiError(`Provider ${provider.id} failed with status ${response.status}. Details: ${errorText}`, response.status, resetTime);
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/ai/src/core/support.ts` around lines 148 - 153, Bound the
provider error body in the non-OK response branch of the request flow before
constructing TempoAiError. Truncate errorText to a reasonable maximum while
preserving the existing status, resetTime, and error-message context, and use
the bounded text in the exception message.
packages/plugins/ai/src/functions/schedule.ts-243-255 (1)

243-255: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Return the first successful provider in race mode, not the first settled one.

Promise.race settles with the first promise that settles, including a rejection. If the fastest provider returns a 500, the whole call fails while slower providers are still able to succeed. The catch block then reports All providers failed in race mode, which does not match what happened.

Use Promise.any, which resolves with the first fulfillment and rejects with an AggregateError only when every provider fails.

🛠️ Proposed fix
 	} else if (mode === AiMode.Race || mode === 'race') {
 		const parentController = new AbortController();
 		try {
 			const promises = availableProviders.map(p => executeProviderCall(p, parentController.signal));
 			promises.forEach(p => p.catch(() => { }));
-			selectedResult = await Promise.race(promises);
+			selectedResult = await Promise.any(promises);
 			parentController.abort();
 		} catch (aggregateErr: any) {
 			parentController.abort();
+			const firstErr = aggregateErr instanceof AggregateError ? aggregateErr.errors[0] : aggregateErr;
-			throw aggregateErr instanceof TempoAiError
-				? aggregateErr
-				: new TempoAiError(`All providers failed in race mode: ${aggregateErr.message}`, 502);
+			throw firstErr instanceof TempoAiError
+				? firstErr
+				: new TempoAiError(`All providers failed in race mode: ${firstErr?.message}`, 502);
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/ai/src/functions/schedule.ts` around lines 243 - 255, Update
the race-mode branch around executeProviderCall and parentController to use
Promise.any instead of Promise.race, so selectedResult receives the first
successful provider and failures are aggregated only after all providers reject.
Preserve abort behavior on both success and failure, and adapt error handling to
safely report the AggregateError when every provider fails.
packages/plugins/ai/src/functions/schedule.ts-23-28 (1)

23-28: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Guard against an undefined title.

Line 27 tests only for key presence. If an event carries title: undefined, String(undefined) assigns the literal string 'undefined'. The b.title || 'Busy' fallbacks at line 69 and line 308 do not catch a non-empty string, so 'undefined' reaches the LLM prompt and the user-facing conflict message.

🛠️ Proposed fix
-				if ('title' in evt) title = String((evt as any).title);
-				else if ('label' in evt) title = String((evt as any).label);
+				const rawTitle = (evt as any).title ?? (evt as any).label;
+				if (rawTitle !== undefined && rawTitle !== null) title = String(rawTitle);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/ai/src/functions/schedule.ts` around lines 23 - 28, Update
the title extraction in the event parsing branch around parsePoint so title:
undefined does not become the literal string "undefined"; require a defined,
usable title value before converting and assigning it, while preserving the
label fallback and allowing the existing "Busy" fallbacks to apply when neither
is usable.
packages/plugins/ai/src/functions/parse.ts-12-12 (1)

12-12: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Strip the AI-only options from coreOptions.

Line 12 removes force, debug, mode, providers, minConfidence, softErrors, cache, and timeout. It leaves anchor, ttl, and cacheAdapter in coreOptions. coreOptions is then spread into the Tempo constructor at lines 30, 69, 84, 231, and 264.

That passes a cacheAdapter object and an AI cache ttl into the core parser, and it passes anchor alongside the already-resolved anchorStr at line 30. AiParseOptions also declares [key: string]: any, so any extra AI option reaches Tempo as well.

🛠️ Proposed fix
-  const { force, debug, mode: aiMode, providers, minConfidence, softErrors, cache: aiCacheOption, timeout: callTimeout, ...coreOptions } = options || {};
+  const { force, debug, mode: aiMode, providers, minConfidence, softErrors, cache: aiCacheOption,
+    timeout: callTimeout, anchor: _anchor, ttl: _ttl, cacheAdapter: _cacheAdapter, ...coreOptions } = options || {};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/ai/src/functions/parse.ts` at line 12, Update the options
destructuring in the parse flow so coreOptions contains only options supported
by Tempo, removing anchor, ttl, cacheAdapter, and any other AI-specific fields
before coreOptions is spread into each Tempo constructor. Preserve the existing
extraction of AI options and ensure the resolved anchorStr remains the sole
anchor value passed to Tempo.
packages/plugins/ai/src/core/manifest.ts-44-53 (1)

44-53: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Clear the abort timer on every path.

clearTimeout(timer) runs only after fetch resolves. If fetch rejects, or if response.json() throws, the timer stays pending for timeoutMs. Each failed call leaks a timer handle and keeps the Node event loop alive. Move the declaration outside the try and clear it in a finally block.

🛠️ Proposed fix
 	const fetchPromise = (async () => {
+		const controller = new AbortController();
+		const timer = setTimeout(() => controller.abort(), timeoutMs);
 		try {
-			const controller = new AbortController();
-			const timer = setTimeout(() => controller.abort(), timeoutMs);
-
 			const response = await fetch(targetUrl, {
 				signal: controller.signal,
 				headers: { Accept: 'application/json' }
 			});
 
-			clearTimeout(timer);
-
 			if (!response.ok) {

Then add the clear to the existing finally block:

 		} finally {
+			clearTimeout(timer);
 			_fetchPromiseMap.delete(targetUrl);
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/ai/src/core/manifest.ts` around lines 44 - 53, Update the
fetch flow in the surrounding manifest function so the timer declared alongside
the AbortController is always cleared, including when fetch or response.json
rejects. Move timer cleanup into the existing finally block and remove the
success-only clearTimeout call, preserving the current timeout and response
handling.
packages/plugins/ai/src/functions/recurrence.ts-312-312 (1)

312-312: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

reasoning is dropped unless debug is set.

Line 312 passes reasoning only when isDebug is true. The native RRULE path at Line 130 always passes a reasoning string. TempoRecurrenceResult.reasoning documents the field as the explanation of the recurrence pattern, with no debug condition.

A caller that reads result.reasoning gets a value for raw RRULE input and undefined for the same request routed to a provider. Either pass reasoning unconditionally, or document the debug requirement on the type and gate the native path the same way.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/ai/src/functions/recurrence.ts` at line 312, Update the
provider recurrence result construction around TempoRecurrenceResult so
reasoning is passed unconditionally, matching the native RRULE path and the
type’s documented contract; remove the isDebug gate from the reasoning field
while preserving any separate debug-only fields.
packages/plugins/ai/test/recurrence.test.ts-228-235 (1)

228-235: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the exact occurrence count before indexing.

Line 230 checks only length > 0. Lines 232 and 234 then index items[0] and items[1]. If the expansion returns one occurrence, the test fails with a TypeError on undefined.format instead of a clear count mismatch.

FREQ=MONTHLY;BYDAY=-1FR;UNTIL=20261231 from the 2026-08-01 anchor produces the last Friday of August through December, so take(5) should return 5 items. Assert that count, and assert size.

💚 Proposed change
 		expect(result.isFinite).toBe(true);
 		const items = result.take(5);
-		expect(items.length).toBeGreaterThan(0);
+		expect(items).toHaveLength(5);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/ai/test/recurrence.test.ts` around lines 228 - 235, Update
the recurrence test around result.take(5) to assert that items.length equals 5
instead of only being greater than zero, and assert the recurrence result’s size
is 5 before indexing items[0] through items[4].
packages/plugins/ai/test/schedule.test.ts-48-61 (1)

48-61: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Expose Interval<Tempo> in the scheduleAI result typing.

scheduleAI returns an Interval<Tempo> wrapper, while TempoScheduleResult only exposes the { start: Tempo; end: Tempo } shape and TempoInterval[] alternatives. Callers that use Interval methods on the slot or alternatives need an unsafe cast. Declare the result and alternatives in terms of Interval<Tempo>, or document the concrete Interval contract clearly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/ai/test/schedule.test.ts` around lines 48 - 61, Update the
scheduleAI result typing and its TempoScheduleResult contract to expose slot and
alternatives as Interval<Tempo> rather than plain start/end and TempoInterval[]
shapes. Preserve the existing runtime Interval behavior so callers can use
Interval methods without casts, including the alternatives collection.
packages/plugins/ai/src/functions/recurrence.ts-32-40 (1)

32-40: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Compute size from the expanded window when after or before is supplied.

expandRRuleEpochs stops the series after producing COUNT generated occurrences from the anchor, then applies afterMs/beforeMs filtering before incrementing resultsCount. For FREQ=DAILY;COUNT=10 with an after/before window, result.size currently reports 10 even though the windowed size is smaller. Use the expansion length when a window is supplied, while still respecting rule.count as the series limit if the window does not narrow it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/ai/src/functions/recurrence.ts` around lines 32 - 40, Update
the size calculation in the recurrence expansion flow so supplying options.after
or options.before derives size from the filtered expandOccurrences result, while
retaining rule.count as the series limit when the window does not reduce it.
Preserve the existing finite-rule and infinite-rule handling, including
Number.POSITIVE_INFINITY for unbounded expansions.
packages/tempo/public/esm_sh.index.html-217-219 (1)

217-219: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Expose the result as a live region.

The page changes #result after module execution, but the element is a plain div. Add role="status" or aria-live="polite" so screen readers announce the result and error message.

Proposed fix
-				<div id="result" class="result pulse-loading">Initializing Temporal...</div>
+				<div id="result" class="result pulse-loading" role="status" aria-live="polite">Initializing Temporal...</div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tempo/public/esm_sh.index.html` around lines 217 - 219, Update the
result element in the output panel to expose dynamic content as an accessible
live region by adding role="status" or aria-live="polite"; preserve its existing
id, classes, and initialization text.
packages/tempo/public/esm_sh.index.html-236-238 (1)

236-238: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Handle static module imports outside the instantiation try.

The import '@js-temporal/polyfill' and import { Tempo } from '@magmacomputing/tempo' statements run before the try block. If either module or its import map fails to resolve/load, that exception does not enter the catch, so the page can stay at Initializing Temporal... while the console error remains unhandled by the UI.

Use top-level try/catch around await import(...), or add a window.addEventListener('error', ...) handler for module-load failures, so this path always updates the visible result.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tempo/public/esm_sh.index.html` around lines 236 - 238, Move the
static imports for `@js-temporal/polyfill` and Tempo out of the module’s top-level
declarations and dynamically import them within the existing initialization
try/catch. Ensure resolution or loading failures reach the catch handler so the
visible result is updated instead of remaining at “Initializing Temporal...”.
packages/tempo/doc/1-getting-started/ai-integration.md-56-56 (1)

56-56: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use one documented month token in both AI guides.

Both changed prompts tell AI assistants to use {mon}, but the available snippet table defines the month token as {mm}. This inconsistency can produce layouts that do not match the documented grammar.

  • packages/tempo/doc/1-getting-started/ai-integration.md#L56-L56: replace {mon} with {mm} in the named-token list.
  • packages/tempo/doc/3-extending-tempo/tempo.layout.md#L108-L109: replace {mon} with {mm} in the AI prompt.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tempo/doc/1-getting-started/ai-integration.md` at line 56, Replace
the undocumented {mon} month token with the documented {mm} token in the
named-token list in packages/tempo/doc/1-getting-started/ai-integration.md:56-56
and in the AI prompt in
packages/tempo/doc/3-extending-tempo/tempo.layout.md:108-109, keeping both
guides consistent with the snippet table.
packages/tempo/doc/2-core-concepts/tempo.parse.md-130-143 (1)

130-143: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep the {tzd} reference aligned with the implementation.

{tzd} already accepts registered timezone abbreviations such as AEST and PST, so this parsing example is valid. Update packages/tempo/doc/3-extending-tempo/tempo.layout.md so the token reference describes {tzd} as accepting offset designators and registered timezone abbreviations.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tempo/doc/2-core-concepts/tempo.parse.md` around lines 130 - 143,
Update the `{tzd}` token reference in the layout documentation to state that it
accepts both timezone offset designators and registered timezone abbreviations
such as AEST and PST. Keep the description aligned with the existing parser
behavior and avoid changing the parsing example.
packages/tempo/doc/1-getting-started/ai-integration.md-24-25 (1)

24-25: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Do not list .cursorrules in the VS Code/Copilot setup.

This is Copilot Chat configuration, and .github/copilot-instructions.md is the VS Code Copilot workspace instruction path. .cursorrules is a Cursor/legacy file, so keep this guide focused on Copilot or move the Cursor file to the Cursor section.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tempo/doc/1-getting-started/ai-integration.md` around lines 24 - 25,
Update the “VS Code & GitHub Copilot” section to mention only
.github/copilot-instructions.md as the workspace instruction file; remove
.cursorrules from this setup guidance, leaving any Cursor-specific guidance to
its appropriate section.
packages/tempo/src/module/module.parse.ts-263-264 (1)

263-264: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Limit the date-prefix guard bypass.

The bypass accepts inputs like 2024-99-99 and 2024-01-01abc, allowing invalid or partially matching text to reach layout parsing. Require valid month/day ranges and enforce a boundary after the date component.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tempo/src/module/module.parse.ts` around lines 263 - 264, Update the
date-prefix check in the module parsing guard so it only bypasses the guard for
valid calendar-shaped prefixes: restrict month and day to valid ranges and
require the date to end at a boundary rather than accepting trailing letters or
other partial text. Keep the existing guard assignment behavior for genuinely
valid date prefixes.
packages/tempo/src/support/support.default.ts-37-37 (1)

37-37: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Require [+-] in the short timezone-offset branch.

Match.offset is embedded into Token.tzd, and the short-offset branch allows a missing sign. Because GMT 10:30 and UTC 1030 are classified as timezone input, set Match.offset to require + or - for numeric short offsets so clock values cannot collide here. Also applies to line 71.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tempo/src/support/support.default.ts` at line 37, Update the short
numeric-offset branch of Match.offset in the offset definitions at both
referenced locations to require an explicit + or - sign before the hour, while
preserving the existing colon and four-digit offset formats and GMT/UTC prefix
handling.
🧹 Nitpick comments (11)
packages/plugins/ai/test/manifest.test.ts (1)

10-18: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reset the shared _state between tests.

resetManifestCache() clears only the manifest maps. initAI mutates the module-level _state in packages/plugins/ai/src/core/init.ts, and that object persists across tests and across test files in the same worker. The test at line 161 leaves _state.config.remoteConfigUrl set to https://tempo.magmacomputing.com.au/manifest-2.json. Any later test that calls initAI without remoteConfigUrl inherits that URL through line 22 of init.ts and resolves defaults from the wrong cache key.

Reset the configuration in beforeEach so each test starts from a known baseline.

♻️ Proposed change
 	beforeEach(() => {
 		resetManifestCache();
 		vi.restoreAllMocks();
+		// isolate the shared module-level _state between tests
+		initAI({ providers: [], remoteConfigUrl: false, fetchDefaults: undefined });
 	});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/ai/test/manifest.test.ts` around lines 10 - 18, Update the
test setup around beforeEach in manifest.test.ts to reset the shared _state
configuration mutated by initAI, including clearing config.remoteConfigUrl,
before each test. Keep resetManifestCache() and mock restoration intact so every
test starts with the default configuration and cannot inherit a prior remote
manifest URL.
packages/plugins/ai/test/schedule.test.ts (1)

10-10: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the commented-out spy.

Line 10 leaves a disabled console.error spy in place. Lines 9 and 11 keep the other two spies active. Delete the line, or restore it so the suite silences console.error consistently.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/ai/test/schedule.test.ts` at line 10, Remove the
commented-out console.error spy near the existing active spies in the schedule
test, leaving the active spy setup unchanged.
packages/plugins/ai/src/types/schedule.type.ts (1)

74-111: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract the duplicated AI metadata shape into a named interface.

The ai object literal is declared twice with identical members, in TempoScheduleResult (Lines 75-82) and TempoScheduleMeta (Lines 103-110). A named interface keeps the two declarations in sync.

♻️ Proposed refactor
+/**
+ * ## TempoScheduleAiMeta
+ * Extended AI execution metadata attached to scheduling results.
+ */
+export interface TempoScheduleAiMeta {
+	provider: string;
+	confidence: number;
+	conflictBumped?: boolean | undefined;
+	originalSlot?: TempoInterval | undefined;
+	reasoning?: string | undefined;
+	[key: string]: any;
+}
 	/** Extended AI execution metadata */
-	ai?: {
-		provider: string;
-		confidence: number;
-		conflictBumped?: boolean | undefined;
-		originalSlot?: TempoInterval | undefined;
-		reasoning?: string | undefined;
-		[key: string]: any;
-	} | undefined;
+	ai?: TempoScheduleAiMeta | undefined;
 	/** Extended AI execution metadata */
-	ai: {
-		provider: string;
-		confidence: number;
-		conflictBumped?: boolean | undefined;
-		originalSlot?: TempoInterval | undefined;
-		reasoning?: string | undefined;
-		[key: string]: any;
-	};
+	ai: TempoScheduleAiMeta;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/ai/src/types/schedule.type.ts` around lines 74 - 111,
Extract the duplicated ai metadata object shape into a named interface in the
schedule type definitions, then replace the inline ai declarations in
TempoScheduleResult and TempoScheduleMeta with that interface while preserving
their existing optionality.
packages/plugins/ai/test/cache.test.ts (1)

8-11: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Await initAI in the tests.

initAI returns Promise<void>. Every call site here ignores the returned promise. The synchronous part of initAI sets _state.config, so the assertions still pass today, but the async tail keeps running after the test body continues and after afterEach restores mocks. Awaiting removes the floating promise and the cross-test ordering dependency.

♻️ Proposed change (apply the same pattern to each call site)
-	beforeEach(() => {
+	beforeEach(async () => {
 		vi.restoreAllMocks();
 		Tempo.cache.clear();
-		initAI({
+		await initAI({
 			providers: [{ id: 'groq', key: 'mock-test-key' }],
 			remoteConfigUrl: false
 		});
 	});

Also applies to: 37-41, 74-78, 105-108, 130-130

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/ai/test/cache.test.ts` around lines 8 - 11, Await every
initAI call in the tests, including the call sites around the existing test
setup and the additional reported locations, so each test completes
initialization before assertions or cleanup run. Mark the containing test or
setup callbacks async as needed while preserving the current initialization
arguments and test behavior.
packages/plugins/ai/src/functions/recurrence.ts (1)

46-58: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

ensureCached re-expands the whole series on every page.

Lines 53-54 discard cachedOccurrences and rebuild it from the anchor each time the needed count grows. Paged reads through take() therefore cost O(n²) expansions across n pages. Each call also re-runs expandRRuleEpochs from the anchor.

Grow the cache instead of rebuilding it, for example by expanding in geometric steps and appending only the new tail.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/ai/src/functions/recurrence.ts` around lines 46 - 58, Update
ensureCached to preserve cachedOccurrences when neededCount grows instead of
clearing and rebuilding from anchorTempo. Expand only the missing tail, using
geometric growth as appropriate, append new occurrences, and set fullyExpanded
when expansion returns fewer items than requested while preserving the existing
after/before filtering behavior.
packages/library/README.md (1)

18-28: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add the RRULE module to the key modules table.

This release adds rrule.library to the public barrel. The table omits it, so the new utilities are not discoverable from the README.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/library/README.md` around lines 18 - 28, Update the key modules
table in the README to add an RRULE entry for the newly public rrule.library
utilities, including a concise description consistent with the existing module
rows.
packages/library/CHANGELOG.md (1)

11-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

List all new public exports.

rrule.library also exports expandRRuleEpochs and isFiniteRRule. Both are part of the public surface through common.index.ts. Add them to the entry.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/library/CHANGELOG.md` at line 11, Update the RRULE Support changelog
entry to list the complete public export set, adding expandRRuleEpochs and
isFiniteRRule alongside the existing rrule.library utilities exposed through
common.index.ts.
packages/tempo/test/discrete/standalone_parse.test.ts (1)

84-97: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the resolved offset for the named zones.

The named-zone cases assert timeZoneId only. PST denotes a fixed -08:00 abbreviation, but America/Los_Angeles on 6 August resolves to -07:00 because daylight saving is active. The test cannot detect whether the parser preserves the literal abbreviation offset or applies the IANA zone rules.

Add offset assertions for both named-zone cases so the intended semantics are pinned.

💚 Proposed additions
 	expect(zdtAest.timeZoneId).toBe('Australia/Sydney');
+	expect(zdtAest.offset).toBe('+10:00');
 
 	const zdtPst = parse('Aug 6, 16:16 PST');
 	expect(zdtPst.month).toBe(8);
 	expect(zdtPst.day).toBe(6);
 	expect(zdtPst.hour).toBe(16);
 	expect(zdtPst.minute).toBe(16);
 
 	expect(zdtPst.timeZoneId).toBe('America/Los_Angeles');
+	expect(zdtPst.offset).toBe('-07:00');					// DST active on 6 August
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tempo/test/discrete/standalone_parse.test.ts` around lines 84 - 97,
Extend the named-zone assertions in the parse test for zdtAest and zdtPst to
verify their resolved offset values, covering both the AEST abbreviation and
PST’s fixed -08:00 offset rather than relying only on timeZoneId.
packages/tempo/test/plugins/extend.recurrence.test.ts (1)

4-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a case for a rule that yields no occurrence.

getNextRRuleEpoch falls back to a fixed one-day shift when the rule is exhausted. That branch is a silent, non-conforming result. Add a test with an expired UNTIL so the fallback behaviour is explicit and intentional.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tempo/test/plugins/extend.recurrence.test.ts` around lines 4 - 16,
Extend the extend.recurrence test suite with a Tempo.prototype.nextOccurrence
case using an RRULE containing an expired UNTIL, and assert the current fixed
one-day fallback result explicitly. Keep the existing string and rrule-object
coverage unchanged.
packages/library/src/common/rrule.library.ts (1)

192-192: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

options.count is ignored when the rule declares COUNT.

maxToFetch prefers rule.count over options.count. getNextRRuleEpoch requests one occurrence, but for FREQ=DAILY;COUNT=500 the expansion generates up to 500 candidates before returning the first. Use the minimum of the two bounds.

♻️ Proposed change
-	const maxToFetch = isDefined(rule.count) ? rule.count : (options?.count ?? 100);
+	const requested = options?.count ?? 100;
+	const maxToFetch = isDefined(rule.count) ? Math.min(rule.count, requested) : requested;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/library/src/common/rrule.library.ts` at line 192, Update the
maxToFetch calculation in the rule expansion flow to use the smaller of
rule.count and options.count when both are defined, while retaining the existing
default of 100 when neither bound is provided. This ensures getNextRRuleEpoch
and other callers never expand beyond either configured limit.
packages/library/test/common/rrule_library.test.ts (1)

40-55: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the untested RRULE paths.

The suite covers DAILY expansion only. The following behaviour is unverified: Sunday handling in WEEKLY and MONTHLY (BYDAY=SU), MONTHLY with nth selectors, YEARLY with BYMONTH, UNTIL and COUNT termination, BYSETPOS, and isFiniteRRule.

A Sunday case would expose the DAY_MAP.SUN defect flagged in packages/library/src/common/rrule.library.ts at Line 214. Add at least that case with the fix.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/library/test/common/rrule_library.test.ts` around lines 40 - 55,
Extend the tests around expandRRuleEpochs and getNextRRuleEpoch to cover Sunday
BYDAY handling for WEEKLY and MONTHLY, including correcting the DAY_MAP.SUN
mapping in the RRULE implementation. Add coverage for MONTHLY nth selectors,
YEARLY BYMONTH, UNTIL and COUNT termination, BYSETPOS, and isFiniteRRule,
preserving expected occurrence ordering and termination behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9217fb79-1cb3-4494-b4e8-8b00f62cebbd

📥 Commits

Reviewing files that changed from the base of the PR and between 6547c1f and 65306fe.

⛔ Files ignored due to path filters (3)
  • packages/library/img/library-logo.svg is excluded by !**/*.svg
  • packages/tempo/img/library-logo.svg is excluded by !**/*.svg
  • packages/tempo/public/library-logo.svg is excluded by !**/*.svg
📒 Files selected for processing (73)
  • .github/workflows/deploy-docs.yml
  • package.json
  • packages/library/CHANGELOG.md
  • packages/library/README.md
  • packages/library/package.json
  • packages/library/src/common.index.ts
  • packages/library/src/common/rrule.library.ts
  • packages/library/test/common/rrule_library.test.ts
  • packages/plugins/.bin/check-branch-diff.sh
  • packages/plugins/.bin/check-versions.sh
  • packages/plugins/ai/CHANGELOG.md
  • packages/plugins/ai/README.md
  • packages/plugins/ai/doc/architecture.md
  • packages/plugins/ai/doc/index.md
  • packages/plugins/ai/doc/init.md
  • packages/plugins/ai/doc/parse.md
  • packages/plugins/ai/doc/rate-limits.md
  • packages/plugins/ai/doc/recurrence.md
  • packages/plugins/ai/package.json
  • packages/plugins/ai/plan/v0.3.0-roadmap.md
  • packages/plugins/ai/src/core/config.ts
  • packages/plugins/ai/src/core/init.ts
  • packages/plugins/ai/src/core/manifest.ts
  • packages/plugins/ai/src/core/support.ts
  • packages/plugins/ai/src/functions/parse.ts
  • packages/plugins/ai/src/functions/recurrence.ts
  • packages/plugins/ai/src/functions/schedule.ts
  • packages/plugins/ai/src/index.ts
  • packages/plugins/ai/src/types/common.type.ts
  • packages/plugins/ai/src/types/index.ts
  • packages/plugins/ai/src/types/parse.type.ts
  • packages/plugins/ai/src/types/recurrence.type.ts
  • packages/plugins/ai/src/types/schedule.type.ts
  • packages/plugins/ai/test/cache.test.ts
  • packages/plugins/ai/test/manifest.test.ts
  • packages/plugins/ai/test/parse.test.ts
  • packages/plugins/ai/test/recurrence.test.ts
  • packages/plugins/ai/test/schedule.test.ts
  • packages/tempo/.vitepress/config.ts
  • packages/tempo/.vitepress/theme/data/catalog.json
  • packages/tempo/CHANGELOG.md
  • packages/tempo/bin/expand-typedoc.mjs
  • packages/tempo/bin/generate-llms-txt.mjs
  • packages/tempo/bin/update-version.mjs
  • packages/tempo/doc/1-getting-started/ai-integration.md
  • packages/tempo/doc/1-getting-started/installation.md
  • packages/tempo/doc/2-core-concepts/tempo.parse.md
  • packages/tempo/doc/3-extending-tempo/tempo.layout.md
  • packages/tempo/doc/3-extending-tempo/tempo.modularity.md
  • packages/tempo/doc/3-extending-tempo/tempo.plugin.md
  • packages/tempo/doc/6-utility-library/tempo.library.md
  • packages/tempo/package.json
  • packages/tempo/public/bundle.index.html
  • packages/tempo/public/esm_core.index.html
  • packages/tempo/public/esm_full.index.html
  • packages/tempo/public/esm_sh.index.html
  • packages/tempo/public/llms-full.txt
  • packages/tempo/public/llms.txt
  • packages/tempo/public/providers.v1.json
  • packages/tempo/public/script.index.html
  • packages/tempo/src/engine/engine.composer.ts
  • packages/tempo/src/engine/engine.lexer.ts
  • packages/tempo/src/interval.class.ts
  • packages/tempo/src/module/module.parse.ts
  • packages/tempo/src/plugin/extend/extend.recurrence.ts
  • packages/tempo/src/support/support.cache.ts
  • packages/tempo/src/support/support.default.ts
  • packages/tempo/src/support/support.enum.ts
  • packages/tempo/src/tempo.class.ts
  • packages/tempo/src/tempo.version.ts
  • packages/tempo/test/discrete/standalone_parse.test.ts
  • packages/tempo/test/plugins/extend.recurrence.test.ts
  • packages/tempo/test/support/cache.test.ts

Comment on lines +82 to +84
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.TEMPO_WORKSPACE_DISPATCH_TOKEN }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

# Resolve the current v3 tag before selecting and reviewing the commit to pin.
git ls-remote https://github.com/peter-evans/repository-dispatch.git refs/tags/v3

# Find all current uses for a consistent pinning policy.
rg -n 'peter-evans/repository-dispatch@' .github/workflows

Repository: magmacomputing/magma

Length of output: 298


Pin peter-evans/repository-dispatch to a reviewed commit SHA.

peter-evans/repository-dispatch@v3 is a mutable tag and receives secrets.TEMPO_LOCK; pin this third-party action to a full commit SHA and keep the release tag in a comment for updates.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/deploy-docs.yml around lines 82 - 84, Update the
repository-dispatch action reference in the workflow to use the reviewed full
commit SHA instead of the mutable v3 tag, and retain the corresponding release
tag in an inline comment for future updates. Leave the existing
TEMPO_WORKSPACE_DISPATCH_TOKEN configuration unchanged.

Comment on lines +211 to +222
if (rule.byDay && rule.byDay.length > 0) {
periodBases = rule.byDay.map(bd => {
const targetDay = DAY_MAP[bd.day] ?? 1;
const currentDow = baseDate.getUTCDay() === 0 ? DAY_MAP.SUN : baseDate.getUTCDay();
const diff = (targetDay - currentDow + DAYS_IN_WEEK) % DAYS_IN_WEEK;
const targetDate = new Date(baseDate.getTime());
targetDate.setUTCDate(targetDate.getUTCDate() + diff);
return targetDate;
});
} else {
periodBases = [baseDate];
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

DAY_MAP.SUN is undefined and produces NaN occurrences.

DAY_MAP defines the key SU, not SUN. At Line 214 DAY_MAP.SUN evaluates to undefined whenever baseDate falls on a Sunday. diff then becomes NaN, setUTCDate(NaN) creates an Invalid Date, and expandRRuleEpochs pushes NaN into results. getNextRRuleEpoch then returns NaN, and Tempo.prototype.nextOccurrence constructs a Tempo from NaN.

The same key error exists at Line 238 in the MONTHLY branch. There, dow becomes undefined for every Sunday, so BYDAY=SU never matches and monthly Sunday rules yield no candidates.

Use DAY_MAP.SU at both sites.

🐛 Proposed fix
-						const currentDow = baseDate.getUTCDay() === 0 ? DAY_MAP.SUN : baseDate.getUTCDay();
+						const currentDow = baseDate.getUTCDay() === 0 ? DAY_MAP.SU : baseDate.getUTCDay();

And in the MONTHLY branch at Line 238:

-							const dow = d.getUTCDay() === 0 ? DAY_MAP.SUN : d.getUTCDay();
+							const dow = d.getUTCDay() === 0 ? DAY_MAP.SU : d.getUTCDay();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/library/src/common/rrule.library.ts` around lines 211 - 222, Replace
the invalid DAY_MAP.SUN references with DAY_MAP.SU in both the weekly
period-base calculation and the monthly BYDAY matching logic, preserving all
other recurrence behavior.

Comment on lines +225 to +229
case 'MONTHLY': {
baseDate.setUTCMonth(baseDate.getUTCMonth() + step * rule.interval);
const year = baseDate.getUTCFullYear();
const month = baseDate.getUTCMonth() + 1;
const daysInMonth = getDaysInMonth(year, month);

Copy link
Copy Markdown
Contributor

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

setUTCMonth overflows for anchors after day 28.

setUTCMonth keeps the day-of-month. For an anchor on 31 January, step = 1 produces 3 March, not 28 February. The RFC 5545 rule is to skip months that do not contain the anchor day. This affects every MONTHLY rule without BYDAY.

Clamp or skip explicitly. One approach is to build the date from Date.UTC(year, monthIndex, day, ...) and discard the period when the resulting month index differs from the intended one.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/library/src/common/rrule.library.ts` around lines 225 - 229, Update
the MONTHLY branch in the recurrence calculation to avoid setUTCMonth overflow
for anchors after day 28. Construct each candidate using the intended year/month
and original day, then skip the period when the resulting month differs from the
intended month, preserving valid anchor dates and existing BYDAY handling.

Comment on lines +258 to +262
case 'YEARLY': {
baseDate.setUTCFullYear(baseDate.getUTCFullYear() + step * rule.interval);
periodBases = [baseDate];
break;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

YEARLY does not expand BYMONTH or BYDAY.

The YEARLY branch produces one base per step. Line 271 then filters that base by BYMONTH. For an anchor in August and FREQ=YEARLY;BYMONTH=1, every base is filtered out. The loop runs the full 1000 steps and returns an empty array. getNextRRuleEpoch then falls back to a one-day shift, which is not an occurrence of the rule.

Expand BYMONTH into one base per listed month within the target year, instead of using it only as a filter for YEARLY. The test at packages/library/test/common/rrule_library.test.ts Line 36 parses such a rule but does not expand it, so this gap is not covered.

Also applies to: 271-272

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/library/src/common/rrule.library.ts` around lines 258 - 262, The
YEARLY branch in the rule expansion logic must create a base date for each month
listed by BYMONTH in the target year, rather than creating only one base and
filtering it later. Update the YEARLY handling around periodBases and the
BYMONTH/BYDAY filtering flow so rules such as FREQ=YEARLY;BYMONTH=1 produce
January candidates and preserve existing behavior when BYMONTH is absent.

Comment on lines +33 to +49
is_gt=$(node --input-type=module -e '
function parseSemver(v) {
const m = String(v).trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/);
if (!m) return null;
return { major: parseInt(m[1], 10), minor: parseInt(m[2], 10), patch: parseInt(m[3], 10), prerelease: m[4] || "" };
}
function compare(a, b) {
const pa = parseSemver(a), pb = parseSemver(b);
if (!pa || !pb) return false;
if (pa.major !== pb.major) return pa.major > pb.major;
if (pa.minor !== pb.minor) return pa.minor > pb.minor;
if (pa.patch !== pb.patch) return pa.patch > pb.patch;
if (!pa.prerelease && pb.prerelease) return true;
if (pa.prerelease && !pb.prerelease) return false;
return pa.prerelease > pb.prerelease;
}
console.log(compare(process.argv[1], process.argv[2]) ? "true" : "false");

Copy link
Copy Markdown
Contributor

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

Use a SemVer-compliant prerelease comparator.

Line 47 compares the full prerelease string lexicographically. The check accepts 1.0.0-alpha.2 as newer than 1.0.0-alpha.10, although it is older under Semantic Version precedence. This can approve a prerelease downgrade.

Replace parseSemver and compare with a SemVer-compliant comparator. It must compare each prerelease identifier by SemVer rules.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/.bin/check-branch-diff.sh` around lines 33 - 49, Replace the
inline parseSemver and compare functions in the Node version check with
SemVer-compliant prerelease comparison: split prerelease values into
dot-separated identifiers, compare numeric identifiers numerically, compare
nonnumeric identifiers lexically, rank numeric identifiers below nonnumeric
ones, and treat a shorter equal-prefix sequence as lower precedence. Preserve
existing core-version and stable-versus-prerelease ordering and return the same
boolean output.

Comment on lines +5 to +8
"groq": {
"url": "https://api.groq.com/openai/v1/chat/completions",
"model": "llama-3.3-70b-versatile",
"tokenParam": "max_tokens"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Repository files matching providers.v1.json:\n'
fd -a 'providers\.v1\.json$' . || true

printf '\nFile content:\n'
if [ -f packages/tempo/public/providers.v1.json ]; then
  cat -n packages/tempo/public/providers.v1.json
else
  echo "packages/tempoPublic/providers.v1.json not found"
fi

printf '\nReferences to providers.v1 and provider model selection:\n'
rg -n "providers\.v1|providers\.v1\.json|llama-3\.3-70b-versatile|tokenParam|max_tokens|model" packages/tempo -S || true

Repository: magmacomputing/magma

Length of output: 6648


🌐 Web query:

Groq deprecations llama-3.3-70b-versatile August 16 2026

💡 Result:

The Groq model llama-3.3-70b-versatile is scheduled for deprecation and will shut down on August 16, 2026 [1][2][3]. This deprecation primarily impacts usage on Groq's free and developer tiers; committed-spend enterprise contracts are generally exempt from these specific model retirements [4][5]. Groq officially recommends that developers migrate their applications to alternative models, specifically suggesting openai/gpt-oss-120b or qwen/qwen3.6-27b as replacements [1][3][5]. Users are advised to update their API integrations before the shutdown deadline to avoid service interruptions [6][5].

Citations:


Replace the retiring Groq default model.

llama-3.3-70b-versatile shuts down on August 16, 2026 for free and developer usage. Groq recommends switching to openai/gpt-oss-120b or qwen/qwen3.6-27b; update this public manifest and test the replacement request format before release.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tempo/public/providers.v1.json` around lines 5 - 8, Update the groq
provider entry in providers.v1.json to replace the retiring
llama-3.3-70b-versatile default with Groq’s recommended openai/gpt-oss-120b or
qwen/qwen3.6-27b model, and verify the replacement request format remains
compatible before release.

Comment on lines +304 to +309
if (zone && /^([+-]\d{1,2})$/.test(zone)) {
const sign = zone[0];
const num = Math.abs(parseInt(zone, 10));
zone = `${sign}${num.toString().padStart(2, '0')}:00`;
}

Copy link
Copy Markdown
Contributor

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate relevant files =="
git ls-files | rg 'packages/tempo/src/(engine/engine\.lexer\.ts|support/support\.default\.ts|.*timezone|.*match).*' || true

echo "== lexer section =="
sed -n '260,330p' packages/tempo/src/engine/engine.lexer.ts

echo "== support offset refs =="
rg -n "parseZone|Match\.offset|offset|timeZone|zone" packages/tempo/src -S

echo "== package deps temporal =="
for f in package.json packages/tempo/package.json pnpm-lock.yaml package-lock.json yarn.lock; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    rg -n "`@js-temporal/polyfill`|Temporal|moment|dayjs|luxon" "$f" -S || true
  fi
done

echo "== behavioral parser probe =="
python3 - <<'PY'
import re
zones = ["+5", "5", "+05", "+5:30", "+530", "-4", "UTC", "+UTC", "GMT", "+10:30", "+0:30"]
for z in zones:
    old = z
    if z and re.match(r'^([+-]\d{1,2})$', z):
        sign = z[0]
        num = abs(int(z,10))
        old = z
        z = f'{sign}{str(num).zfill(2)}:00'
    print(f"{z!r:12} -> {old if z==z else z!r}")
PY

Repository: magmacomputing/magma

Length of output: 30745


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate relevant files =="
git ls-files | rg 'packages/tempo/src/(engine/engine\.lexer\.ts|support/support\.default\.ts|.*timezone|.*match).*' || true

echo "== lexer section =="
sed -n '260,330p' packages/tempo/src/engine/engine.lexer.ts

echo "== support offset refs =="
rg -n "parseZone|Match\.offset|offset|timeZone|zone" packages/tempo/src -S

echo "== package deps temporal =="
for f in package.json packages/tempo/package.json pnpm-lock.yaml package-lock.json yarn.lock; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    rg -n "`@js-temporal/polyfill`|Temporal|moment|dayjs|luxon" "$f" -S || true
  fi
done

echo "== behavioral parser probe =="
python3 - <<'PY'
import re
zones = ["+5", "5", "+05", "+5:30", "+530", "-4", "UTC", "+UTC", "GMT", "+10:30", "+0:30"]
for z in zones:
    old = z
    if z and re.match(r'^([+-]\d{1,2})$', z):
        sign = z[0]
        num = abs(int(z,10))
        z = f'{sign}{str(num).zfill(2)}:00'
    matched_short = bool(re.match(r'^([+-]?)\d{1,2}(?::?\d{2})?$', old))
    print(f"input={z!r:8} previously_normalized_to_short_offset={matched_short}")
PY

Repository: magmacomputing/magma

Length of output: 31139


🌐 Web query:

Temporal ZonedDateTime toPlainDateTime toZonedDateTime TimeZone offset valid ID examples @js-temporal/polyfill``

💡 Result:

In the Temporal API (supported by @js-temporal/polyfill), Temporal.ZonedDateTime is a timezone-aware, calendar-aware date/time object [1][2]. Conversion Methods toPlainDateTime: Converts a ZonedDateTime to a PlainDateTime by removing the time zone and offset information, leaving only the wall-clock date and time [3][4]. toZonedDateTime(timeZone): This is a method on PlainDateTime (and other types) that creates a ZonedDateTime from a local wall-clock time by applying the specified time zone [5]. Time Zone and Offset Identifiers Time zones in Temporal are typically IANA time zone identifiers (e.g., 'America/New_York', 'Asia/Kolkata') [6]. Offset-based time zones are also supported when an IANA ID is not strictly required or when dealing with fixed-offset scenarios. Valid formats include '+HH:mm', '-HH:mm', '+HHmm', or '-HH' (e.g., '+05:30', '-08:00', '+0600') [3][6]. The 'Z' offset (UTC) is supported but behaves differently than '+00:00'; 'Z' indicates the UTC time is known but the offset is unknown, whereas '+00:00' is treated as a specific local offset [3]. You may optionally add a critical flag to an identifier, such as '[!America/New_York]', to signal that the system must support this specific zone or throw an error [3]. Disambiguation When converting from a wall-clock time (PlainDateTime) to a ZonedDateTime, the time might be ambiguous or non-existent due to daylight saving time transitions. Temporal allows you to configure this using the disambiguation option (e.g., 'compatible', 'earlier', 'later', 'reject') [3][5]. Example Usage // Converting ZonedDateTime to PlainDateTime const zdt = Temporal.ZonedDateTime.from('2026-08-08T12:00:00+00:00[UTC]'); const pdt = zdt.toPlainDateTime; // '2026-08-08T12:00:00' // Converting PlainDateTime to ZonedDateTime const dt = Temporal.PlainDateTime.from('2026-08-08T12:00:00'); const zdtFromDt = dt.toZonedDateTime('America/Los_Angeles'); // '2026-08-08T12:00:00-07:00[America/Los_Angeles]'

Citations:


Normalize short timezone offsets with minutes.

Match.offset accepts forms like +5:30, but parseZone only expands hour-only offsets. Unnormalized offsets such as +5:30 can be passed to Temporal and rejected, so the parser keeps the prior timezone instead of applying the requested offset. Normalize hour and minute offsets before toZonedDateTime.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tempo/src/engine/engine.lexer.ts` around lines 304 - 309, Update the
timezone normalization logic in parseZone to also normalize signed
hour-and-minute offsets such as +5:30, padding the hour and preserving the
minutes in the canonical ±HH:MM form before toZonedDateTime. Keep existing
hour-only offset normalization unchanged and ensure normalized offsets are
applied instead of retaining the prior timezone.

Comment on lines +318 to +324
const resolvedZone = enums.TIMEZONE[zone.toLowerCase()] ?? zone;
if (config) config.timeZone = resolvedZone;
try {
dateTime = zdt.toPlainDateTime().toZonedDateTime(resolvedZone);
} catch {
logWarn(`Unrecognized or invalid timezone identifier: '${zone}'`, config);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect the referenced lexer implementation and surrounding timezone handling.
git ls-files | rg 'packages/tempo/src/engine/engine\.lexer\.ts$' || true

wc -l packages/tempo/src/engine/engine.lexer.ts
sed -n '280,340p' packages/tempo/src/engine/engine.lexer.ts

# Search for config.timeZone consumers/usages to assess later composition behavior.
rg -n "config\.timeZone|timeZone" packages/tempo/src packages -g '*.ts' | head -n 120

Repository: magmacomputing/magma

Length of output: 15996


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Behavioral probe: use Luxon semantics if available to verify that invalid timezone
# conversion throws and leaves the pre-call value unchanged.
node - <<'JS'
try {
  const { DateTime, ZonedDateTime, Settings } = require('luxon');
  console.log('luxon version', require('luxon/package.json').version);
  const now = DateTime.local();
  const zdt = now.setZone('local');
  let caughtTime;
  try {
    const result = zdt.toPlainDateTime().toZonedDateTime('Invalid/Zone');
    caughtTime = result;
  } catch (e) {
    caughtTime = e && e.code !== undefined ? `${e.code}: ${e.message}` : String(e);
  }
  console.log(JSON.stringify({
    lexicalZone: 'Invalid/Zone',
    thrownByToZonedDateTime: caughtTime,
    zdtAfterTryCatch: zdt.zone.zoneName
  }), 'success');
} catch (e) {
  console.log('luxon dependency unavailable in sandbox:', e && (e.code || e.message));
}
JS

Repository: magmacomputing/magma

Length of output: 216


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the composition path that consumes config.timeZone and the Temporal call sites.
sed -n '1,120p' packages/tempo/src/engine/engine.composer.ts
sed -n '1128,1148p' packages/tempo/src/tempo.class.ts
sed -n '1660,1760p' packages/tempo/src/tempo.class.ts
sed -n '888,906p' packages/tempo/src/tempo.class.ts
sed -n '32,50p' packages/tempo/src/engine/engine.term.ts

Repository: magmacomputing/magma

Length of output: 11542


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Static verifier: check parseZone assigns config.timeZone before the toZonedDateTime call,
# and that the catch block only calls logWarn without reverting config.timeZone.
python3 - <<'PY'
from pathlib import Path
p = Path('packages/tempo/src/engine/engine.lexer.ts')
text = p.read_text().splitlines()
try:
    i = next(idx for idx, line in enumerate(text) if 'const resolvedZone = enums.TIMEZONE' in line)
except StopIteration:
    raise SystemExit('parseZone assignment not found')

try:
    catch_idx = next(idx for idx in range(i+1, min(i+15, len(text))) if '{' in text[idx] and idx > i)
except StopIteration:
    raise SystemExit('try block not found')

print('resolvedZone assignment line:', i+1)
print(text[i].strip())
print('try/zdt line:', catch_idx+1)
print(text[catch_idx].strip())
print('catch assignment lines:', [(n+1, text[n].strip()) for n in range(catch_idx+1, catch_idx+5) if 'config.timeZone' in text[n]])
PY

Repository: magmacomputing/magma

Length of output: 308


Update config.timeZone only after toZonedDateTime succeeds.

parseZone stores resolvedZone before calling toZonedDateTime, so an invalid timezone leaves config.timeZone set to the bad identifier after the catch. Later Temporal calls that reuse this.#local.config.timeZone can then throw. Move config.timeZone = resolvedZone into the successful conversion path.

Proposed fix
-		if (config) config.timeZone = resolvedZone;
 		try {
-			dateTime = zdt.toPlainDateTime().toZonedDateTime(resolvedZone);
+			dateTime = zdt.toPlainDateTime().toZonedDateTime(resolvedZone);
+			if (config) config.timeZone = resolvedZone;
 		} catch {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const resolvedZone = enums.TIMEZONE[zone.toLowerCase()] ?? zone;
if (config) config.timeZone = resolvedZone;
try {
dateTime = zdt.toPlainDateTime().toZonedDateTime(resolvedZone);
} catch {
logWarn(`Unrecognized or invalid timezone identifier: '${zone}'`, config);
}
const resolvedZone = enums.TIMEZONE[zone.toLowerCase()] ?? zone;
try {
dateTime = zdt.toPlainDateTime().toZonedDateTime(resolvedZone);
if (config) config.timeZone = resolvedZone;
} catch {
logWarn(`Unrecognized or invalid timezone identifier: '${zone}'`, config);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tempo/src/engine/engine.lexer.ts` around lines 318 - 324, In
parseZone, move the config.timeZone assignment from before the conversion into
the successful toZonedDateTime path, after it completes without throwing. Keep
invalid timezone handling in the catch without mutating config.timeZone.

Comment on lines +17 to +19
const rruleStr = isString(rrule) ? rrule : rrule.rrule;
const nextMs = getNextRRuleEpoch(rruleStr, this.epoch.ms);
return new Tempo(nextMs, this.config);

Copy link
Copy Markdown
Contributor

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 10 '\b(getNextRRuleEpoch|expandRRuleEpochs)\b' packages/library packages/tempo
rg -n -C 8 '\btimeStamp\b|new Tempo\(' packages/tempo/src

Repository: magmacomputing/magma

Length of output: 43835


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- tempo.class constructor signature ---\n'
ast-grep outline packages/tempo/src/tempo.class.ts --match Tempo --view expanded || true

printf '\n--- relevant constructor area ---\n'
sed -n '89,180p' packages/tempo/src/tempo.class.ts | cat -n

printf '\n--- epoch getters / resolve area ---\n'
sed -n '1520,1660p' packages/tempo/src/tempo.class.ts | cat -n

printf '\n--- parse numeric value handling ---\n'
sed -n '300,385p' packages/tempo/src/module/module.parse.ts | cat -n

printf '\n--- compose argument area ---\n'
sed -n '90,140p' packages/tempo/src/engine/engine.composer.ts | cat -n

printf '\n--- timestamp parser area ---\n'
sed -n '220,320p' packages/tempo/src/engine/engine.parser.ts | cat -n

printf '\n--- search explicit numeric timestamp parser ---\n'
rg -n -C 5 "epochMilliseconds|epochSeconds|fromEpoch|toZonedDateTime|Temporal|value type|Number|timestamp" packages/tempo/src/engine/engine.parser.ts packages/tempo/src/engine/engine.composer.ts

Repository: magmacomputing/magma

Length of output: 249


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- tempo.class constructor signature ---'
ast-grep outline packages/tempo/src/tempo.class.ts --match Tempo --view expanded || true

echo
echo '--- relevant constructor area ---'
sed -n '89,180p' packages/tempo/src/tempo.class.ts | cat -n

echo
echo '--- epoch getters / resolve area ---'
sed -n '1520,1660p' packages/tempo/src/tempo.class.ts | cat -n

echo
echo '--- parse numeric value handling ---'
sed -n '300,385p' packages/tempo/src/module/module.parse.ts | cat -n

echo
echo '--- compose argument area ---'
sed -n '90,140p' packages/tempo/src/engine/engine.composer.ts | cat -n

echo
echo '--- parser timestamp handling ---'
sed -n '220,320p' packages/tempo/src/engine/engine.parser.ts | cat -n

echo
echo '--- search parser numeric timestamps ---'
rg -n -C 5 "epochMilliseconds|epochSeconds|fromEpoch|toZonedDateTime|Temporal|value type|Number|timestamp" packages/tempo/src/engine/engine.parser.ts packages/tempo/src/engine/engine.composer.ts

Repository: magmacomputing/magma

Length of output: 32430


🏁 Script executed:

#!/bin/bash
set -eu o pipefail

echo '--- constructor implementation ---'
sed -n '1309,1735p' packages/tempo/src/tempo.class.ts | cat -n

echo
echo '--- compose implementation ---'
sed -n '70,180p' packages/tempo/src/engine/engine.composer.ts | cat -n

echo
echo '--- unit lookup ---'
rg -n -C 4 "UNIT_LOOKUP|scale|timeStamp" packages/tempo/src/engine packages/tempo/src/module packages/tempo/src/tempo.class.ts

echo
echo '--- behavioral probe for numeric epoch vs timeStamp ---'
node - <<'JS'
const anchor = BigInt(Date.UTC(2026, 7, 7, 0, 0, 0, 0));
const nextMs = anchor + 86_400_000n;

console.log('anchor ms:', Number(anchor));
console.log('next ms:', Number(nextMs));
console.log('tempo 1000 in ms:', Number(Temporal.Instant.fromEpochMilliseconds(anchor + 1000n).epochMilliseconds));
console.log('tempo 1000 in ss:', Temporal.Instant.fromEpochNanoseconds(anchor / 1000n * 1000n).epochMilliseconds);
JS

Repository: magmacomputing/magma

Length of output: 37390


🏁 Script executed:

#!/bin/bash
set -euo pipefail

node --experimental-temporal - <<'JS'
const anchor = BigInt(Date.UTC(2026, 7, 7, 0, 0, 0, 0));
const nextMs = anchor + 86_400_000n;

console.log('anchor ms:', Number(anchor));
console.log('next ms:', Number(nextMs));

const { scale } = { ss: 1_000_000_000n, ms: 1_000_000n, us: 1_000n, ns: 1n }?.ms;
console.log('tempo(1000) in ms:', Number(Temporal.Instant.fromEpochNanoseconds(1000n * scale).epochMilliseconds));
console.log('tempo(next) in ms:', Number(Temporal.Instant.fromEpochNanoseconds(nextMs * scale).epochMilliseconds));
console.log('tempo(next) in ss:', Number(Temporal.Instant.fromEpochNanoseconds(nextMs * 1_000_000_000n).epochMilliseconds));
JS

Repository: magmacomputing/magma

Length of output: 200


Preserve the receiver class and millisecond epoch.

getNextRRuleEpoch returns a millisecond epoch, but numeric Tempo construction applies this.config.timeStamp; with timeStamp: 'ss', the returned instant will be shifted by the seconds-to-nanoseconds scale. Use this.constructor and an explicit millisecond instant so the recurrence result is not reinterpreted and decorated subclasses are preserved.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tempo/src/plugin/extend/extend.recurrence.ts` around lines 17 - 19,
Update the recurrence result construction after getNextRRuleEpoch in the extend
method to instantiate via this.constructor, preserving decorated subclasses, and
explicitly mark the nextMs value as a millisecond instant so
this.config.timeStamp cannot reinterpret it. Keep the existing recurrence
calculation and configuration propagation unchanged.

Comment on lines +5 to +15
test('Tempo.prototype.nextOccurrence returns next date matching RRULE string', () => {
const start = new Tempo('2026-08-07T00:00:00.000Z');
const next = start.nextOccurrence('FREQ=DAILY;INTERVAL=1');
expect(next.format('{yyyy}-{mm}-{dd}')).toBe('2026-08-08');
});

test('Tempo.prototype.nextOccurrence accepts object with rrule property', () => {
const start = new Tempo('2026-08-07T00:00:00.000Z');
const next = start.nextOccurrence({ rrule: 'FREQ=DAILY;INTERVAL=2' });
expect(next.format('{yyyy}-{mm}-{dd}')).toBe('2026-08-09');
});

Copy link
Copy Markdown
Contributor

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

These assertions depend on the runner timezone.

getNextRRuleEpoch computes in UTC, but format('{yyyy}-{mm}-{dd}') renders in the timezone of the Tempo instance. The anchor is midnight UTC. In any negative-offset timezone the formatted date is the previous calendar day, so '2026-08-08' becomes '2026-08-07' and the test fails.

Pin the timezone for these instances, or assert on next.epoch.ms against an explicit Date.UTC value.

💚 Proposed fix using an explicit UTC assertion
 	test('Tempo.prototype.nextOccurrence returns next date matching RRULE string', () => {
 		const start = new Tempo('2026-08-07T00:00:00.000Z');
 		const next = start.nextOccurrence('FREQ=DAILY;INTERVAL=1');
-		expect(next.format('{yyyy}-{mm}-{dd}')).toBe('2026-08-08');
+		expect(next.epoch.ms).toBe(Date.UTC(2026, 7, 8, 0, 0, 0, 0));
 	});
 
 	test('Tempo.prototype.nextOccurrence accepts object with rrule property', () => {
 		const start = new Tempo('2026-08-07T00:00:00.000Z');
 		const next = start.nextOccurrence({ rrule: 'FREQ=DAILY;INTERVAL=2' });
-		expect(next.format('{yyyy}-{mm}-{dd}')).toBe('2026-08-09');
+		expect(next.epoch.ms).toBe(Date.UTC(2026, 7, 9, 0, 0, 0, 0));
 	});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test('Tempo.prototype.nextOccurrence returns next date matching RRULE string', () => {
const start = new Tempo('2026-08-07T00:00:00.000Z');
const next = start.nextOccurrence('FREQ=DAILY;INTERVAL=1');
expect(next.format('{yyyy}-{mm}-{dd}')).toBe('2026-08-08');
});
test('Tempo.prototype.nextOccurrence accepts object with rrule property', () => {
const start = new Tempo('2026-08-07T00:00:00.000Z');
const next = start.nextOccurrence({ rrule: 'FREQ=DAILY;INTERVAL=2' });
expect(next.format('{yyyy}-{mm}-{dd}')).toBe('2026-08-09');
});
test('Tempo.prototype.nextOccurrence returns next date matching RRULE string', () => {
const start = new Tempo('2026-08-07T00:00:00.000Z');
const next = start.nextOccurrence('FREQ=DAILY;INTERVAL=1');
expect(next.epoch.ms).toBe(Date.UTC(2026, 7, 8, 0, 0, 0, 0));
});
test('Tempo.prototype.nextOccurrence accepts object with rrule property', () => {
const start = new Tempo('2026-08-07T00:00:00.000Z');
const next = start.nextOccurrence({ rrule: 'FREQ=DAILY;INTERVAL=2' });
expect(next.epoch.ms).toBe(Date.UTC(2026, 7, 9, 0, 0, 0, 0));
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tempo/test/plugins/extend.recurrence.test.ts` around lines 5 - 15,
Update the nextOccurrence tests to avoid runner-timezone dependence by asserting
next.epoch.ms against explicit Date.UTC values, or by constructing the Tempo
instances with a pinned UTC timezone. Apply this to both RRULE string and
rrule-object cases while preserving their expected daily intervals.

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