Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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/*
```
Comment thread
magmacomputing marked this conversation as resolved.


### 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"
```

### "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

# 4. Switch to your new branch to continue working
git checkout feature/my-feature
```

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;
}

.tempo-btn-alt {
Expand Down
14 changes: 4 additions & 10 deletions packages/tempo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@
"**/plugin/extend/extend.*.ts",
"dist/engine/engine.*.js",
"src/engine/engine.*.ts",
"dist/parse/parse.*.js",
"src/parse/parse.*.ts",
"dist/module/module.*.js",
"src/module/module.*.ts",
"dist/discrete/discrete.*.js",
Expand All @@ -54,8 +52,8 @@
"default": "./dist/core.index.js"
},
"#tempo/enums": {
"development": "./src/support/tempo.enum.ts",
"default": "./dist/support/tempo.enum.js"
"development": "./src/support/support.enum.ts",
"default": "./dist/support/support.enum.js"
},
"#tempo/duration": {
"development": "./src/module/module.duration.ts",
Expand Down Expand Up @@ -109,10 +107,6 @@
"development": "./src/discrete/discrete.parse.ts",
"default": "./dist/discrete/discrete.parse.js"
},
"#tempo/parse/*.js": {
"development": "./src/parse/*.ts",
"default": "./dist/parse/*.js"
},
"#tempo/format": {
"development": "./src/discrete/discrete.format.ts",
"default": "./dist/discrete/discrete.format.js"
Expand All @@ -132,8 +126,8 @@
"import": "./dist/tempo.index.js"
},
"./enums": {
"types": "./dist/support/tempo.enum.d.ts",
"import": "./dist/support/tempo.enum.js"
"types": "./dist/support/support.enum.d.ts",
"import": "./dist/support/support.enum.js"
},
"./extend/*": {
"types": "./dist/plugin/extend/extend.*.d.ts",
Expand Down
23 changes: 12 additions & 11 deletions packages/tempo/plan/RELEASE-D.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,21 @@ This release focuses on modularizing and refactoring the parsing and pattern-mat

## Task Breakdown & Tracking


### Pattern Compiler + Cache Extraction
- [ ] Extract `compileRegExp`, `setPatterns`, and helpers to new module
- [ ] Integrate memoization/caching logic as needed
- [ ] Refactor engine and consumers to use new module
- [ ] Ensure compatibility with snippet/layout definitions
- [ ] Add/expand unit tests for pattern logic and cache
- [ ] Update documentation and references
- [x] Extract `compileRegExp`, `setPatterns`, and helpers to new module (PatternCompiler)
- [x] Integrate memoization/caching logic as needed (PatternCompiler cache)
- [x] Refactor engine and consumers to use new PatternCompiler module
- [x] Ensure compatibility with snippet/layout definitions
- [x] Add/expand unit tests for pattern logic and cache
- [x] Update documentation and references

### Alias Resolution Engine Extraction
- [ ] Extract alias resolution logic to new module
- [ ] Define interfaces for registration, lookup, collision
- [ ] Refactor engine and plugins to use new APIs
- [ ] Add/expand unit tests for alias/collision
- [ ] Update documentation and references
- [x] Extract alias resolution logic to new module
- [x] Define interfaces for registration, lookup, collision
- [x] Refactor engine and plugins to use new APIs
- [x] Add/expand unit tests for alias/collision
- [x] Update documentation and references
Comment thread
magmacomputing marked this conversation as resolved.

### Guard Builder Extraction (Assessment)
- [ ] Identify all guard-building/token-ingestion logic
Expand Down
4 changes: 2 additions & 2 deletions packages/tempo/src/discrete/discrete.parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import { defineInterpreterModule } from '../plugin/plugin.util.js';
import type { Range, ResolvedRange } from '../plugin/plugin.type.js';
import { sym, isTempo, TermError, getRuntime, Match } from '../support/support.index.js';
import { markConfig, setPatterns, init, extendState } from '../support/support.index.js';
import { setProperty } from '#tempo/support/tempo.util.js';
import enums from '../support/tempo.enum.js';
import { setProperty } from '#tempo/support/support.util.js';
import enums from '../support/support.enum.js';
import * as t from '../tempo.type.js';
import type { Tempo } from '../tempo.class.js';

Expand Down
11 changes: 4 additions & 7 deletions packages/tempo/src/engine/engine.alias.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,17 +86,14 @@ export class AliasEngine {
this.#config = options.config;
this.#id = AliasEngine.#idCounter++;

if (this.#parent) {
if (!(this.#parent instanceof AliasEngine)) {
const msg = "Parent engine must be an instance of AliasEngine";
this.#logger?.error(this.#config, msg);
throw new TypeError(msg);
}

if (this.#parent instanceof AliasEngine) {
this.#depth = this.#parent.#depth + 1;
this.#state = Object.create(this.#parent.#state); // create a new state object that inherits from the parent engine's state
this.#words = Object.create(this.#parent.#words); // create a new words object that inherits from the parent engine's words for collision detection
} else {
if (this.#parent)
this.#logger?.error(this.#config, "Parent engine must be an instance of AliasEngine");

this.#depth = 0;
this.#state = Object.create(null); // initialize an empty state for the root engine (no parent)
this.#words = Object.create(null); // initialize an empty words object for the root engine (no parent)
Expand Down
Loading
Loading