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
51 changes: 31 additions & 20 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,35 @@
# For append-only JSONL, `union` is that behaviour and is built in.
.trinity/experience/*.jsonl merge=union

# NOW.md: canonical location is docs/NOW.md. The root NOW.md is NOT a symlink
# — `git ls-tree master NOW.md` reports mode 100644, a regular file (a symlink
# would be 120000). It is its own divergent document, last stamped 2026-08-09
# while docs/NOW.md is stamped by every pull request. An earlier version of
# this comment described a symlink and merge-of-the-link-target; no such
# symlink has existed here. Whether the root file should become a real
# symlink, be deleted, or keep its own content is still open — see #2253.
# NOW entries: `merge=union` RETIRED -- there is nothing left to union.
#
# This pattern has no slash, so git matches it by basename at ANY depth: it
# already covers docs/NOW.md (and sub/dir/NOW.md), verifiable with
# `git check-attr merge -- docs/NOW.md`. The rule below is therefore
# redundant, and is kept only because it is the one people look for.
NOW.md merge=union

# docs/NOW.md is the file that actually conflicts: it conflicted on every
# branch (seven times in one campaign), each time resolved identically by hand
# — keep both entries. `theirs` used to be named here, but it is not a
# built-in driver: it needs a merge.theirs.driver entry in .git/config that no
# fresh clone has, so it silently did nothing. `union` is built in, needs no
# setup, and concatenates both sides — exactly that hand resolution.
docs/NOW.md merge=union
# Both rules that used to sit here (`NOW.md merge=union` and
# `docs/NOW.md merge=union`) existed because every PR prepended to one file and
# so collided on its first line. Entries now live one-per-file under `docs/now/`
# (see docs/now/README.md), so two PRs write two different paths and the shared
# line that union was papering over no longer exists.
#
# Retiring it is a fix, not just a cleanup, for three measured reasons:
#
# 1. GitHub never applied it. `git merge-tree` against master with these rules
# in force reports docs/NOW.md CLEAN for PRs that GitHub simultaneously
# labels CONFLICTING -- the mergeability computation ignores merge drivers.
# So union bought nothing on the platform where the conflicts were reported.
# 2. Off the platform it silently corrupted. Union's failure mode is
# DUPLICATION, not removal: two branches editing one `Last updated:` line
# merge with NO conflict into two adjacent `Last updated:` lines under one
# heading. Under the default driver that is a conflict a human resolves.
# The invariant docs/NOW.md should hold is one `Last updated:` line per
# entry heading; exactly one heading violates it -- `Wave Loop 421
# close-out / Wave Loop 422 setup (2026-07-06)` carries no date line, lost
# to exactly this. Stated as an invariant on purpose: an earlier draft of
# this comment pinned absolute counts (137 headings / 136 date lines) and
# they went stale within a single wave.
# 3. It suppressed real conflict detection locally while providing no benefit
# remotely -- the worst of both.
#
# The root NOW.md is NOT a symlink, contrary to what the retired comment here
# claimed: `git ls-tree` shows `100644 blob`, a divergent regular file last
# touched 2026-08-09. Whether it should become a real symlink, be deleted, or
# keep its own content is still open -- see #2253. It simply no longer carries
# a merge rule.
25 changes: 13 additions & 12 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
#!/usr/bin/env bash
# Gate: NOW.md must be updated today before any commit.
# Gate: a fresh docs/now/ entry must exist before any commit.
# Plus: NotebookLM continuous sync integration.
# Pipeline entry: ./scripts/tri check-now → t27c check-now (Rust; see tests/OWNERS.md).
set -euo pipefail

ROOT="$(git rev-parse --show-toplevel)"
cd "$ROOT"

# ===== NOW.md Gate =====
# ===== NOW entry Gate =====
bash "$ROOT/scripts/tri" check-now

# The canonical file is docs/NOW.md — this gate used to match '^NOW.md$'
# only, so every commit that correctly updated docs/NOW.md still printed the
# warning, and the root file (a regular 390-line file, not the symlink the
# .gitattributes comment describes) has not been touched since 2026-08-09.
# Accept either path; the root copy's fate is the owner's call, not a hook's.
if ! git diff --cached --name-only | grep -qE '^(docs/)?NOW\.md$'; then
if git diff --name-only | grep -qE '^(docs/)?NOW\.md$'; then
# Entries are one file per unit of work: docs/now/<YYYY-MM-DD>-<slug>.md .
# They used to be prepended to the single file docs/NOW.md, which made every
# concurrent PR collide on its first line. docs/NOW.md is now a frozen archive,
# so a modification to it is no longer what this warning is looking for.
ENTRY_RE='^docs/now/[0-9]{4}-[0-9]{2}-[0-9]{2}-[A-Za-z0-9._-]+\.md$'
if ! git diff --cached --name-only | grep -qE "$ENTRY_RE"; then
if git status --porcelain --untracked-files=all -- docs/now \
| sed 's/^...//' | grep -qE "$ENTRY_RE"; then
echo ""
echo "⚠️ WARNING: NOW.md is modified but NOT staged."
echo " Run: git add docs/NOW.md"
echo "⚠️ WARNING: a docs/now/ entry exists but is NOT staged."
echo " Run: git add docs/now"
echo " Or: stage and commit it together with your changes."
echo ""
fi
fi

echo "✅ NOW.md gate passed"
echo "✅ NOW entry gate passed"

# ===== NotebookLM Continuous Sync =====
# Track commits for periodic activity.md sync
Expand Down
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

- [ ] PR title follows semantic convention: `feat(scope): description`, `fix(scope): description`, etc.
- [ ] PR body includes **`Closes #N`** reference (see **[Issue Gate](.github/workflows/issue-gate.yml)**)
- [ ] **`docs/NOW.md`** is updated with today's date (**`YYYY-MM-DD`**) if applicable
- [ ] A **`docs/now/<YYYY-MM-DD>-<slug>.md`** entry is added (`./scripts/tri now add "<title>" --bullet "<what changed>" --closes <N>`)
- [ ] Tests added/updated: `./scripts/tri test` passes locally
- [ ] Specs changed → seals refreshed: `./scripts/tri seal specs/path/to/module.t27 --save`

Expand Down
41 changes: 7 additions & 34 deletions .github/workflows/now-sync-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,54 +26,27 @@ jobs:
with:
fetch-depth: 0

- name: Check docs/NOW.md is updated (pull_request)
# Presence AND freshness are both asserted inside the script now. They used
# to be two steps: a diff check here and a `grep -m1 "Last updated:"` parse
# of docs/NOW.md below. With one entry file per PR the date lives in the
# filename, so a single pass over the added paths establishes both -- and
# there is no longer a shared line for two branches to edit.
- name: Require a fresh docs/now/ entry (pull_request)
if: env.IS_BOT != 'true' && github.event_name == 'pull_request'
env:
GITHUB_EVENT_NAME: pull_request
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: bash scripts/ci/now-sync-gate-diff.sh

- name: Check docs/NOW.md is updated (push)
- name: Require a fresh docs/now/ entry (push)
if: env.IS_BOT != 'true' && github.event_name == 'push'
env:
GITHUB_EVENT_NAME: push
PUSH_BEFORE: ${{ github.event.before }}
PUSH_AFTER: ${{ github.sha }}
run: bash scripts/ci/now-sync-gate-diff.sh

- name: Validate NOW.md date is today or recent
if: env.IS_BOT != 'true'
run: |
set -euo pipefail
# Window is [YESTERDAY_UTC .. TOMORROW_UTC]. TOMORROW is included so a
# contributor in an east-of-UTC timezone (e.g. UTC+07) who stamps NOW.md
# with their LOCAL calendar date is not rejected when UTC is still on the
# previous day. A date older than YESTERDAY (stale NOW.md) or newer than
# TOMORROW (typo far in the future) still fails. Closes #1031 follow-up.
TODAY=$(date -u +%Y-%m-%d)
YESTERDAY=$(date -u -d yesterday +%Y-%m-%d)
TOMORROW=$(date -u -d tomorrow +%Y-%m-%d)
LINE=$(grep -m1 "Last updated:" docs/NOW.md || true)
LAST=""
if [ -n "$LINE" ]; then
LAST=$(echo "$LINE" | grep -oE '[0-9]{4}-[0-9]{2}-[0-9]{2}' | head -1 || true)
fi
if [ -z "$LAST" ]; then
echo "::error file=docs/NOW.md::NOW.md is missing a 'Last updated: YYYY-MM-DD' line."
exit 1
fi
# String compares are valid for zero-padded ISO-8601 (YYYY-MM-DD) dates.
if [ "$LAST" \< "$YESTERDAY" ]; then
echo "::error::NOW.md date ($LAST) is too old. Update to $TODAY (UTC)."
exit 1
fi
if [ "$LAST" \> "$TOMORROW" ]; then
echo "::error::NOW.md date ($LAST) is too far in the future (> $TOMORROW UTC). Check for a typo."
exit 1
fi
echo "NOW.md freshness check passed: $LAST (UTC window: $YESTERDAY .. $TOMORROW)"

- name: Check agent sync JSON exists
if: env.IS_BOT != 'true'
run: |
Expand Down
4 changes: 2 additions & 2 deletions bootstrap/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
// - gen-verilog: Generate synthesizable Verilog from .t27
// - gen-c: Generate C code from .t27
// - seal: Compute seal hashes (with --save / --verify)
// - check-now: Gate on docs/NOW.md Last updated date
// - check-now: Gate on a fresh dated docs/now/ entry
// - serve: Start HTTP server (requires 'server' feature)

mod bridge;
Expand Down Expand Up @@ -1273,7 +1273,7 @@ enum Commands {
repo_root: PathBuf,
},

/// Require docs/NOW.md "Last updated" calendar date to match today (local timezone)
/// Require a dated docs/now/ entry within [yesterday .. tomorrow] (UTC)
CheckNow {
#[arg(long, default_value = ".")]
repo_root: PathBuf,
Expand Down
Loading
Loading