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
21 changes: 21 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,27 @@ Standard restart: `pkill -f "uvicorn dashboard.app" && sleep 1 && doppler run --

Verify it's up: `curl -s http://localhost:8000/api/health` or check `lsof -i :8000`.

## Locked Hygiene Safety Valves

The "locked project hygiene" workflow (umbrella card #6118) requires every edit to land on a feature branch with a PR. Two valves prevent that rule from blocking legitimate work:

**1. `.scratch/` directory.** Every project gets a gitignored `.scratch/` at its repo root (added by the Phase E rollout). Phase B's branch-on-first-edit hook lets edits in any `.scratch/` subdir through unconditionally. Use it for "I just want to read code and poke at it" — local notes, throwaway test scripts, scratch files that should never reach a PR. Files there are invisible to git and exempt from hygiene checks. If something in `.scratch/` turns into real work, move it out before committing.

**2. `pt migration` recording mode.** For bulk operations that touch many paths and need an audit trail (mass renames, doc reorgs, scripted refactors). Wraps the operation in a recorded session:

```bash
pt migration start <name> # captures git HEAD + porcelain baseline
# ... do bulk edits, run scripts, whatever ...
pt migration finish <name> # writes MIGRATIONS.md section
pt migration finish <name> --commit # same, marks as committed
pt migration finish <name> --revert # restores tracked via `git restore`,
# trashes untracked via send2trash
```

State lives in `~/.project-tracker/migrations/<name>.json` (outside the repo, survives `--revert`). The manifest is appended to `MIGRATIONS.md` at the repo root — never overwritten. `--revert` NEVER uses raw `rm`; tracked paths go through `git restore`, untracked paths go through `send2trash`.

This addresses the SIL post-mortem failure mode where 14 untracked markdown files were left from a Documents/ migration with no record of intent or origin.

## Database Safety — CRITICAL

On 2026-01-27, an agent dropped the tasks table without backup, destroying 94 tasks. The rule below exists because of that incident. Do not skip the gate.
Expand Down
21 changes: 21 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,27 @@ Standard restart: `pkill -f "uvicorn dashboard.app" && sleep 1 && doppler run --

Verify it's up: `curl -s http://localhost:8000/api/health` or check `lsof -i :8000`.

## Locked Hygiene Safety Valves

The "locked project hygiene" workflow (umbrella card #6118) requires every edit to land on a feature branch with a PR. Two valves prevent that rule from blocking legitimate work:

**1. `.scratch/` directory.** Every project gets a gitignored `.scratch/` at its repo root (added by the Phase E rollout). Phase B's branch-on-first-edit hook lets edits in any `.scratch/` subdir through unconditionally. Use it for "I just want to read code and poke at it" — local notes, throwaway test scripts, scratch files that should never reach a PR. Files there are invisible to git and exempt from hygiene checks. If something in `.scratch/` turns into real work, move it out before committing.

**2. `pt migration` recording mode.** For bulk operations that touch many paths and need an audit trail (mass renames, doc reorgs, scripted refactors). Wraps the operation in a recorded session:

```bash
pt migration start <name> # captures git HEAD + porcelain baseline
# ... do bulk edits, run scripts, whatever ...
pt migration finish <name> # writes MIGRATIONS.md section
pt migration finish <name> --commit # same, marks as committed
pt migration finish <name> --revert # restores tracked via `git restore`,
# trashes untracked via send2trash
```

State lives in `~/.project-tracker/migrations/<name>.json` (outside the repo, survives `--revert`). The manifest is appended to `MIGRATIONS.md` at the repo root — never overwritten. `--revert` NEVER uses raw `rm`; tracked paths go through `git restore`, untracked paths go through `send2trash`.

This addresses the SIL post-mortem failure mode where 14 untracked markdown files were left from a Documents/ migration with no record of intent or origin.

## Database Safety — CRITICAL

On 2026-01-27, an agent dropped the tasks table without backup, destroying 94 tasks. The rule below exists because of that incident. Do not skip the gate.
Expand Down
3 changes: 3 additions & 0 deletions USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ You can configure the behavior of Project Tracker using these environment variab
| `PT_BACKUP_RCLONE_DEST` | Optional rclone destination for one off-machine full backup per day. | unset |
| `PT_MEMORY_DB_PATH` | Optional override for read-only `pt memory` JSON commands. | `$PROJECTS_ROOT/ai-memory/brain.db` |
| `PT_SKIP_DOPPLER` | Set to `1` for read-only SSH/cron commands that should not invoke Doppler. | `0` |
| `PT_MIGRATION_DIR` | Override for `pt migration` state files (testing/isolation). | `~/.project-tracker/migrations/` |

**Locked-hygiene safety valves:** edits in any `.scratch/` directory are exempt from the branch-on-first-edit hook. Use `pt migration start <name>` / `pt migration finish <name> [--commit | --revert]` to record bulk-operation sessions with an appended manifest at `MIGRATIONS.md` — see project `CLAUDE.md` for details.

---

Expand Down
52 changes: 52 additions & 0 deletions scripts/db/migrations/011_add_migrations_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Create migrations table for bulk-operation recording sessions (Phase F).

`pt migration start <name>` opens a recording session that captures a
baseline (git HEAD + porcelain status) so that `pt migration finish` can
diff the current tree against the baseline and emit a manifest. The DB
row tracks the lifecycle (started_at, finished_at, status).

Classification: LOCAL_ONLY (by design — see DECISIONS.md)
---------------------------------------------------------------------------
A migration session is anchored to a specific repo's working tree. The
state file at ~/.project-tracker/migrations/<name>.json contains paths
that are only meaningful on the machine that captured them. Replicating
across machines would be a tease, not a resume point. Keep the table
LOCAL_ONLY for the same reason handoffs are.
"""

from __future__ import annotations

import sqlite3

CRR_TABLES: frozenset[str] = frozenset()


def up(conn: sqlite3.Connection) -> None:
conn.execute("""
CREATE TABLE IF NOT EXISTS migrations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
project_id TEXT,
started_at TEXT NOT NULL,
finished_at TEXT,
status TEXT NOT NULL DEFAULT 'recording'
CHECK(status IN ('recording', 'finished',
'committed', 'reverted')),
baseline_head TEXT,
manifest_path TEXT,
created_by TEXT
)
""")
conn.execute("""
CREATE INDEX IF NOT EXISTS idx_migrations_name
ON migrations (name)
""")
conn.execute("""
CREATE INDEX IF NOT EXISTS idx_migrations_status
ON migrations (status)
WHERE status = 'recording'
""")
conn.execute("""
CREATE INDEX IF NOT EXISTS idx_migrations_project
ON migrations (project_id)
""")
Loading