Skip to content
prod e051e98
Browse

3 · Project constitution

Objective — write the constitution: the small set of files (AGENTS.md, CLAUDE.md, CLAUDE.local.md) that tells every AI tool what this project is, how to behave, and what never to touch — loaded automatically at the start of every session so the agent never starts blind.

A CodeCanyon app is unfamiliar vendor code. The constitution is three files, each with a distinct reader and a distinct fate in git:

flowchart LR
A["AGENTS.md<br/>cross-tool constitution<br/>(committed)"]
C["CLAUDE.md<br/>thin pointer → AGENTS.md<br/>(committed)"]
L["CLAUDE.local.md<br/>personal env + secrets refs<br/>(gitignored)"]
C -->|points to| A
A -.read by.-> Tools["Claude Code · Cursor · Codex · Gemini · others"]
L -.personal overlay.-> Claude["the Claude Code session"]
FileCommitted?Role
AGENTS.mdThe canonical contract. Tool-agnostic. Tech stack, conventions, safety rules, the read-set. Every modern agent reads it.
CLAUDE.mdA thin pointer that says “read AGENTS.md first,” plus any Claude-specific notes. Keeps a single source of truth.
CLAUDE.local.md❌ gitignoredPersonal: local URL, server IPs, zone IDs, credential-store references. Differs per developer; never in git.

Use this instead of hand-authoring constitution through Cursor wiring file-by-file (Claude config, Rules & skills, and Cursor & other IDEs are included in the same drop).

  1. Fetch + install in one command — from your project root (the app folder with the vendor tree from Create the project). No repo clone, no manual download:

    Terminal window
    curl -fsSL https://library.zajapps.com/kits/codecanyon-ai-system.tgz | tar -xz
    bash seed.sh # renames dot-* → real dotfiles; materializes project_context.md

    The tarball unpacks its contents straight into the current directory, then seed.sh wires them up. Prefer to inspect first? Browse the file map or MANIFEST, and read seed.sh after extracting — before you run it.

  2. Fill placeholders — run the onboarding interview below (or answer the kit’s printed prompts) so AGENTS.md and project_context.md match this app.

  3. Pick a permission mode./.claude/claude-mode/bin/set-claude-mode.sh medium (see Claude config).

  4. Restart your agent session, then continue to Verify & gate.

What seed.sh lays down in one pass: AGENTS.md, CLAUDE.md, CLAUDE.local.md.example, the full .claude/ tree (settings, rules, skills, hooks, mode switcher), .mcp.json, and .cursor/ mirrors. The individual pages remain the reference if you need to change one subtree later.

