Naming follows a **(domain, leaf) derivation** model.
Rules
Apps Readme
3When editing an `apps/*/README.md`, MUST have YAML frontmatter with these required fields: `name` (human-readable display name), `description` (1–3 sentences; use `>-` for multiline preservation), `schedule` (freeform timing string e.g.
When authoring an `apps/*/README.md` from scratch, MUST follow this canonical frontmatter scaffold (absorbed from the deleted `.claude/templates/app-readme.md`): ```yaml --- name: App Display Name description: >- 1-3 sentence description of what the app does.
Apps Scripts
1When generating AI text in an apps/ script, MUST use the cross-provider router; MUST NOT call LLM APIs directly.
Behavior Platform Contracts
2Before tuning a config to a rate limit, quota, or API constraint from a third-party doc, MUST run one empirical probe to confirm the actual observed limit; MUST NOT trust documented limits as observed behavior.
When applying a fix that depends on a managed platform honoring a config setting (Windmill CE `concurrent_limit`, Cloudflare KV limits, etc.), MUST empirically verify before shipping (fire the limit, observe enforcement); MUST NOT trust an unverified platform contract.
Behavior Tests
1Before declaring a new test complete, MUST verify it fails on the known-regressed state (revert the fix, run the test, confirm failure, re-apply the fix); MUST NOT rely on "the test passes" as sufficient evidence.
Behavior
9When implementing any new computation or flow, MUST enumerate expected behavior at 0, 1, empty, max, and error inputs.
When the diagnosed design flaw is a **class-level mechanism** (affects more than one file/script: shared pattern across siblings, orchestrator families, repeated anti-pattern), MUST land every instance the Step 1.7 peer-sweep finds in the same session.
A session that produced a new app or a major change to an existing app (new file under `apps/**/` at any nesting depth — including domain-grouped `apps/{domain}/{name}/` — or >50 lines added/modified across `apps/**/*.ts` ∪ `integrations/**/*.ts`) MUST be machine-reviewed by `/implement-audit` on **Codex, a different vendor from the author**, BEFORE the change lands.
When the task is a DECISION (architecture, strategy, vendor, process, >$500 impact), MUST present alternatives + trade-offs + flagged risks and wait for user selection; MUST NOT execute unilaterally.
When a task has an authoritative deterministic source (REST API, web scrape with a stable HTML anchor, computation, lookup table, repo file), MUST use that source instead of an AI call; MUST NOT substitute AI inference for data that can be fetched, computed, or parsed.
Pre-send completion test: before ending ANY turn, every task surfaced this turn MUST be either (a) DONE now, or (b) explicitly blocked on the user with the blocker named.
MUST NOT build hooks, checks, or rules for failure modes that have not yet occurred.
When planning any non-trivial work, MUST address all four before starting implementation: downstream consumers (grep for users), peer consistency (find sibling files doing similar), edge cases (enumerate), doc surface (which docs drift).
Every work product produced in a turn MUST terminate in one of two destinations: (a) printed directly in the assistant response to the user, or (b) a file inside the context repo.
Compose Files
1After editing an `integrations/*/compose*.yml` file, MUST run `integrations/komodo/deploy-stack.sh <service>` in the same session; MUST NOT expect the compose change to apply automatically — Komodo deploys the specified service to the configured target.
Data Reliability
18At request/SSR time (Astro `.astro` frontmatter, API routes, middleware, any per-request handler), MUST NOT `await` a KV / DB / `fetch` read inside a loop over a key list or a collection — that is O(N) sequential round-trips and is the recurring slow-page bug.
Business logic MUST live in TypeScript independent of orchestrator (Hatchet handles scheduling/concurrency/retries; the orchestrator is infrastructure); MUST NOT couple business logic to orchestrator APIs beyond the platform-provided primitives (`task.run`, `step` outputs, `concurrency` config).
When a piece of metadata describes a data product (SLA, data sources, frontmatter), MUST co-locate it with the code that produces the product (script header for writer SLA; `pageInfo` for reader data sources; README frontmatter for app); MUST NOT create standalone inventory files.
[Folded into @rule:co-locate-metadata on 2026-05-13 — `derive-inventories-from-code` lived here from 2026-03-28 to 2026-05-13] [2026-05-13]
`validateOutcome` MUST check correctness (expected fields non-null, arrays non-empty when expected), not just existence; warnings MUST be treated as failures; MUST NOT display zeros or blanks from missing data as real values.
Every automation MUST be idempotent — running twice yields the same result; MUST NOT rely on append-only or increment-only side effects; state is derived from inputs, not accumulated.
[Folded into @rule:no-mrr-clients-duplication on 2026-05-13 — `new-client-data-goes-to-extension` lived here from 2026-03-28 to 2026-05-13] [2026-05-13]
When adding client-level data used by all three WSP portals, MUST add to `mrr:clients` (FINANCE_DATA); when data is portal-specific, MUST add to that portal's extension namespace joined by codename; MUST NOT duplicate `mrr:clients` fields (codename, clientName, type, amount, dates, users, atRisk flags) into portal-specific KV.
For any external data source, MUST consolidate to one fetch per schedule-group with many writes from it; MUST NOT issue multiple independent fetches of the same entity at different times — contradictory snapshots corrupt downstream reads.
Every KV key MUST have exactly one script that owns writes and declares an SLA; MUST NOT introduce a KV key whose owner or SLA is ambiguous.
When a fetch returns garbage (schema mismatch, missing required fields, Zod validation fails), MUST refuse to write and keep the last good value with a staleness warning; MUST NOT overwrite good data with bad.
Every **timer-SLA** data product MUST declare a hard TTL (typically 2-3× the SLA); after TTL, DataHealth MUST show "missing" not "stale"; MUST NOT display last month's financials as merely "stale." [2026-03-28]
CF Pages build command MUST run `astro check` (or `tsc --noEmit`) then `astro build`; MUST NOT deploy on type errors; MUST NOT skip the check.
Every external API access MUST go through a typed client co-located with the fetch script; MUST NOT use raw `fetch()` for external APIs against known integration hosts (`hosts:` frontmatter from `integrations/*/README.md`).
Production code MUST be TypeScript; MAY use shell ONLY for thin wrappers around other tools (deploy scripts, hook glue); MUST NOT implement business logic in shell.
For every external fetch, MUST assert at least one domain invariant before using the data (date range overlaps the request, result count ≥ expected floor, entity types match the query); MUST NOT trust a valid schema as sufficient — valid-but-wrong data is silent corruption.
[Folded into @rule:one-owner-one-sla-per-key on 2026-05-13 — `writer-header-declares-products` lived here from 2026-03-28 to 2026-05-13] [2026-05-13]
Every read and write boundary for external data MUST validate via Zod; MUST NOT accept or emit external data with unvalidated shape; schema drift MUST break the build, not the runtime.
Feedback
21When a task surfaces items only Ashkaan can resolve (unclassifiable data, a taxonomy boundary, a naming call), MUST batch them into one `AskUserQuestion` at the moment they surface; MUST NOT hold them until the end, park them, or wait to be asked "any questions?".
When solving a problem requires modifying shared infrastructure on any host other than the repo working tree — host networking (`ip link/addr/route`, NetworkManager, systemd-networkd), daemon configs (`/etc/docker/daemon.json`, `/etc/ssh/sshd_config`, `/etc/systemd/...`, `/etc/cni/...`), creating or enabling systemd unit files outside `~/.config/systemd/user/`, modifying root-owned credentials (`/root/.docker/config.json`, `/root/.ssh/`, `/etc/op/...`), or anything that persists across reboots on a shared host (Hermes, the WSP portal hosts, MNK servers) — MUST stop and `AskUserQuestion` BEFORE executing, even if the change is "small," "additive," "documented workaround," "reversible," or "the standard fix." Diagnose freely (read-only).
Universal, every reply (not just answers): use fewer words and less jargon.
Each create/edit/write/execute under `.claude/` is a separate user approval prompt (harness-gated, not governed by `settings.json`, not disableable).
When implementing, deploy is part of the work — MUST NOT ask "want me to deploy?", offer it as a follow-up, or list "Deploy X" as a pending item.
Before any design artifact (SPEC, plan, code, Codex consensus, agent dispatch), MUST present two plain-English sections — **Goal:** (one sentence to a few bullets, sized to the work) and **Simplest mechanism:** (the lightest-weight way to achieve it) — then MUST wait for explicit approval.
When the user asks "which would you pick?", "what's best?", "what would you choose if greenfield?", or any other question seeking a single recommendation, MUST reason from greenfield: ignore sunk cost, migration cost, and Claude's own work cost — the user does not bear those.
Before asserting Claude lacks access to any external service, system, or data source ("I can't access X", "the agent couldn't access X"), MUST check three sources in order: (a) `integrations/<service>/README.md` exists — follow its access pattern; (b) `op item list --vault "AI Context" | grep -i <service>` matches — creds are stored; (c) the service appears in a Komodo stack list if containerized — access flows through `integrations/komodo/`.
Ashkaan lives in **West Hills, CA** (~91307), timezone America/Los_Angeles — SSOT `knowledge/people/ashkaan-hassan/README.md § Identity`.
When proposing to "lock in" / "select" / "finalize" / "choose" a runtime, framework, vendor, major library, or major version for repeated use across the repo, MUST first run BOTH (a) a workload-shaped smoke test that exercises the smallest end-to-end production behavior the technology will actually perform — NOT just module load + API-surface init + a connection; AND (b) an external-evidence search covering the vendor's public issue tracker, the official compatibility/test matrix, and an authoritative current-LTS/current-stable lookup against the live registry — NOT training-data recall.
When the user has explicitly authorized an action this turn (verbally, via `AskUserQuestion`, or a "do it" / "yes" / "I approve" directive), MUST execute it end-to-end myself.
When invoking the `Agent` tool, MUST NOT pass any non-default `isolation:` value — no isolation is the only acceptable shape; the subagent shares the main session's filesystem, and a change-bearing isolated worktree orphans a parallel checkout on a synthetic branch the orchestrator must then consume.
Before asserting any fact about current repo state, platform contracts, API limits, versions, counts, timezones, or dates, MUST investigate with a tool (Read, Grep, Glob, WebFetch, curl, gh) in THIS session; MUST NOT assert from training data or plausible inference — training-data recall for factual questions is a violation.
MUST NOT add fmt, lint, or similar auto-quality enforcement that covers `.md` files in hooks, scripts, or CI; markdown is not code.
When executing a `systemctl restart|stop|disable` (or any process-killing action) against a service whose process tree contains the current Claude session's runtime, MUST stop and ask the user — killing it terminates the session issuing the command.
Before invoking a skill (`/project`, `/solve`, `/implement-audit`, etc.), MUST verify the skill is either (a) explicitly requested by the user in the CURRENT message, OR (b) invoked as a declared sub-skill by another skill's step graph already active in the session; MUST NOT trigger skills on keyword matches in user answers or incidental conversation flow — user answers to `AskUserQuestion` are responses, not skill invocations.
When a SPEC review, an `/implement` verify-assumptions discovery, or any other mid-implementation surface introduces ANY new infrastructure dependency or scope addition that was NOT in the Step-0 goal+shape the user said yes to — host-infra changes, new 1P items, new env-var categories, new shared-helper signatures beyond pure additive backwards-compat, `/etc/hosts` edits, supervisor/compose changes, "fall back if X fails" branches on systems the user hasn't seen, or re-using an existing cred for a new purpose — MUST stop and surface it to the user as a plan-revision conversation BEFORE folding it into the SPEC AND BEFORE executing it.
When verifying or QA'ing a change to a CF-Access-gated portal (finance / operations / sales / dashboard), MUST verify against production KV (`cfKvRead` / REST) or screenshot the live gated page with the dashboard-probe service token, per `integrations/cloudflare/README.md § "Verifying a portal feature"`; MUST NOT conclude a change is "unverifiable locally" or "needs deploy first" because the page is auth-gated — a CF-Access gate is a login wall for my browser, never a data wall.
When building or migrating an automation that observes or acts on an external system, MUST research what each target exposes (cross-network API / push-webhook / SSH-only) BEFORE picking a transport, and MUST prefer the cross-network API (HTTP/REST/S3) over SSH whenever one exists AND it does not lengthen the runner→target path; MUST NOT default to SSH without checking.
When QA'ing visual UI output (rendered slides, screenshots, page renders, design candidates, portal pages) before shipping a change to a deployed surface, MUST dispatch a fresh-context subagent for the visual review; MUST NOT inline-QA by reading PNGs in the main orchestrator context.
Default to the simplest mechanism that works.
Google Urls
1For Google URLs (Sheets, Docs, Drive, Calendar, Gmail, Contacts), MUST use the typed clients in `integrations/google/` (`sheetsApi`, `gmailApi`, `calendarApi`, `contactsApi`); MUST NOT use WebFetch on those URLs — they require auth and always fail with 401/403.
Governance Apps
5When an app's SPEC declares `ai_judgment_features:` (non-empty list in YAML frontmatter), MUST include `apps/{name}/evals/eval.ts` + at least one fixture file (`apps/{name}/evals/fixtures/*.json`); MUST NOT ship a new AI-judgment app or feature without an eval.
When adding a major feature to an existing app under `apps/{name}/`, MUST author `apps/{name}/specs/YYYY-MM-DD_feature-slug.md` with the 9-section schema (mirror an existing app's SPEC.md) before any implementation code is written; SPEC persists as long-term documentation.
When creating a new app under `apps/{name}/` whose implementation includes `.ts`/`.tsx` files (automation, Windmill script, edge worker, etc.), MUST include `apps/{name}/SPEC.md` with the 12-section schema (mirror `integrations/trigger-dev/new-app-guide.md § SPEC.md` for the canonical template), covering all 11 base sections: (0) user's ask verbatim, (0a) how we interpreted this, (0b) simplest shape that could work, (1) behavior contract, (2) input contract (trigger, schedule, params, credentials), (3) output contract (KV writes with envelope type + SLA, stdout shape, side effects), (4) boundary cases at 0 / 1 / empty / max / error, (5) one acceptance command with inline expected stdout, (6) test plan pointer, (7) data-reliability checklist, (8) failure modes — plus section 9 (AI Eval Plan) when the SPEC declares `ai_judgment_features:` non-empty in YAML frontmatter (per `@rule:apps-eval-required-for-ai`).
When creating a new app under `apps/{name}/` that adds `.ts`/`.tsx` code (beyond test files themselves), MUST include at least one paired test file exercising the acceptance behavior (`apps/{name}/*.test.ts` or `apps/{name}/tests/*.ts`).
MUST NOT send email directly from arbitrary code.
Governance Integrations
2Writes to the context repo from automation code (apps/, integrations/) MUST go through `integrations/github/git_local.ts` (`commitViaSsh` — SSH to the repo host, commit in a per-automation worktree) — NOT through the GitHub REST API.
When writing or modifying a typed API client for an external integration, MUST place the canonical reference at `integrations/{name}/{name}.ts`; Daedalus-side code (apps, `projects/**/*.ts`) MUST import the reference directly via filesystem relative path.
Governance Projects
4The shard model is **opt-in**, for projects deliberately split into ≥2 independent parts that can be pursued in parallel.
When a project's status is `blocked`, MUST use `blocked-on:` frontmatter field naming what's blocking; MUST NOT use `next:` for blocked projects.
MUST NOT autonomously set `status: completed` — only the user marks completion.
When creating a new project, MUST place at `projects/{domain}/YYYY-MM-DD_name/` with `README.md` containing required frontmatter (project, status, created, tags); MUST push immediately after creation so the index regenerates.
Governance Readme
1When adding or editing a markdown table in a README whose first column lists files or subdirectories of the README's own directory, MUST either (a) auto-generate it via Phase A with a named reader or (b) delete it and replace with narrative; MUST NOT hand-maintain a file-listing table — drift is inevitable.
Governance
6When my own tools (curl, fetch, headless screenshot) cannot access a user-protected resource (CF Access, SSO, VPN, browser auth, IP allowlist), MUST NOT assume the user also cannot access it; MUST NOT escalate to a more-public mechanism (remove the auth gate, publish to a no-auth URL, upload to a public image host, route via a separate unauthenticated CDN) to solve my OWN tool-access problem.
Every git commit subject MUST begin with an actionable verb (add / fix / update / refactor / remove / etc.), ≤100 chars, no prefix-only-no-content shapes; MUST NOT use vague subjects ("wip without scope", "fix it", multi-line streams).
Before writing "shipped", "deployed", "live", "auto-deploys", or any deploy-command instruction in session output (close summary, implement-audit report, plan file, commit message), MUST consult the relevant `integrations/<name>/README.md` for the repo's actual deploy model and verify the claim against it; MUST NOT treat repo-local convenience scripts (`package.json` `deploy`, `Makefile` `deploy`, etc.) as authoritative when the integration README documents a different model.
Git commit messages MUST NOT include `Co-Authored-By: Claude ...
When a lint or format error occurs, MUST fix it at the source; MUST NOT add the file to an exclude list to avoid the fix.
When the user says "close", "wrap up", "let's close", MUST invoke `/close` (journal entry + commit + push).
Health Data
1Health data MUST be stored in year-based files: `knowledge/health/{topic}/{year}.md`; MUST load current year by default; MUST NOT always-load historical years — pull only on-demand for trend analysis.
Integrations
2A folder under `apps/` holds repo-authored code — anything we wrote.
A folder under `integrations/` MUST wrap an external product — third-party SaaS, OSS running on another host, vendor API, or a host-bound product we connect to.
Journal
4Every session block MUST start with `**Action:** <verb>` where `<verb>` is one of the closed set: `completed | progressed | scaffolded | fixed | investigated | planned | shipped | migrated | removed | updated`; MUST NOT omit, replace, or use a verb outside the enum.
When editing a journal entry, frontmatter MUST contain `date` (YYYY-MM-DD) and `tags` (list); MUST contain a `sessions:` block listing one `- slug: <heading>` entry per session worked that day (parsed by `.claude/hooks/telemetry/local_aggregator.ts`); MAY contain additional structured telemetry fields per @rule:journal-session-frontmatter; MUST NOT add unrelated ad-hoc frontmatter fields.
Journal entries MUST be structured (headings, bullets, labeled sections like Changes/Lessons/Blocked/Next); MUST NOT contain narrative prose, code diffs, or deploy play-by-play.
Each session in a journal MUST use a `###` heading matching one of two shapes: `one-off (<brief description>)` OR `<domain>/<YYYY-MM-DD>_<slug>` (optionally followed by ` (<parenthetical>)`, ` — <em-dash-tail>`, `/<sub-slug>`, or `_<sub-slug>`); MUST NOT use `##` or freeform section names.
Meta · Adversarial Tools
7Adversarial tools MUST treat "zero findings" as a permitted output and report it explicitly; MUST NOT pad output with trivia or speculative items to avoid the empty-result case; linter flags outputs containing only `speculative` verdicts as probable padding.
When `/implement-audit` exceeds 2 rounds on the same ship even after user override, MUST ship with remaining findings captured as deferred items in the project README; MUST NOT continue fix-round loops past round 3 — the implement-audit loop is self-generating because each fix creates new audit-able surface, and diminishing returns become negative returns.
Adversarial tools MUST enforce a recursion cap of 2 rounds on the same ship; round 3 MUST invoke `AskUserQuestion` asking `ship | redesign | defer`, and log the choice; MUST NOT proceed to round 3+ without the user decision gate.
Adversarial tools MUST operate only on changes in the current conversation or the named diff range; MUST NOT comment on unrelated repo state or other sessions' work.
Adversarial-tool output MUST include a structured section (YAML fence with `findings: [{id, verdict, rule, message}]`) consumable by the telemetry aggregator, in addition to any human-facing prose; MUST NOT emit prose-only output.
Adversarial-tool output MUST emit every finding with a triage verdict: `fix-now | deferred-batch-N | speculative | out-of-scope`; MUST NOT emit free-form "here's a list of findings" output — untriaged findings produce the fix-every-finding audit loop.
Any adversarial or review tool (`/implement-audit`, `/review`, `/security-review`) MUST emit triaged findings (`fix-now | deferred-batch-N | speculative | out-of-scope`) AND enforce a recursion cap of 2 rounds on the same ship; on round 3 MUST `AskUserQuestion` for `ship | redesign | defer`; MUST NOT loop indefinitely.
Meta · Ai Layer Authoring
8When an agent runs work Ashkaan is not present to supervise, each of its hard limits MUST be enforced by withholding the tool that would breach it — scope the agent's `tools:` list — and MUST NOT rest on an instruction in its brief telling it not to.
When authoring or dispatching a `.claude/` artifact that does LLM work whose work-type MATCHES a row in `apps/ai/prompt-router/policy.ts` (machine-readable mirror `policy.json`), the model MUST be the row's assignment; MUST NOT be chosen at the call site.
When a rule is violated 3+ times despite being in the right file, MUST promote it to enforcement (hook, linter); MUST NOT leave a 3x-violated rule as prose-only.
For "When X then Y" automation placement, MUST pick surface by what X is: a `git commit` (any committer — Claude, scripts, or trigger.dev SSH) → a **native git hook** under `.githooks/` per `@rule:tiebreaker-commit-hook-placement` (thin entrypoint → body in `.claude/hooks/bodies/`); any other Claude tool call → `.claude/hooks/` PreToolUse/PostToolUse with body scripts in `.claude/hooks/bodies/`; clock tick (declarative verification, scheduled checks, anything else) → trigger.dev (a scheduled `schedules.task`).
For "Always think Y when reasoning about Z" guidance placement, MUST pick by scope of Z, DEFAULTING to the lightest deterministic trigger and treating always-loaded as the justified exception (per `@rule:rule-default-deterministic-trigger` + `@rule:no-model-judgment-jit`): mechanizable convention → a hook/linter; convention that only matters editing certain files → `.claude/rules/{topic}.md` with `paths:` frontmatter (path-scoped); task-typed guidance reliably signaled by a word that IS its activation condition → keyword-injected via `.claude/hooks/integration-match.sh`; judgment-core Claude behavior that applies every session AND cannot be checked or path-scoped → `.claude/rules/{topic}.md` with no `paths:` (always-loaded) AND a `<!-- judgment-core: <reason> -->` marker per `check-rule-minimal-context.sh`; persistent fact about a person → `knowledge/people/{name}/`; domain-specific → that domain's README; multi-step named operation → `.claude/skills/{name}/SKILL.md`.
Every automation MUST emit at least one signal channel (post-commit echo, hook stderr, Daedalus notification); MUST NOT ship invisible automation — silent automations cannot be debugged or trusted.
Commit-time automation MUST be authored as a **native git hook** under `.githooks/` (`core.hooksPath=.githooks`) — `pre-commit`, `commit-msg`, or `post-commit` — as a thin entrypoint delegating to the body script under `.claude/hooks/bodies/git-{precommit,commit-msg,postcommit}.sh`.
When splitting work between skill and agent, MUST place in a skill if the work needs session history (orchestration, editing based on session state, user-facing slash commands, commit/push flows); MUST place in an agent if the work benefits from fresh context (adversarial review, cold-reader analysis, isolated investigation, parallel offload to protect the main context window).
Meta · Foundations
5Every behavioral rule MUST either run automatically (hook, linter) OR fire as a concrete tool call at a named decision point inside a skill step graph OR carry a `<!-- judgment-core: <reason> -->` marker per `@rule:rule-default-deterministic-trigger` justifying why neither is possible; MUST NOT be cited as enforcement if it exists only as advisory prose.
When multiple surfaces constrain the same actor for the same action, MUST consolidate to the most powerful surface (hook > app > rule for enforcement); the mechanism's header comments are the declaration.
The weekly telemetry aggregator (`local_aggregator.ts`) MUST flag rules that have not fired in the last 28 days AND have no `enforces:` references from skills/hooks; flagged rules are deletion candidates; MUST NOT let the rule-count grow monotonically without a deletion gradient.
Every fact (rule content, step definition, peer declaration, wire format, schema) MUST live in exactly one file; MUST NOT be mirrored as prose across multiple files.
When rules conflict or interpretation is ambiguous, MUST defer to Ashkaan; MUST flag conflicts for resolution; MUST NOT rewrite rules unilaterally.
Meta · Hooks
5Hooks MUST fall into one of nine allowed categories: (1) syntactic checks (lint/fmt/shellcheck/gitleaks/check-refs), (2) peer-file co-commit validation, (3) progress-doc co-commit validation, (4) checklist completion gating, (5) journal frontmatter validation, (6) memory-write redirect, (7) tool-sandbox blocks (block-context-builds, block-send-gmail), (8) session-discipline gate (refuse a tool/skill invocation that violates a session-shape invariant — e.g.
Every blocking hook MUST emit an error message naming the exact file, the exact line (when applicable), and a concrete remediation; MUST NOT say "fix the issue" without pointing at the issue.
Hooks MUST NOT subject `.md` files to syntactic checks (fmt, lint, linewidth); markdown is not code.
Every hook shell script MUST start with `set -euo pipefail` (after shebang); MUST NOT let errors silently propagate — hooks with broken state fail hard, not quietly.
Every hook shell script MUST pass `shellcheck` on commit; MUST NOT disable warnings without an inline `# shellcheck disable=SCXXXX` naming the specific issue.
Meta · Progress Docs
5`projects/README.md`, `apps/README.md`, `integrations/README.md` regenerate via `.claude/hooks/bodies/git-precommit.sh` Phase A; MUST NOT manually edit these files — edit the underlying per-project/per-app/per-integration README, and the index regenerates.
Journal entries MUST carry session frontmatter listing sessions worked on; MUST NOT commit journal entries with only `date` and `tags` when the session did project work — structured fields feed the telemetry aggregator.
When a commit touches files under `projects/<domain>/<date>_<slug>/` (other than that project's README), the commit MUST also update the project's README (`status`, `next`, `blocked-on`, or progress section) OR include `[progress-docs-current]` in the commit message (logged to telemetry); MUST NOT ship project work without synchronized progress docs.
Every project README MUST have frontmatter with: `project`, `status` (active|blocked|monitor|completed), `priority` (high|medium|low) for active/blocked/monitor, `created` (YYYY-MM-DD), `tags`, `description`; when `status: blocked`, MUST use `blocked-on:` instead of `next:`; when `status: monitor`, MUST use `monitoring-until: YYYY-MM-DD` naming the observation window end; MUST NOT mark `status: completed` autonomously — user-only.
When `/solve` fires, the session block in today's journal MUST contain exactly one line in this format: `**Root-Cause Status:** fixed | deferred-with-project | unknown-pending-verification — <project-path or verification-step>`; `/close` reads this via grep; MUST NOT use backticks on the status label or omit the separator.
Meta · Rule Authoring
10Every new rule, hook, or enforcement MUST cite a real prior failure (journal entry, incident, PR comment) in its evidence date `[YYYY-MM-DD]`; MUST NOT ship rules citing "TBD" or hypothetical scenarios — speculative rules inflate the ruleset without reducing real failures.
A rule MAY leave the always-loaded surface ONLY via a deterministic trigger (hook / `paths:` glob / high-signal keyword where the keyword IS the rule's activation condition).
Files with peer relationships MUST declare them in frontmatter `peers: [<paths>]`; `find-peers.sh` reads frontmatter across changed files; MUST NOT hardcode peer pairs in the find-peers script — adding a peer is a 1-line frontmatter edit.
When promoting a prose rule to a mechanism (hook, linter) and retiring the prose per `@rule:one-behavior-one-surface`, MUST first verify the mechanism's coverage equals the prose's full scope; MUST NOT retire prose broader than the mechanism enforces.
When a rule body contains a literal string that looks like a URL, version, vault name, path, or namespace, MUST classify the string as either a *governance convention* (the string IS what the rule enforces — keep inline) or a *vendor detail* (the string describes an external fact that happens to appear but isn't the rule's enforcement target — reference `integrations/{name}/README.md` or add a forcing function).
A new rule MUST default to a deterministic trigger — mechanized (hook/linter) or path-scoped (`paths:` glob) — so it loads or enforces without Claude choosing.
Every rule MUST end with `[YYYY-MM-DD]` citing a journal entry or commit where the originating failure was observed; MUST NOT use "TBD", "none", or empty brackets — the evidence date is the anti-speculation anchor.
Every `.claude/rules/*.md` file MUST have frontmatter with either `paths: [<globs>]` (path-scoped) or nothing (always-loaded); MUST NOT add arbitrary frontmatter fields — Claude Code only recognizes `paths:`.
A new or edited rule body MUST be minimal-context: the imperative (`MUST`/`MUST NOT`), a `[YYYY-MM-DD]` evidence date, and at most a ≤1-line why.
Rule IDs (section slugs) MUST be stable once shipped — renaming breaks cross-file `@rule:<id>` references; MUST NOT rename a rule without updating all `enforces:` and `@rule:` citations in the same commit (linter enforces).
Meta · Skill Authoring
6The skill's markdown body MUST match the step graph in frontmatter (every step-ID mentioned in body matches an id in `steps:`, and every frontmatter step has a body section describing it); MUST NOT let body and frontmatter drift — linter checks parity.
Every rule ID in a skill's `enforces: [@rule:<id>]` field MUST resolve to an existing rule section in `.claude/rules/`; MUST NOT reference rules that don't exist — linter rejects dangling enforces.
Every step of `kind: gate` MUST specify the `tool` (`AskUserQuestion`, `shell`, `halt`, `validate-schema`, `agent`, `skill`) and `on_fail` (`halt | warn | continue`); MUST NOT use "prompt the author" as a tool — the tool name must be a concrete mechanism.
Every inter-skill handoff MUST appear in both producer's `handoffs_to:` and consumer's `handoffs_from:`; wire format declared in producer's `writes:` field MUST match consumer's `reads:` field; MUST NOT leave handoffs one-sided — linter flags asymmetry.
Every skill MUST declare in frontmatter: `name`, `description`, `argument-hint` (when the skill takes arguments), `disable-model-invocation` (explicit true/false), `enforces:` (`@rule:<id>` list — may be empty `[]` if the skill enforces no specific rules); MUST declare `peers:` when the skill loads co-located files; MUST declare `steps:` and `handoffs_to:` / `handoffs_from:` when the skill has a gated step graph.
When a skill has gates (AskUserQuestion, halt, shell checks, or inter-skill handoffs), its `steps:` frontmatter MUST declare each step's `id`, `kind` (action | gate), and either an `action:` description (for kind=action) or a `gate:` spec (for kind=gate); MUST NOT use ambiguous intermediate forms.
Meta · Telemetry
5`.claude/hooks/telemetry/local_aggregator.ts` MUST be invoked weekly to read the last 7 days of journal frontmatter and produce the rule-firing histogram, silent-path-scope failures, root-cause-status distribution, stale-rule list, and correction-vs-rule-deltas signal.
If the weekly aggregator report is not read for 30 days, the aggregator MUST be marked deprecated; MUST NOT run telemetry that no human consumes — unread metrics are dead weight.
Session telemetry emits via two surfaces: (a) mechanism-firing log via `.claude/hooks/checks/log-mechanism-fired.sh` sourced from each check script — the primary signal, (b) journal frontmatter `sessions:` block with `rules_should_have_fired`, `implement_audit_rounds`, `root_cause_status` — the supplementary self-noticed signal.
When the aggregator shows a failure class not dropping over 2 weeks, that data point MUST drive the next rule change (new rule, revised mechanism, or promotion to enforcement); MUST NOT evolve rules on intuition without telemetry support.
The ai-workflow-telemetry report MUST emit either formatted text or JSON each run; MUST NOT persist a separate report artifact.
Node Modules Location
1For ANY TS app in this repo that uses npm dependencies, `node_modules/` MUST live outside `apps/{name}/` and `integrations/{name}/`.
Node Stack
3When about to write a `/tmp/*.ts` or scratch script that calls a typed client from `integrations/{name}/`, MUST first prefer one of three shapes in order: (1) copy the documented one-liner from `integrations/{name}/README.md § "Common invocations"` if it covers the use case; (2) inline via `node --input-type=module -e '...'` for one-shot inspection (no file written, no cleanup, no permission prompt); (3) write the snippet directly into `integrations/{name}/README.md § "Common invocations"` as a new cookbook entry, then run it.
When running TypeScript in this repo — trigger.dev task containers, Hatchet workers (coexistence window only), Daedalus bootstrap scripts (`integrations/*/[name]_auth.ts`), one-off smoke probes, REPL-style invocations on Daedalus — MUST use Node 22 (`node script.ts`, `node --strip-types script.ts`, or `npx tsx script.ts` for transitional callers); MUST NOT use `deno run` or `bun run` for any repo TS code.
TS execution on Node 22 SHOULD prefer the bare `node script.ts` invocation when running on 22.6+ (type-stripping is default-on at that version); MAY pass `--strip-types` explicitly for documentation in `ExecStart` lines and shell snippets, or use `--experimental-strip-types` on 22.x below 22.6.
People
3All people and entities MUST live in `knowledge/people/{name}/` with a `README.md`; MUST NOT create ad-hoc person files elsewhere (projects/, journal/ references are OK — canonical record lives in people/).
When a person is mentioned or involved in project work, MUST enrich their `knowledge/people/<name>/` files (README, bio.md, mind.md, professional.md as relevant) in the same session; MUST NOT defer enrichment to "later." [2026-03-12]
When editing a `knowledge/people/<name>/README.md`, MUST follow the format of existing people READMEs (bio, professional, mind, health, assessments as applicable); MUST NOT deviate from the pattern without a stated reason in the file.
Portal Apps
2When making a user-facing portal change, MUST update `src/changelog.json` in the same commit; MUST NOT ship user-visible changes without a changelog entry.
MUST NOT run `npm run build`, `npm install`, or `wrangler pages deploy` locally on portal sites; portal deploys happen automatically on git push (Cloudflare Pages watches the repo).
Shell
1Every shell script MUST start with `set -euo pipefail` (after the shebang); MUST NOT rely on default shell safety — errors must surface, not silently propagate.
Style Anti Ai Tells
1When drafting any content for a human reader other than Ashkaan (LinkedIn posts, blog articles, email, Slack, client/team notes, texts), MUST pass the AI-voice checks: (a) NO banned AI-tell tokens (SSOT `anti-ai-banned-tokens.txt`, mechanism `check-anti-ai-tokens.sh` blocks the producer apps at commit) and no thesaurus-substitute dodges; (b) NO em dashes — use commas, parentheses, or split the sentence; (c) NO mechanical bold (`**`) — CAPS one word or restructure; (d) take a side, vary sentence rhythm, no rule-of-three, no copula-substitution, no tidy-bow summary sentences, keep at least one rough edge; (e) LinkedIn posts MUST use specific numbers / named frameworks / concrete outcomes and include ≥1 personal-voice marker; (f) when revising a draft Ashkaan wrote himself, MUST preserve his original phrasing and only optimize for the checks above.
Style App Development
17Every SSR app MUST ship BOTH `src/pages/404.astro` (with `export const prerender = true;` so CF Pages serves the static `404.html` for direct hits) AND `src/pages/[...slug].astro` (SSR catch-all returning **200**, not 404, so Astro's ClientRouter doesn't full-page-reload during View Transitions); MUST NOT ship only one — the prerendered file misses client-side nav, the catch-all alone breaks SEO/bots.
When doing date arithmetic on calendar dates, MUST use `addMonths(dateStr, n)` and `todayLocal()` from `@theme/lib/dates`; MUST NOT use `Date` constructor + `setMonth` patterns — TZ-shift bugs are silent.
When parsing a calendar date for display, MUST use `parseCalendarDate(dateStr)` from `@theme/lib/dates`; MUST NOT use `new Date("YYYY-MM-DD")` — JS spec parses date-only strings as UTC midnight, shifting the day back for US-timezone users.
A calendar date (no time component — complaint date, close date, review date, due date) MUST be stored as `"YYYY-MM-DD"` string (the format `<input type="date">` produces); MUST NOT round-trip through `new Date()` for storage.
Theme-affecting interactive components (`ThemeSwitcher`, `Changelog`, `FeatureRequest`) MUST hydrate with `client:load`; MUST NOT use `client:idle` or `client:visible` — they need to be live before user interaction.
When creating a new internal app, MUST scaffold via `/portal-init [new-name] --from [source-portal-slug]` (clones the closest-matching live portal — `finance.wesolve.tech`, `operations.wesolve.tech`, `sales.wesolve.tech`, `dashboard.ashkaan.me` — into `~/code/[new-name]`, strips `node_modules` + `.git`, resets `name` in `package.json` + `wrangler.toml`, runs `git init`); MUST NOT hand-roll Astro/wrangler/KV setup or recreate it from documentation.
Every app API route MUST guard with `getEmail(request)` from `@theme/lib/auth` (checks `CF-Access-Authenticated-User-Email` header first, falls back to `CF_Authorization` JWT cookie) and return `401` when null; MUST NOT roll custom auth — Cloudflare Access (Zero Trust) is the canonical auth surface.
Every internal app MUST use `DashboardLayout.astro` with: outer `flex h-screen w-full`; sidebar `hidden md:flex w-64 flex-col border-r bg-sidebar` carrying `transition:animate="none"`; content `flex flex-col flex-1 min-w-0`; main `flex-1 overflow-hidden`.
Every app MUST expose `POST /api/feature-request` accepting `{ description, appName, page?, requesterName?
When local-dev needs `locals.runtime.env` (KV, D1, or other bindings), MUST use `wrangler pages dev -- astro dev`; plain `astro dev` runs faster but bindings are `undefined`.
MUST NOT run `npm` commands from the context repo root — run from the app folder or `~`; otherwise `package.json`/lock files/`node_modules` land in the context repo.
Every `.astro` page in an internal app MUST define `const pageInfo: PageInfoEntry` in frontmatter and pass it to `DashboardLayout` via `pageInfo={pageInfo}`; `PageInfo.tsx` reads it for the per-page context modal (purpose, mechanics, data sources, notes); presence is CLAUDE-applied (no automated check); MUST NOT ship a page without `pageInfo`.
When persisting user preferences in an app, MUST use `getItem/setItem/syncFromServer` from `@theme/lib/prefs` (synchronous localStorage read, fire-and-forget background PUT to `/api/prefs` → KV); the app MUST expose `/api/prefs` (GET/PUT) endpoints — the edge-app scaffold includes them; MUST NOT bypass the localStorage-first pattern.
When styling an app, MUST use the base radius `var(--radius)` = 10px (variants `rounded-sm` 6px through `rounded-2xl` 18px), navy-tinted shadows in light mode and black-tinted in dark (`shadow-card`/`shadow-card-hover`), blue glows via `shadow-glow`/`shadow-glow-lg` for interactive elements; MUST NOT hand-roll one-off shadow stacks.
When styling or building UI in an internal app, MUST use the shared theme's canonical surface — design tokens, the typography trio, utility classes, and animation classes — from `theme/` (git subtree of `github.com/Ashkaan/internal-apps-shared.wesolve.tech`, the SSOT); MUST NOT introduce app-specific palette/font overrides, re-implement a themed pattern inline, or write custom keyframes for the standard cases.
A timestamp (precise moment in time — `createdAt`, `updatedAt`, `lastUpdated`) MUST be stored as `new Date().toISOString()` (`"2026-04-09T19:42:11.000Z"`); display via `formatTimestamp(isoStr, options?)` from `@theme/lib/dates`.
Every app `wrangler.toml` MUST declare: `name = "app-name"`, `compatibility_date = "2025-09-01"`, `compatibility_flags = ["nodejs_compat_v2"]`, `pages_build_output_dir = "dist"`, `max_duration = 30`; MUST NOT change compatibility date/flags without verifying every binding still works.
Style Biome
1The repo's TypeScript formatter + linter is Biome `^2.4.15` <!-- rule-ref: allowed --> installed at `~/.local/lib/quality/node_modules/@biomejs/biome` (per `@rule:node-modules-out-of-repo`).
Style Code Conventions
12TypeScript and shell identifiers MUST follow these casing conventions: - **Variables, functions, methods (TS):** camelCase (`dayStart`, `fetchJson()`).
Shell scripts MUST follow these formatting + safety conventions (naming covered by @rule:naming-conventions): - 2-space indentation (no tabs), 80-char hard line limit.
Exported functions MUST have an explicit return-type annotation; MUST NOT rely on inference for exports — exports are the contract.
Thrown error messages MUST be sentence case, no trailing period, active voice, with context (`throw new Error(\`Cannot refresh token for ${path}: ${r.status}\`)`); MUST NOT use vague messages ("Token refresh error.").
TypeScript imports MUST be grouped in this order, blank-line separated: (1) type imports, (2) third-party packages, (3) `integrations/` paths, (4) relative imports; MUST NOT mix groups.
When defining an object shape, MUST use `interface` (extendable, better error messages); when defining unions, intersections, or aliases, MUST use `type`; result types MUST be named (not inline) to document the contract; for untyped API responses, MUST use `as ApiResponse` cast on `await res.json()`.
When catching errors, MUST log or push to warnings; MUST NOT use empty `catch {}` — silent failures are bugs.
TypeScript code MUST fetch secrets via the canonical resolver: `getCred(CREDS.x, "field")` from `integrations/1password/op_helper.ts` (typed UUID-keyed credential map per `projects/homelab/2026-05-01_credential-architecture/`).
MUST NOT write JSDoc on internal/non-exported code — types are the documentation; MAY write a one-line `/** description */` + `@param` tags ONLY on shared utility exports (functions imported from `integrations/{name}/`).
TypeScript code MUST be explicit; MUST NOT use `Proxy`, `eval`, or runtime metaprogramming patterns — implicit behavior is hard to trace and harder to debug.
Node.js scripts on Daedalus MUST use `.js` extensions in relative imports (Node ESM requirement); MUST NOT omit the extension.
Before committing TypeScript changes, MUST pass the code-quality checks: `biome-quality` (lint), `gitleaks`, `check-refs`; MUST NOT commit with quality-gate failures — fix at the source.
Style Design
2Browser automation MUST go through the `npx playwright` CLI directly OR an inline Node script that imports the `playwright` npm package.
When designing for slideshows or presentations, MUST keep body / bullets / supporting copy at `text-lg` (18px) or larger; eyebrows + mono captions MAY sit at `text-base` (16px) with bolder weight + tighter tracking to keep hierarchy.
Style Longform Format
2When drafting LinkedIn posts, MUST use plain text with line breaks, CAPS (sparingly), and `→` arrows; MUST NOT use markdown `**bold**`, `# headers`, or `- bullets`; LinkedIn renders them as raw characters.
When drafting blog articles or long-form posts, MUST use sequential heading levels (H1 → H2 → H3); MUST NOT skip from H1 to H3; AI does this routinely.
Trigger Dev Stack
5Every trigger.dev app under `apps/{name}/` (flat) or `apps/<domain>/{name}/` (domain member) MUST use the B2 layout: docs + config at the app root (`README.md`, `SPEC.md`, `package.json`, `tsconfig.json`, `trigger.config.ts`), task declarations under `src/tasks/*.task.ts`, pure helpers under `src/lib/*.ts`, tests under `tests/*.test.ts`.
Every `apps/{name}/trigger.config.ts` MUST set `runtime: "node-22"`; MUST NOT use `"node"` (which selects 21.7.3), `"bun"`, or any other value — single-runtime Node 22 across the repo (per `@rule:node-runtime-is-node-22`) is the coherence guarantee.
A trigger.dev **project** is a deploy bundle (one built image → one GHCR slot → one run-history view).
All trigger.dev apps share ONE off-repo dep cache at `~/.local/lib/trigger-dev/` (bootstrapped one-time via `bash .claude/hooks/checks/trigger-dev-build.sh bootstrap`).
Every `apps/{name}/package.json` that depends on trigger.dev MUST pin `@trigger.dev/sdk` to `^4.4.6` <!-- rule-ref: allowed --> (the latest stable at the 2026-05-14 plan-write, verified via `npm view @trigger.dev/sdk version`); MUST NOT depend on `@trigger.dev/sdk@beta`, `@v4-beta`, `@v4-prerelease`, or any 0.0.0-dated dist-tag — the trigger.dev team publishes dozens of dated dist-tags (`next`, `prerelease`, `chat-prerelease`, etc.); accepting any of those into the repo's lockfile pulls non-reproducible code into production.
Ui Behavior
9Interface copy MUST be specific ("Enter email", not "Enter value"), active ("Save changes", not "Changes will be saved"), and MUST tell the user what to do rather than only what happened.
Destructive actions MUST prefer undo over a confirmation dialog — users click through confirmations without reading, so a dialog buys less safety than a reversal window.
Before shipping any view, MUST enumerate expected behavior — not merely "test with" — at each axis.
Text that will be translated MUST use logical properties (`margin-inline-start`, `padding-inline`, `border-inline-end`) rather than left/right, and MUST mirror directional glyphs under RTL (`[dir="rtl"] .arrow { transform: scaleX(-1) }`).
Every interactive element MUST define all eight states before it ships: default, hover, focus, active, disabled, loading, error, success.
Loading states MUST NOT use jokey placeholder copy ("Herding pixels", "Teaching robots to dance", "Consulting the magic 8-ball") — instantly recognizable as machine-generated and the fastest way to make a product feel cheap.
Dropdowns, tooltips, popovers, and menus MUST escape their clipping ancestor.
UI work MUST hold Core Web Vitals: LCP <2.5s, INP <200ms, CLS <0.1, and 60fps (~16ms/frame) on interaction.
Responsive behavior MUST key off input capability where the concern is input — `@media (pointer: coarse)` for touch-sized padding, `@media (hover: hover)` to gate hover-only affordances — and off width only where the concern is space.
Ui Craft
9When defining or extending a palette, MUST author in OKLCH (lightness 0–100%, chroma ~0–0.4, hue 0–360) rather than HSL — HSL lightness is not perceptually uniform, so equal-lightness steps look uneven.
Text and UI MUST meet WCAG AA: body 4.5:1, large text (18px+, or 14px bold) 3:1, UI components and focus indicators 3:1.
When a directive seems to call for a new primitive — a color, gradient, radius, shadow, font, or decorative effect not already in the repo's `DESIGN.md` — MUST NOT invent it.
When loading a webfont, MUST declare fallback metric overrides (`size-adjust`, `ascent-override`, `descent-override`, `line-gap-override`) so the fallback occupies the same box — unmatched fallback metrics are the actual cause of font-swap layout shift.
Custom transitions MUST take their duration from the band matching the change — 100–150ms instant feedback (press, toggle, color), 200–300ms state change (menu, tooltip, hover), 300–500ms layout change (accordion, modal, drawer), 500–800ms entrance (page load, hero reveal) — and MUST use one of three easings: `--ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1)`, `--ease-out-quint: cubic-bezier(0.22, 1, 0.36, 1)`, `--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1)`.
MUST NOT ship the generated-UI signature set: gradient text (`background-clip: text` on a gradient), glassmorphism panels, neon accents on near-black, cyan/purple gradient pairs, cream/sand body backgrounds (`oklch(97% 0.01 60)`-family), thick one-side accent borders on rounded cards, repeating tiny uppercase kickers above every section heading, sequential `01/02/03` section markers, icon-tile stacks, thin-border-plus-wide-shadow cards, and scroll-fade-rise on every section.
Before acting on any subjective design directive ("bolder", "quieter", "cleaner", "more premium"), MUST resolve which register the surface is in and state it.
Spacing MUST come from a 4pt scale (4, 8, 12, 16, 24, 32, 48, 64, 96) — 8pt-only is too coarse, since 12px is needed between 8 and 16.
When defining a type ramp, MUST pick one ratio and commit to it: brand surfaces ≥1.25 (1.25 major third / 1.333 perfect fourth / 1.5 perfect fifth), product surfaces 1.125–1.2.