Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .agent/workflows/interactive-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ To use a NodeJS interactive session to test your Tempo library, you can use the

// turbo
```bash
npx tsx -i --import ./test/repl.ts
npx tsx --conditions=development -i --harmony-temporal --import ./bin/repl.ts
```


### Purpose
This command starts a Node.js REPL (Read-Eval-Print Loop) while pre-loading the `Tempo` class and the `Temporal` polyfill into the global scope. This allows you to try different invocations of `Tempo` directly without writing a script.
This command starts a Node.js REPL (Read-Eval-Print Loop) while pre-loading the `Tempo` class and the `Temporal` support into the global scope. This allows you to try different invocations of `Tempo` directly without writing a script.

### Usage Examples
Once the REPL has started, you can run commands like:
Expand All @@ -32,4 +32,4 @@ t1.add({ days: 5 }).format('plain');
### Why this works
- `npx tsx`: Uses the `tsx` runner to handle TypeScript files on the fly.
- `-i`: Explicitly requests an interactive session.
- `--import ./test/repl.ts`: Loads the helper script before starting the REPL, which attaches `Tempo` to `globalThis`.
- `--import ./bin/repl.ts`: Loads the helper script before starting the REPL, which attaches `Tempo` to `globalThis`.
35 changes: 0 additions & 35 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,39 +34,4 @@ jobs:
run: npm test
working-directory: packages/tempo

test-parse-prefilter:
name: Test with parse.preFilter enabled
runs-on: ubuntu-latest
timeout-minutes: 30
if: (github.event_name == 'push' || github.event_name == 'pull_request') && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/release/D' || github.event.pull_request.base.ref == 'main' || github.event.pull_request.base.ref == 'release/D')
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- name: Install monorepo dependencies
run: npm ci
working-directory: ${{ github.workspace }}
- name: Run all tests with parse.preFilter
run: npm test
working-directory: packages/tempo
env:
TEMPO_PREFILTER_CI: 'true'
- name: Run end-to-end benchmark
run: npx tsx --conditions=development bench/bench.parse.prefilter.e2e.ts > bench-output.json 2> bench-error.log
working-directory: packages/tempo
- name: Upload benchmark output
if: always()
uses: actions/upload-artifact@v4
with:
name: bench-parse-prefilter-e2e
path: |
packages/tempo/bench-output.json
packages/tempo/bench-error.log
- name: Validate benchmark output
run: |
node -e "const r=require('./packages/tempo/bench-output.json');if(!r.success){console.error('Benchmark failed:',r.errors);process.exit(1)}else{console.log('Benchmark passed.')}"
working-directory: ${{ github.workspace }}

147 changes: 147 additions & 0 deletions doc/main_branch_protection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# Preventing Writes to the Main Branch Locally

To prevent accidental writes (commits and pushes) to the `main` branch on your local workstation, you can use **Git Hooks**.


## Example: Local Hooks for Main Branch Protection
Set up local hooks to:
1. **`pre-commit`**: Prevent direct commits to the `main` branch.
2. **`pre-push`**: Prevent pushing changes to the remote `main` branch.

### How to Override
If you genuinely need to write to `main` (e.g., for an urgent fix), you have two options:
- **Environment Variable**: Prepend the command with the override flag:
- `ALLOW_MAIN_COMMIT=true git commit -m "Urgent fix"`
- `ALLOW_MAIN_PUSH=true git push`
- **Skip Hooks**: Use the standard Git flag:
- `git commit --no-verify`
- `git push --no-verify`

---

## Applying This Globally (Recommended)
If you want this protection to apply to **every repository** on your workstation, you can configure a global hooks directory.

### 1. Create a Global Hooks Directory
Choose a location (e.g., `~/.git-hooks`) and move the hook scripts there:

```bash
mkdir -p ~/.git-hooks
# Copy the hooks from your repository (replace /path/to/repo with your repo root or use the command below)
# Example using git to find the repo root:
cp $(git rev-parse --show-toplevel)/.git/hooks/pre-commit ~/.git-hooks/
cp $(git rev-parse --show-toplevel)/.git/hooks/pre-push ~/.git-hooks/
chmod +x ~/.git-hooks/*
```


### 2. Configure Git Globally (with Important Warning)
Run this command to tell Git to use your new global hooks directory:

```bash
git config --global core.hooksPath ~/.git-hooks
```

**⚠️ Warning:** Setting `core.hooksPath` with the `--global` flag disables all per-repository `.git/hooks/*` scripts. This will break tools that rely on per-project hooks, such as Husky, lefthook, lint-staged, and others. Any hooks defined in individual repositories will be ignored as long as the global `core.hooksPath` is set.

#### Recommended Alternatives

- **Chain per-repo hooks from your global hook scripts:**
- Manually update your global hook scripts (in `~/.git-hooks/`) to call any existing hooks in each repository’s `.git/hooks/` directory, so you don’t lose project-specific logic.
- **Scope the setting to individual repositories:**
- Instead of using `--global`, set the hooks path only for the current repository:
```bash
git config core.hooksPath ~/.git-hooks
```
- This way, only the current repo is affected, and other repos keep their own `.git/hooks/*` scripts.