Before writing files, gather context. The agent should inspect both the stack and the app surface first, then pre-fill evidence-based answers for the user to confirm or correct.

  1. Auto-detect the stack from the repo.

    Terminal window
    head -30 composer.json # Laravel version, PHP req, key packages
    head -20 package.json # frontend deps (Livewire / Alpine / Vue / Tailwind)
    ls routes/ # route files → surface area
    ls -d app/Http/Controllers/*/ 2>/dev/null # controller organization
    ls database/migrations/ | wc -l # migration count → deploy timeout risk
    # Expected: Laravel version, frontend deps, route/controller layout, migration count
    • ✅ The auto-detected Laravel version, frontend, CSS, auth, and DB are captured for the interview.
  2. Inspect what the app actually does before asking business questions. Generic interview choices miss the mark on CodeCanyon apps. Read the models, migrations, modules, and route groups so the agent can pre-fill the app’s feature surface and monetization model from evidence.

    Terminal window
    find app/Models -maxdepth 1 -type f -name '*.php' 2>/dev/null | sed 's#app/Models/##' | sort | head -80
    find Modules app/Classes -maxdepth 2 -type d 2>/dev/null | head -80
    rg -n "Plan|Subscription|Order|Coupon|Module|Trial|Payment|Invoice|Warehouse|Helpdesk|Chat" \
    app database routes Modules 2>/dev/null | head -120
    # Expected: feature areas, billing/plan objects, module system, and route groups are visible enough to pre-fill the interview
    • ✅ The agent can say, “I see subscription plans / orders / coupons / modules / feature areas X-Y-Z; confirm or correct.”
    • ✅ Human-only questions stay human-owned, but they are grounded in the actual codebase instead of hardcoded generic options.
  3. Answer the human-only questions and record them — they populate the files below.

    • App identity: What’s the app called (its name)? What type is it — SaaS, marketplace, directory, or other? Who’s the author/owner (your name + email)? These feed the <AppName> / “what this is” lines in AGENTS.md plus your git config and license/branding.
    • Business: What does the app do? Who’s the customer? What’s the monetization? Start from the agent’s evidence summary (Plan, Subscription, Order, Coupon, modules, feature routes), then correct what the code cannot know.
    • Vendor: Which CodeCanyon item / vendor family (Froiden, LiquidThemes, WorkDo, InfyOm, IqonicDesign, other)? This predicts gotchas later.
    • Stack confirmation: Confirm the auto-detected Laravel version, frontend, CSS, auth (Sanctum/Passport), and DB.
    • Infra: Where does it deploy? Production domain(s)? Staging URL? Hosting provider + plan?
    • Integrations: Stripe (test + live)? Mail provider? Anything else holding secrets?
    • Team: Solo or multi-developer? (Decides whether the gitignored personal files need a shared convention.)
    • ✅ All seven answer-sets recorded in a scratch note before drafting any file.

Write AGENTS.md at the repo root using the auto-detected stack so the tech-stack section is accurate. The kit ships a complete version; the essential shape is below.

  1. Draft the file at the repo root with the canonical shape.

    # AGENTS.md — <AppName>
    > Cross-tool constitution. Every AI agent (Claude Code, Cursor, Codex, Gemini) reads this first.
    ## What this is
    <AppName> — a <SaaS / marketplace / directory> built on a CodeCanyon Laravel base (<vendor family>).
    ## Read first (the read-set)
    1. This file (AGENTS.md).
    2. `.claude/rules/` — behavioral + reference rules (auto-loaded).
    3. `CLAUDE.local.md` — personal env (if present, gitignored).
    4. `_CUSTOMIZATIONS.md` — log of every deviation from vendor code (create an empty stub at [Create the project §6](/tech-stack/laravel/codecanyon/build/playbooks/setup-new/01-ai-system/01-project-setup/) or Phase 2 import; until then, treat as “not yet created”).
    > **Logging cadence:** append to `_CUSTOMIZATIONS.md` **at the moment of the deviation**, not in a batch later — one entry per in-place vendor edit (`ZAJ:BEGIN/END`), net-new file (`ZAJ:FILE`), custom `_zaj` migration, and provisional/strategic decision (e.g. the Stripe account-strategy block). After any installer / deploy / migration / vendor update, run `git diff --name-only` and reconcile anything untracked against the ledger before committing.
    ## Tech stack
    - Laravel <version> (PHP ^<version>), <Livewire/Alpine/Vue>, <Tailwind/Bootstrap>, MySQL, <Sanctum/Passport>.
    ## Conventions
    - Custom migrations use a `_zaj` suffix; never edit vendor migrations (overwritten on update).
    - Wrap in-place vendor edits with `ZAJ:BEGIN` / `ZAJ:END` markers; net-new files start with `ZAJ:FILE`.
    - Use `gh` for all GitHub operations.
    ## Safety rules (non-negotiable)
    - **Tinker:** never create/update/delete records via `php artisan tinker` without explicit approval; prefer direct SQL reads.
    - **Vendor files:** never modify `vendor/`; document deviations in `_CUSTOMIZATIONS.md`.
    - **Migrations:** guard with `Schema::hasTable()/hasColumn()`; never `migrate:fresh|wipe|reset` on shared data.
    - **Route protection:** block sensitive paths at the web-server level (`.htaccess` `[F]` / Nginx `deny`), not only in middleware. CodeCanyon apps often boot heavy middleware stacks; a middleware-only installer block can 500 before it blocks. Web-server denial stops the request before PHP boots.
    - **`.env` quoting:** **always** double-quote *every* `.env` value. Unquoted, the characters `# $ & ^ [ + * ;` and spaces break `.env` parsing. A password like `p@ss#w&rd$1` written unquoted silently truncates to `p@ss` (parsing stops at the first `#`). Applies to `DB_PASSWORD`, `REDIS_PASSWORD`, `MAIL_PASSWORD`, API keys — every value:
    ```dotenv
    # ❌ silently truncates to p@ss
    DB_PASSWORD=p@ss#w&rd$1
    # ✅ stored intact
    DB_PASSWORD="p@ss#w&rd$1"
    • Composer auth: for private/paid packages, set composer config --global github-oauth.github.com <token> — never paste tokens inline into composer.json.
    • Post-change: after any installer/deploy/migration, run git diff --name-only and report before committing.
    • Secrets: store in a secrets manager or the gitignored credentials file — never echo secret characters into a session.
    • Cloudflare budgets: free-plan WAF/rate-limit/cache budgets are tight. Scope WAF/rate-limit to /admin, /login, /install, and /update; never challenge the public landing page or checkout without a human SEO/business decision.
    • Read this file and .claude/rules/ before any SSH or deploy action.
    • Prefer MCP/API tools and gh over hand-run shell where one exists.
    • Stay within resource budgets — shared hosting (migration timeouts, memory) and the Cloudflare free plan (≈ 5 WAF rules, 1 rate-limit rule, 10 cache rules). Note usage and remaining capacity after each Cloudflare operation.
    • Never challenge the landing page. Scope every WAF / rate-limit rule to specific sensitive paths (/admin, /login, /install) — never apply a challenge or block to / or a broad URI pattern; that blocks search engines and real visitors.
    • Never replace the vendor landing page or demo content silently — flag it for a human decision.
    - ✅ The tech-stack line matches what Create the project auto-detected; vendor family and business lines match the interview.
  2. Verify on disk — commit as C3 at the end of this page.

    Terminal window
    test -f AGENTS.md && git add -n AGENTS.md
    # Expected: AGENTS.md listed (dry-run) — commit in §7 below
    • AGENTS.md is at the repo root (written now; committed at C3).

Keep CLAUDE.md short so there’s no drift — it only redirects to AGENTS.md plus any Claude-specific notes.

  1. Create the pointer at the repo root.

    # CLAUDE.md — <AppName>
    > Single source of truth lives in **AGENTS.md** — read it first.
    > This file holds only Claude-specific notes to avoid cross-tool drift.
    ## Claude-specific notes
    - Read `~/.claude/skills/zaj-laravel-codecanyon/SKILL.md` at session start — its orchestration + safety rules apply whether or not the skill is invoked.
    - `CLAUDE.local.md` (gitignored) holds personal/machine notes.
    • CLAUDE.md contains only a redirect + Claude-specific notes — no duplicated stack/conventions.
  2. Verify on disk (same timing as AGENTS.md — commit at C3).

    Terminal window
    test -f CLAUDE.md && git add -n CLAUDE.md
    # Expected: CLAUDE.md listed (dry-run)
    • CLAUDE.md sits at the repo root alongside AGENTS.md.

4. Create CLAUDE.local.md (personal, gitignored)

Section titled “4. Create CLAUDE.local.md (personal, gitignored)”

This file holds the per-developer environment — local URL, server IPs, zone IDs — and only references credentials, never the secrets themselves. It must never enter git.

  1. Write the personal file at the repo root.

    # CLAUDE.local.md — Personal (NOT committed)
    ## My environment
    - Local URL: http://<app>.test
    - DB GUI: TablePlus
    ## Infra
    - Zone ID: <your-zone-id> Server IP: <your-server-ip>
    - DNS token env var: $CF_API_TOKEN
    - Hosting: <provider> · root: /home/<user>/domains/<domain>/public_html
    ## Credentials
    - Stored in: <1Password vault / credentials file path>
    - ⚠️ When writing passwords to `.env`, always double-quote them.
    • ✅ The file references where credentials live, never the secret values.
  2. Gitignore it and confirm.

    Terminal window
    grep -q "CLAUDE.local.md" .gitignore || echo "CLAUDE.local.md" >> .gitignore
    git check-ignore CLAUDE.local.md # → CLAUDE.local.md
    # Expected: the command prints "CLAUDE.local.md", proving it's ignored
    • git check-ignore CLAUDE.local.md echoes the filename — git will never stage it.

CLAUDE.local.md only references credentials; the secrets themselves live in one of three stores. The default is 1Password (op) because it keeps secrets encrypted and lets the project commit safe op:// templates instead of plaintext env copies.

OptionBest forHow it works
A · 1Password CLI (op) default / recommendedTeams, repeat deployments, secret rotationSecrets stay in a project vault. A committed .env.tpl (or .env.<env>.tpl) holds only op://<Project>/<Environment>/<ENV_VAR> references; op inject renders the real .env on demand.
B · Vault templatesOffline / no 1PasswordPer-env files live only in gitignored Admin-Local/1-Project/2-ProjectVault/; only .env.example is tracked — wired in Phase 2 · Wire .env templates.
C · credentials.mdSolo / throwawayA single gitignored markdown file in the vault lists each secret. Simpler, but unencrypted on disk — never commit it.

For 1Password references, use this convention everywhere: op://<vault>/<item>/<field> where vault = project (Custojo), item = environment (Local, Staging, Production), and field = env var (DB_PASSWORD, APP_KEY, STRIPE_SECRET). Example value line: DB_PASSWORD="op://Custojo/Production/DB_PASSWORD".

  1. (Option A) Install and verify op. Machine setup has the full CLI walkthrough; here you only confirm the chosen store is usable from this project.

    Terminal window
    command -v op >/dev/null || brew install --cask 1password/tap/1password-cli
    op --version
    op vault ls >/dev/null && echo "op vault access OK"
    # Expected: op 2.x; vault access OK
    • op vault ls works. Do not use op whoami as the service-account test.
  2. Pick or create the project vault — service account creates it. For the zero-manual-grant path, the same service account from Machine setup §3.5 creates the project vault. Do not reuse a human-created vault unless a human explicitly granted that service account access in 1Password.

    Terminal window
    PROJECT="<ProjectName>"
    op vault get "$PROJECT" >/dev/null 2>&1 || op vault create "$PROJECT" >/dev/null
    op vault get "$PROJECT" >/dev/null && echo "vault OK: $PROJECT"
    # Expected: vault OK: <ProjectName>
    • ✅ The vault name is the project name, not the environment and not a generic “Env” bucket.
    • ✅ If op vault create fails with access/permission error, recreate the service account with vault-creation permission or use the 1Password UI to grant access; do not keep retrying item creation against a vault the token cannot see.
  3. Create one item per environment. Create or reuse Local, Staging, and Production items inside the project vault. Each env var becomes a field on the matching environment item, so secrets can vary naturally by environment.

    Terminal window
    PROJECT="<ProjectName>"
    for item in Local Staging Production; do
    op item get "$item" --vault "$PROJECT" >/dev/null 2>&1 \
    && echo "item exists: $item" \
    || echo "TODO: create 1Password item '$item' in vault '$PROJECT' with one field per env var"
    done
    # Expected: each item exists, or an explicit TODO for the user to create/fill it
    • Local, Staging, and Production items exist before rendering templates.
  4. Generate .env.tpl from .env.example keys — don’t hand-write it.

    Terminal window
    PROJECT="<ProjectName>"
    ENVIRONMENT="Local"
    test -f .env.example || { echo "FAIL: create .env.example first"; exit 1; }
    awk -F= -v vault="$PROJECT" -v item="$ENVIRONMENT" '
    /^[[:space:]]*#/ || /^[[:space:]]*$/ { print; next }
    /^[A-Za-z_][A-Za-z0-9_]*=/ {
    key=$1
    print key "=\"op://" vault "/" item "/" key "\""
    next
    }
    { print }
    ' .env.example > .env.tpl
    grep -q 'op://'"$PROJECT"'/'"$ENVIRONMENT" .env.tpl && echo ".env.tpl refs OK"
    if grep -n '^[[:space:]]*#.*op://' .env.tpl; then
    echo "FAIL: remove op:// references from .env.tpl comments"; exit 1
    fi
    # Expected: .env.tpl exists; every key points at op://<Project>/<Environment>/<KEY>
    • .env.tpl contains references only. It is safe to commit because it has no secret values.
    • .env.tpl has no commented op:// references. Comments may describe the environment, but examples containing op:// live in docs, not inside the template.
  5. Render + verify without printing values.

    Terminal window
    op inject -i .env.tpl -o .env
    test -s .env && echo ".env rendered"
    grep -q '^APP_KEY=' .env && echo "APP_KEY present (value hidden)"
    git check-ignore .env
    git check-ignore .env.tpl || echo ".env.tpl trackable OK"
    # Expected: .env rendered; .env ignored; .env.tpl not ignored
    • .env is populated and gitignored; .env.tpl is trackable and contains only op:// references.
  6. Thread staging/production through deploy. For each environment, render from that environment item rather than copying a local .env.

    Terminal window
    # Example: render production from op://<Project>/Production/<KEY>
    PROJECT="<ProjectName>" ENVIRONMENT="Production"
    awk -F= -v vault="$PROJECT" -v item="$ENVIRONMENT" '
    /^[[:space:]]*#/ || /^[[:space:]]*$/ { print; next }
    /^[A-Za-z_][A-Za-z0-9_]*=/ { key=$1; print key "=\"op://" vault "/" item "/" key "\""; next }
    { print }
    ' .env.example > ".env.${ENVIRONMENT}.tpl"
    if grep -n '^[[:space:]]*#.*op://' ".env.${ENVIRONMENT}.tpl"; then
    echo "FAIL: remove op:// references from template comments"; exit 1
    fi
    op inject -i ".env.${ENVIRONMENT}.tpl" -o ".env.${ENVIRONMENT}"
    # Expected: .env.Production rendered locally for upload/deploy, never committed
    • ✅ Each deployment environment has a reproducible render path from 1Password.

Confirm a fresh agent actually reads the contract before declaring this step done.

  1. Start a fresh agent session in the project and watch for unprompted stack/convention references.

    • ✅ Without being told, the agent references the stack/conventions — proof it read AGENTS.md. If not, confirm the files are at the repo root and restart the session.
  1. Stage and commit C3 from the project root.

    Terminal window
    git branch --show-current # must be develop
    git add AGENTS.md CLAUDE.md .gitignore
    test -f .env.tpl && git add .env.tpl # Option A — references only; safe to commit
    git commit -m "feat(ai): project constitution (AGENTS.md + CLAUDE.md)"
    git log --oneline -3 # C3 atop C2 + C1
    # Expected: C3 on develop; CLAUDE.local.md not staged; the CLAUDE.local.md ignore rule is committed
    • git log -3 shows C3; git check-ignore CLAUDE.local.md still succeeds.
    • ✅ When Option A (1Password) was chosen, .env.tpl lands in the same atomic commit — references only, no secret values.

Do not mark this step done until every box below is checked.

  • 🔀 Interview answers recorded — app identity (name/type/author), business, vendor family, stack, infra, integrations, and team size captured in a scratch note after stack + feature/business recon.
  • 🤖 AGENTS.md written — at the repo root, tech-stack line matches the auto-detected stack; C3 committed.
  • 🤖 CLAUDE.md written — thin pointer only; no duplicated stack/conventions; included in C3.
  • 👤 CLAUDE.local.md created — personal env filled in; references (not values) for credentials.
  • 🤖 CLAUDE.local.md gitignoredgit check-ignore CLAUDE.local.md echoes the filename.
  • 👤 Credential store chosen — Option A (1Password, default), B (vault templates), or C (credentials.md) recorded in CLAUDE.local.md.
  • 🔀 Constitution verified — a fresh agent session references the stack/conventions unprompted.
  • 🤖 C3 committed on developAGENTS.md + CLAUDE.md + .gitignore (+ .env.tpl when Option A); CLAUDE.local.md gitignored.