When each Claude Code primitive activates over a session. The harness fires hooks deterministically; Claude reads anchors (CLAUDE.md) and applies its conventions/guardrails as situations arise; slash-command skills run only when invoked. MCP servers are available the whole session once configured; subagents get isolated context for delegated work. Routines sit outside the session entirely — scheduled cloud agents that fire on a cron clock with your laptop closed.
An npm-workspaces monorepo. Every top-level folder performs one of six roles. Color-coded to match the primitives in §3 and the lifecycle bars in §1.
CLAUDE.mdroot — conventions, gotchas, workflowREADME.mdproject overview + stackdocs/*.mdARCHITECTURE, DATABASE, LEARNINGS, specsdocs/*.htmlvisual maps (GitHub Pages site)scripts/session-role.shSessionStart — OWNER vs CONSUMER detectionscripts/session-context.sh newSessionStart — injects latest LEARNINGS + memory indexscripts/precompact-context.sh newSessionStart(compact) — re-grounds after compactionscripts/budget-guard.sh newPreToolUse(Bash) — DENIES banned AWS cost commandsscripts/consumer-guard.sh newPreToolUse(Bash) — DENIES duplicate service starts on a CONSUMER sessionscripts/git-guard.sh newPreToolUse(Bash) — DENIES commits/pushes to main (PR-only)scripts/doc-sync-guard.sh newPostToolUse(Edit/Write) — reminds to update matching docsscripts/security-guard.sh newPostToolUse(Edit/Write) — flags PII/encryption/Plaid/AI-write violationsscripts/changelog-guard.sh newPostToolUse(Edit/Write) — flags a stale Operating System changelogscripts/dev-stack.shmanual — become OWNER (docker + API + worker)scripts/check-api-key-security.shmanual — guards leaked API keys / .envscripts/sync-portfolio.sh new/ship 6.6 — docs → portfolio mirror (origin/main only)scripts/sync-changelog.py/ship 6.5 — regenerates this page's changelog from git logscripts/sync-memory.shmanual — snapshots ~/.claude memory into memory/.claude/settings.jsonwires SessionStart + Pre/PostToolUse hooks + permissionsDoc-syncschema/arch/flow change → update .md + .html same PRDATA FLOW = SPECdata-flow-map.html is the primary specLEARNINGS loopauto-append bugs/lessons to LEARNINGS.md$46/mo budgetno EKS / NAT / ALB / ElastiCache / Secrets MgrSecurityAES-256 PII, Bouncer per resource, redactForLoggingBankDataProvidernever call Plaid directlyOWNER / CONSUMERone session owns the stack + migrations.claude/ extensions — commands are user-invoked; skills can be model-invoked; agents are isolated subagents./ux-critiquecommand — principal-designer mobile UI/UX audit/shipcommand — branch → PR → merge → notify (+ changelog, mirror sync)/setup-evalscommand — scaffold / run an eval pipelineupdate-learnings newSKILL — model-invoked on any bug fix → LEARNINGS.mdupdate-docs newSKILL — model-invoked on spec-bearing changes → matching docs, same PR/new-ai-tool newSKILL — scaffolds the 4 Bedrock-tool files in lockstep + ai-pipeline-map/new-model newSKILL — model+migration with encryption / serializeAs / HMAC / Bouncer baked in/doc-drift newSKILL — on-demand all-docs drift sweep → docs: PR (twin of the §5 nightly routine)spec-auditor newAGENT — read-only data-flow=spec drift audit.claude/commands/ (no SKILL.md, user-invoked only) · skills = .claude/skills/ · agents = .claude/agents/apps/mobile/React Native + Expo (iOS dev build)apps/backend/AdonisJS 6 API + BullMQ workerpackages/bank-data-provider/BankDataProvider + PlaidAdapterpackages/shared/types, encryption utils, constantsevals/92 tests, rule checks + AI judge, pre-flight, runnerinfra/Dockerfiles, k3s manifests, Terraform.github/workflows/ci.yaml, evals.yml, deploy.yaml, judge-experiment.yml, drift-supersede.ymldocker-compose.ymllocal Postgres + RedisGitHub Pagesserves docs/ as the visual-docs site (Settings → Pages, main /docs).worktreeincludeparallel-session worktree isolation
The five layers people most commonly confuse — different in who runs them, when, what
they're best for, and crucially whether they get their own context and a
restricted toolset. In Tide, the "policy" layer lives as conventions inside
CLAUDE.md rather than a dedicated policies/ folder. Agents are
isolated helper Claudes under .claude/agents/; Routines are the newest layer
and the only one that runs with no session at all — a scheduled agent firing on a cron
clock in the cloud, laptop closed.
| Dimension | Hooks | Conventions | Skills | Agents | Routines |
|---|---|---|---|---|---|
| Tripwire | Checklist | Playbook | Specialist | Clockwork | |
| What it is | Shell scripts run by the harness at lifecycle events | Markdown rules in CLAUDE.md Claude reads when a trigger fires | Bundled workflows (command prompt + instructions) | A separate Claude defined by a frontmatter file (system prompt + declared tools) | A cloud agent + a cron schedule (a claude.ai routine), with its own repo checkout |
| Who runs it | The harness (deterministic) | Claude (judgment) | Claude — a true skill can self-invoke from its description; a command only when the user types /name |
A fresh Claude instance the main one delegates to — runs independently, then returns | Anthropic's cloud scheduler — fires the agent unattended, no one online |
| When it fires | Lifecycle events (SessionStart, …) | When the matching situation arises | When invoked via slash command | When a bounded task is delegated to it (by Claude or you) | On a cron schedule (e.g. nightly 07:00 UTC) — even with your laptop closed |
| Best for | Non-negotiables; observability; deterministic side effects | Situational judgment | Reusable multi-step workflows | Noisy fan-out work whose result you want without the file-dump; tasks that must be sandboxed (e.g. read-only) | Recurring unattended upkeep — nightly audits, drift sweeps, scheduled reports that open a PR |
| Own context? | No — it's a shell process, not Claude | No — it lives inside Claude's context | No — runs in the main conversation | Yes — isolated window; returns only its result | Yes — a fresh cloud session per run; result lands as a PR/commit, not in your chat |
| Tool access | Whatever the script itself does | Claude's full toolset | Claude's full toolset | Restricted set declared in frontmatter (sandboxed) | Declared per routine — repo checkout + chosen tools; runs off your machine |
| In this repo | scripts/session-role.shscripts/session-context.sh (SessionStart)scripts/precompact-context.sh (SessionStart:compact)scripts/budget-guard.sh (PreToolUse)scripts/consumer-guard.sh (PreToolUse)scripts/git-guard.sh (PreToolUse)scripts/doc-sync-guard.sh (PostToolUse)scripts/security-guard.sh (PostToolUse)scripts/changelog-guard.sh (PostToolUse) |
DATA FLOW = SPEC — judgment only (spec-auditor + nightly sweep)OWNER / CONSUMER — partly hook-backed (consumer-guard.sh)BankDataProvider only — partly hook-backed (security-guard.sh) |
commands: /ship /setup-evals /ux-critiqueskills: update-learnings, update-docs, /doc-drift (model-invoked) |
spec-auditor (read-only: Read, Grep, Glob, Bash)Riu persona — drift auditor for DATA FLOW = SPEC |
Nightly Doc-Drift Sweep (Opus 4.8, 07:00 UTC)→ opens a docs: PR; lives in claude.ai, not the repo |
What the workspace talks to outside its own repo: runtime services, MCP tool servers, and the Claude Code host.
Plaidbank data — via BankDataProvider onlyAWS BedrockClaude Sonnet/Haiku — AI coach + classifiersAWS CognitoJWT auth (1h access / 30d refresh)PostgreSQLlocal docker / RDS-readyRedis + BullMQqueue / background jobsFigma integrateddesign context for UI workGitHub integratedPRs, issues, CI, reviewsGoogle Drive / Box availableGmail / Calendar availableVercel / DocuSign availableClaude CodeOpus 4.8 (1M ctx) — web / remote container.claude/settings.jsonrepo-level hooks + permissions.claude/commands/repo slash-command skillsRoutines liveclaude.ai — scheduled cloud agents (nightly doc-drift sweep)GitHub Pages livepublishes docs/ on merge to mainThe cadences and automations that keep the workspace current and the docs honest — Tide's "self-improvement layer." The throughline of the latest iteration: migrate the highest-risk rules out of the §3 "Conventions judge" column (which depends on Claude remembering) and into the "Hooks fire" column — deterministic tripwires that cannot be silently skipped. The cards below are tagged tripwire (harness-enforced), convention (Claude judgment), CI (GitHub Actions), or routine (scheduled cloud agent).
scripts/session-role.sh probes ports 3333 / 8081 / 5432 / 6379 and prints
whether this session is the dev-stack OWNER or a CONSUMER, so parallel sessions
don't collide on the shared DB or port.
scripts/session-context.sh injects the latest docs/LEARNINGS.md entries
(newest N=8) + the MEMORY.md index into every session's context at start.
A SessionStart hook's stdout is added to the model's context, so a fresh session boots already
aware of recent lessons + project memory — the just-in-time / progressive-disclosure pattern
(surface a digest, not the whole file).
scripts/precompact-context.sh fires on SessionStart with
source=compact — the only hook that can inject context into the freshly
compacted window (PreCompact can't). It re-asserts the compaction-specific delta:
the in-flight branch + uncommitted work, any spec-bearing edits that still owe a doc sync in
this PR, the ./NOTES.md working-memory scratchpad, and a "recall may be degraded
— re-verify paths/flags" checklist. Directly implements Anthropic's guidance to preserve
architectural decisions + unresolved work across compaction.
scripts/doc-sync-guard.sh fires the moment an edit lands on a spec-bearing path
(a migration, a route/controller, services/ai/**, a mobile screen) and injects an
explicit reminder of exactly which .md + .html maps now owe an
update in the same PR. The old "code + docs move together" mandate was convention-only —
now the harness raises it on every relevant edit. data-flow-map.html is the product
spec; code and flow must never drift.
The portfolio site (vikkybharadwaj.github.io/projects/tide/) republishes most of
docs/*.html — and for two weeks nothing synced it, so it kept showing features the
app had removed. scripts/sync-portfolio.sh makes the mirror a build artifact:
manifest-driven from .claude/doc-map.json (portfolio_mirror), it copies
every published page, re-applies the mirror's nav transform, deletes orphans, and warns when the
hand-owned portfolio landing's stat numbers fall behind. /ship step 6.6 runs it in the
same ship cycle as any docs change; /doc-drift runs --check as its 8th
sweep area. Mirror pages are never hand-edited again. Because it publishes whatever the working tree
holds, the apply mode refuses (exit 3) unless the checkout is exactly origin/main with a
clean docs/: a stale local branch can no longer overwrite the live mirror with old docs.
--check stays read-only and runs anywhere.
This page's Changelog (top-right button) bills itself as sourced from the commit
history — but it was a hand-maintained array, so it silently froze at #11 while five more
PRs shipped without a row. Now scripts/sync-changelog.py regenerates it from the file's
real git log (date, ET time, author, PR#), only ever appending so hand-curated
highlight prose survives. /ship records each PR's own row in the same PR (no
one-ship lag), and scripts/changelog-guard.sh is the PostToolUse backstop
that re-flags the list the instant it falls behind committed history. The textbook
convention→tripwire migration, turned on the page that describes it.
The update-learnings skill (.claude/skills/) carries a description that
lets Claude self-invoke it whenever a bug is fixed, a crash resolved, or a non-obvious
stack fact discovered — appending a dated, root-caused entry to docs/LEARNINGS.md in
the same PR. Upgrades the old "remember to append" convention into a model-invoked routine,
reviewed before mobile work to avoid repeating known pitfalls.
scripts/git-guard.sh hard-denies git commit while on
main and any git push that targets main. "Everything ships via
PR" was convention-only and failed three days running — small fixes kept landing straight on main
because the agent judged them "too small for a PR". Now there is no judgment call to get wrong:
/ship is the only path.
scripts/budget-guard.sh hard-denies any Bash command that would create a
budget-busting AWS resource — eks, nat-gateway, elbv2/ALB,
elasticache, secretsmanager — with a reason pointing to
STACK_DECISIONS.md. The $46/mo budget Gotcha was convention-only; now a
careless or prompt-injected agent physically cannot run the forbidden command.
scripts/consumer-guard.sh re-probes :3333 on every Bash call: if a stack
is already up (this session is a CONSUMER), it denies duplicate service starts —
node ace serve / queue:listen / npx expo start /
docker compose up — and asks for confirmation before a shared-DB migration.
session-role.sh only warned; this enforces it, killing the port-conflict /
zombie-process / shared-DB-corruption class for parallel sessions.
scripts/security-guard.sh inspects the file just edited and flags likely breaches of
the fintech non-negotiables: an encrypted column missing serializeAs: null, PII in a
console.* call without redactForLogging(), a direct Plaid SDK call outside
PlaidAdapter, or a DB write from the read-only services/ai/** layer. The
ORM-level encryption + Bouncer discipline was convention-only; now a violation surfaces the moment
it's typed.
The spec-auditor agent (.claude/agents/, read-only) checks that
data-flow-map.html still matches the code — parsing each flow's spec block (windows,
thresholds, formulas, tool shapes) and diffing it against the implementation, then adversarially
confirming before reporting drift. Makes DATA FLOW = SPEC a checkable invariant and
materializes the long-referenced "Riu" reviewer persona as a real project agent.
A claude.ai routine (Opus 4.8) fires nightly at 07:00 UTC (3am ET) on a fresh
cloud checkout — no machine of yours involved. It diffs every tracked doc against the code
— data-flow-map.html, DATABASE.md, ARCHITECTURE.md, the
security / ai-pipeline / cost maps, and the landing-page counts — and, when it finds drift, opens
ONE review-only docs: PR citing each mismatch as doc:line → code:line;
finds nothing, opens nothing. The cloud cousin of the spec-auditor agent — same
DATA FLOW = SPEC discipline, but unattended, on a clock, and across the whole doc set.
Managed at claude.ai/code/routines.
.github/workflows/drift-supersede.yml closes older doc-drift sweep PRs the moment
a new one opens. Each sweep is a fresh audit of main, so an older one is never worth
merging, and left alone they piled up (fifteen open at once, all editing the same lines, so none
could merge). Now exactly one is ever waiting on a person.
.github/workflows/ci.yaml runs lint + typecheck + tests against an ephemeral
Postgres. Reused by deploy.yaml as a pre-deploy gate.
.github/workflows/evals.yml runs the full 92-test suite nightly (06:00 UTC).
A PR touching apps/backend/app/services/ai/** or evals/** always gets the
free structural checks, but the paid suite runs only when the PR carries the
run-evals label. Each answer is scored by two graders (rule checks + an AI judge); a
pre-flight check stops a run whose setup is broken, so it's reported as a harness failure rather
than a 0% score. judge-experiment.yml (manual) measures the AI judge itself against
human grades.
GitHub Pages publishes the docs/ folder, so this page and every visual map go live at
vikkybharadwaj.github.io/tide without a build step.