For more details, see the [Git documentation on `core.hooksPath`](https://git-scm.com/docs/git-config#Documentation/git-config.txt-corehookspath).

---

## Hook Implementation Details

### pre-commit
This script checks the current branch before every commit.

```bash
#!/bin/bash
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
if [ "$CURRENT_BRANCH" = "main" ]; then
if [ "$ALLOW_MAIN_COMMIT" != "true" ]; then
echo "❌ ERROR: Direct commit to 'main' branch is prohibited."
exit 1
fi
fi
```

### pre-push
This script checks the remote branch being pushed to.

```bash
#!/bin/bash
while read local_ref local_sha remote_ref remote_sha
do
if [ "$remote_ref" = "refs/heads/main" ]; then
if [ "$ALLOW_MAIN_PUSH" != "true" ]; then
echo "❌ ERROR: Pushing to 'main' branch is prohibited."
exit 1
fi
fi
done
```

---

## 🆘 I'm on 'main' and have changes, what do I do?

If you've already made changes on `main` and the hook blocks your commit, **don't panic and don't drop your stash!** You can easily move your work to a new branch.

### The "Magic" Command: Just Create a New Branch
Git allows you to create and switch to a new branch while keeping your uncommitted changes.

```bash
# 1. Create and switch to a new branch
git checkout -b feature/my-cool-feature
# OR (modern syntax)
git switch -c feature/my-cool-feature

# 2. Now you can commit normally
git add .
git commit -m "My feature changes"
```

### If you want to be extra safe (The Stash Method)
If you have a lot of complex changes and want to ensure `main` stays clean:

```bash
# 1. Save your work temporarily
git stash

# 2. Create and switch to the new branch
git checkout -b feature/my-cool-feature

# 3. Bring your changes back
git stash pop

# 4. Commit
git commit -am "My feature changes"
```
Comment thread
magmacomputing marked this conversation as resolved.

### "I accidentally committed before I added the hook!"
If you have local commits on `main` that you haven't pushed yet, you can move them to a new branch:

```bash
# 1. Create a new branch at your current (accidental) commit
git branch feature/my-feature

# 2. Make sure your local reference to 'origin/main' is up-to-date
git fetch origin
# 3. Reset your local 'main' back to where it should be (the remote version)
# (Fetching first ensures you don't accidentally reset to a stale origin/main reference)
git reset --hard origin/main

Comment thread
magmacomputing marked this conversation as resolved.
# 4. Switch to your new branch to continue working
git checkout feature/my-feature
```

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tempo-monorepo",
"version": "2.9.1",
"version": "2.9.2",
"private": true,
"description": "Magma Computing Monorepo",
"repository": {
Expand Down Expand Up @@ -41,4 +41,4 @@
"overrides": {
"esbuild": "^0.25.0"
}
}
}
2 changes: 1 addition & 1 deletion packages/library/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@magmacomputing/library",
"version": "2.9.1",
"version": "2.9.2",
"description": "Shared utility library for Tempo",
"author": "Magma Computing Solutions",
"license": "MIT",
Expand Down
20 changes: 20 additions & 0 deletions packages/tempo/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.9.2] - 2026-05-07

### Added
- **Identity-Based Layout Resolution**: Hardened `resolveLayoutClassificationOrder` to support identity-based symbol lookups. This ensures that tokens without descriptions or aliases (such as raw symbols) can be correctly prioritized in preferred layout ordering.
- **Named Capture for Separators**: Updated the default `{sep}` snippet to use a named capture group `(?<sep>...)`, improving the inspectability of generated regex patterns.

### Changed
- **Modular Decompression**: Removed the redundant `parse.layout.ts` re-export module and consolidated all layout resolution logic into `engine.layout.ts`. Updated internal Specifiers and test-aliases to point to the new canonical home.
- **Node.js Harmony Support**: Updated documentation to highlight native `Temporal` support in Node.js 20+ via the `--harmony-temporal` flag, reducing the need for external polyfills in modern server-side environments.

### Fixed
- **Utility Security Hardening**: Refactored the `create<T>` and `setPatterns` utilities with robust prototype-shadowing guards. These improvements prevent `TypeError` crashes when interacting with null-prototype objects and guarantee `PatternCompiler` state isolation across concurrent Tempo instances.
- **RegExp Preview Accuracy**: Corrected the documentation example for `Tempo.regexp()` to accurately reflect the anchored outer capture group and named snippet expansions produced by the engine.

## [2.9.1] - 2026-05-07

### Fixed
- **Support Utility Consolidation**: Completed the rename and migration of internal support utilities to the `@packages/tempo/src/support/` directory.
- **Pattern Compiler isolated test state**: Fixed state-leakage in `pattern_compiler_optimization.test.ts` by implementing `TempoRuntime.createScoped()` and `init({}, false)` within `beforeEach` hooks.

## [2.9.0] - 2026-05-06

### Added
Expand Down
8 changes: 4 additions & 4 deletions packages/tempo/doc/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,13 @@ For more implementation details, see [Soft Freeze Strategy](./soft_freeze_strate
## ⚡ 3. Master Guard (Guarded-Lazy Strategy)
Used for: `new Tempo(string | number)`

The **Guarded-Lazy** strategy ensures that even with hundreds of custom plugins, the entry point remains nearly instantaneous. In **v2.0.1**, this was refined for 100% matching reliability.
The **Guarded-Lazy** strategy ensures that even with hundreds of custom plugins, the entry point remains nearly instantaneous. As of **v2.9.2**, this logic is decoupled into a dedicated `engine.guard.ts` module.

### How it works:
1. **Longest-Token Matching**: To prevent partial matching (e.g., matching `qtr` inside `quarter`), the guard uses a "Scan-and-Consume" loop that prioritizes the longest available token.
1. **Longest-Token Matching**: To prevent partial matching (e.g., matching `qtr` inside `quarter`), the guard uses a greedy "Scan-and-Consume" loop that prioritizes the longest available token.
2. **Unified Wordlist**: The guard automatically ingests all registered Terms, Timezones, Month names, and Custom Events into a single high-speed lookup Set.
3. **High-Speed Gatekeeper**: By avoiding complex backtracking regexes, the gatekeeper provides predictable $O(1)$ performance even as the plugin list grows.
4. **Versioned Registry (v2.9.0)**: To avoid redundant wordlist rebuilding, the Guard now monitors a `#version` counter on the alias registry. The wordlist is only rebuilt when a mutation actually occurs.
3. **High-Speed Gatekeeper**: By avoiding complex backtracking regexes, the gatekeeper provides predictable $O(1)$ performance regardless of how many plugins are registered.
4. **Versioned Registry**: To avoid redundant wordlist rebuilding, the Guard monitors a version counter on the alias registry. The wordlist is only rebuilt when a mutation actually occurs.
5. **Auto-Lazy**: Valid inputs that pass the guard automatically switch the instance to `mode: 'defer'`, deferring the full $O(N)$ parse work until a property is actually read.

---
Expand Down
16 changes: 14 additions & 2 deletions packages/tempo/doc/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@

`Temporal` is now at Stage 4 and is expected to land broadly in runtimes soon. To avoid needlessly inflating package size with a dependency that will increasingly become unnecessary, `Tempo` does not bundle a `Temporal` polyfill by default.

As of 13 January 2026, Chrome 144 has shipped `Temporal`, and Firefox 139 also includes native `Temporal` support, while Node.js still does not provide built-in `Temporal` globally. Please verify support in your actual target runtime(s) and add a polyfill only when needed.
As of 13 January 2026, Chrome 144 has shipped `Temporal`, and Firefox 139 also includes native `Temporal` support.

While Node.js does not yet enable `Temporal` by default, recent versions (Node 20+) support it via the `--harmony-temporal` flag. This allows you to use `Tempo` without an external polyfill package.

Please verify support in your actual target runtime(s) and add a polyfill only when needed.

You can check at runtime with a simple guard:

Expand Down Expand Up @@ -39,7 +43,15 @@ import { Tempo } from '@magmacomputing/tempo';
const t = new Tempo('next Friday');
```

### Node.js quick-start (if Temporal is not available)
### Node.js (with Native Temporal)

If you are using Node.js 20+, you can enable native `Temporal` support without installing a polyfill:

```bash
node --harmony-temporal my-app.js
```

### Node.js (with Polyfill)

The polyfill import shown here is conditional guidance, not required for all environments.

Expand Down
12 changes: 12 additions & 0 deletions packages/tempo/doc/tempo.layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,18 @@ When crafting raw regex, the following capture groups are used by the engine:
- `per`: Period string offset
- `unt`: Relative unit (e.g., `days`, `weeks`)

## Prototyping with `Tempo.regexp()`

You can use the static `Tempo.regexp()` method to "preview" how a layout string will be compiled by the engine. This is useful for testing custom regex logic before applying it to your configuration.

```typescript
// Expands {dd}, {sep}, {mm}, etc. into a final anchored RegExp
const regex = Tempo.regexp('{dd}{sep}{mm}{sep}{yy}');

console.log(regex.source);
// Output (illustrative): ^((?<dd>...)(?<sep>...)(?<mm>...)(?<sep>...)(?<yy>...))$
```
Comment thread
magmacomputing marked this conversation as resolved.

---

## Professional Services
Expand Down
5 changes: 3 additions & 2 deletions packages/tempo/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -556,11 +556,12 @@ function focusActiveCard() {
}

.tempo-btn-brand {
background-color: var(--vp-c-brand-1);
background-color: #3498db;
color: white;
}
.tempo-btn-brand:hover {
background-color: var(--vp-c-brand-2);
background-color: #2980b9;
color: white;
}
Comment thread
magmacomputing marked this conversation as resolved.

.tempo-btn-alt {
Expand Down
Loading