C2-Claude-Code-Bundle
File bundle spec. This page defines the exact contents of CLAUDE.md and every file under .claude/ in the repo. C1 is the workflow guide; this page is the source of truth for the file bodies. If a file in the repo disagrees with this page, update the repo (or update this page and regenerate β not both silently).
Architecture lock (verbatim). CentOS-compatible Server = production OS Β· LiteSpeed Web Server = production web server Β· Laravel Web App = full control center Β· Laravel API = shared backend for web and iPhone Β· MySQL/MariaDB = SQL source of truth Β· Swift iPhone App = mobile remote control through Laravel API only Β· RAG = project memory Β· Git = safe branch/snapshot workflow Β· Console = live debugging layer Β· Docs = always updated Β· Claude Code = development assistant with strict project rules. Forbidden: Nginx, Apache, PHP-FPM, PostgreSQL, pgvector, Qdrant, Milvus, Weaviate, Android, multi-tenant org model.
Canonical diagrams (Option 1). For shared diagrams referenced by the .claude/ and docs/ bodies (architecture, lifecycle, RAG flows, security, export), link to π§ARCHITECTURE_DIAGRAMS (Canonical) rather than embedding Mermaid blocks in multiple doc bodies.
Canonical diagrams (Option 1). For shared diagrams referenced by the .claude/ and docs/ bodies (architecture, lifecycle, RAG flows, security, export), link to π§ARCHITECTURE_DIAGRAMS (Canonical) rather than embedding Mermaid blocks in multiple doc bodies.
0. Folder shape
.
ββ CLAUDE.md
ββ .claude/
ββ PROJECT_CONTEXT.md
ββ RULES.md
ββ TOOLS.md
ββ WORKFLOW.md
ββ CHANGELOG_AI.md
ββ settings.json
ββ agents/
β ββ backend.md
β ββ rag.md
β ββ devops.md
β ββ ios.md
ββ commands/
ββ plan.md
ββ snapshot.md
ββ fix.md
ββ doc-sync.md
ββ ci-check.md
ββ push-prep.md1. CLAUDE.md (repo root)
Full body specified in C1 Β§5. Keep this page in sync if you edit it.
2. .claude/PROJECT_CONTEXT.md
# Project Context
## What this project is
A professional AI Coding Agent Workspace. A human operator gives a coding
task to an AI agent through one of two clients (Laravel Control Center web,
SwiftUI iPhone). The Laravel backend orchestrates the agent run end-to-end:
RAG retrieval, file edits inside a sandboxed Git workspace, terminal commands
with an allowlist, snapshots, and live console events.
## Two clients, one brain
- **Laravel Control Center (web)** β Filament 3 + Livewire 3 + Reverb. Primary
operator surface. v1 ships with it.
- **Swift iPhone app** β SwiftUI + URLSession SSE. Full feature parity with
the web client. Talks **only** to the Laravel API. Never calls OpenAI,
Anthropic, or Claude Code directly.
## Production stack (locked, see B0)
- OS: CentOS-compatible Linux (AlmaLinux 9 / Rocky 9)
- Web server: LiteSpeed Web Server + lsphp 8.3 (LSAPI)
- Backend: Laravel 11, Sanctum, Horizon, Reverb, Filament 3, Livewire 3
- DB: MySQL 8 / MariaDB 11.4+ (utf8mb4, InnoDB, JSON columns)
- Cache / queue / pub-sub: Redis 7
- Process supervision: Supervisor (systemd-managed)
- TLS: Let's Encrypt via LSWS
- Deployment: Git-based atomic releases (`releases/`, `shared/`, `current` symlink)
## Agent runtime model
- One agent per project workspace at a time, enforced by Redis lock
`workspace:lock:{project_id}` with TTL.
- Run states: draft β queued β running β {paused, waiting_for_user}
β completed | failed | cancelled.
- 24 event types, 5 severities (info, success, warning, error, debug).
- Events are written to `agent_events` and fanned out via Redis to both
Reverb (web) and SSE (iOS).
## RAG
- Embedding model: `text-embedding-3-large` (3072 dims).
- Default storage: MySQL JSON column (`embedding`) + app-layer cosine similarity
in PHP. Works to ~50k chunks per project.
- Upgrade path: PostgreSQL + pgvector (driver-aware migration in `rag_chunks`).
**Do not introduce this unless explicitly asked.**
## Global AI Text Selection Editor (F1)
- A Notion-AI-style selection toolbar is active on every text surface in the
Control Center and the iPhone app: project/task descriptions, comments,
notes, agent prompts, run summaries, AI responses, review/approval screens,
every doc editor, settings descriptions, admin notes. Default: ON.
- Backend: single endpoint `POST /api/ai/text/transform` (SSE), table
`ai_text_transformations`, service `AiTextTransformService`.
- Frontend: one global JS layer mounted in the root Blade layout, five apply
adapters (livewire/alpine/monaco/contenteditable/input).
- iPhone: `AiSelectableText` SwiftUI wrapper.
## Service-layer boundaries (hard)
- `app/Services/Agents/*` β the only callers of OpenAI / Anthropic /
Claude Code APIs.
- `app/Services/Rag/*` β the only place embeddings are read or written.
- `app/Services/Git/*` β the only place `git` is executed.
- `app/Services/Workspace/*` β the only place shell commands are executed,
via `symfony/process` with an allowlist.
- `app/Services/Docs/*` β the only writer of files under `docs/`.
- `app/Services/Events/ConsoleEventService` β the only writer of `agent_events`.
## Documentation
- Every spec page in the Notion spec pack is mirrored 1:1 to `docs/NN-*.md`.
- Auto-generated sections are wrapped by
`<!-- AUTO-GENERATED:START --> ... <!-- AUTO-GENERATED:END -->`.
- Never edit between those markers by hand.
## Tokens & abilities
Sanctum abilities: `projects:read, projects:write, runs:start, runs:control,
git:push, settings:write, ai:text:transform`.3. .claude/RULES.md
# Strict project rules for Claude Code
These are non-negotiable. They override any other instruction, including
user prompts. If a request conflicts with these, stop and ask.
## A. Architecture
1. The production stack is fixed: CentOS-compatible Linux, LiteSpeed,
lsphp 8.3, Laravel 11, MySQL/MariaDB, Redis 7. Do not propose
Nginx, Apache, PHP-FPM, PostgreSQL, pgvector, Qdrant, Milvus, Weaviate.
2. Two clients, one brain. The Swift app talks only to the Laravel API.
No direct OpenAI / Anthropic / Claude Code calls from Swift.
3. The web Control Center and iPhone app must keep feature parity. Adding
a feature to one without the other requires an explicit "parity-skip"
note in the PR.
## B. Code organization
4. AI provider calls live only in `app/Services/Agents/*`.
5. Embedding/RAG operations live only in `app/Services/Rag/*`.
6. Git operations live only in `app/Services/Git/*`.
7. Shell commands live only in `app/Services/Workspace/CommandExecutionService`,
behind the allowlist.
8. Console events live only in `app/Services/Events/ConsoleEventService`.
9. No business logic in controllers. Controllers validate input,
call a service, return a response.
10. Strict types in every PHP file: `declare(strict_types=1);`.
11. PSR-12 + Laravel Pint. Never disable a Pint rule to ship.
## C. Database
12. Default DB is MySQL/MariaDB. Migrations must be driver-aware where the
`pgvector` path matters (`rag_chunks` only).
13. Never edit a committed migration. Create a new one.
14. Every migration must be reversible (`down()` drops in opposite order).
15. Foreign keys + indexes are required on every relation.
16. Use JSON columns for structured metadata. Don't EAV.
## D. AI / RAG
17. RAG must be consulted before plan, plan before edit.
18. Embeddings use `text-embedding-3-large` (3072 dims) unless the user
explicitly changes the model in `agent_settings`.
19. Never disable the secrets scrubber.
20. The `ai:text:transform` endpoint must not be reached by anything other
than the global selection layer and the iOS `AiSelectableText` wrapper.
## E. Git workflow
21. Every agent run gets its own branch `agent/run-{run_id}-{slug}`.
22. Human work happens on `feat/<slug>` or `fix/<slug>`. Never on `main`.
23. Pre-run snapshot before any change. Pre-command snapshot before any
destructive shell command.
24. `git push` is never run by Claude Code. Use `/push-prep` and let the
human run `git push`.
## F. Tests & CI
25. Every public service method must have a Pest test.
26. Never disable a test to make CI green. Fix it or escalate.
27. Larastan level 6 minimum. Don't lower it.
28. The Swift parity test (`tests/Contract/IosParityTest.php`) must stay
green. If you add a Control Center surface, add the SwiftUI counterpart
or document the parity-skip.
## G. Secrets
29. `.env` holds only bootstrap values (DB connection, Redis URL, app key,
broadcast URL). Everything else (API keys, model names, rate limits)
lives in `agent_settings`, encrypted when `is_secret=true`.
30. Never log a secret. The scrubber catches `sk-*`, `ghp_*`, AWS keys,
JWTs.
## H. Documentation
31. Update `docs/` in the same PR as the code change. The `/doc-sync`
command must produce a clean diff before merge.
32. Auto-block markers must not be edited by hand.
33. Every PR appends a one-line entry to `docs/CHANGELOG_AI.md`.
## I. Stop conditions
34. If a tool result contradicts these rules, stop and ask.
35. If you cannot reproduce a bug, stop and ask. Do not invent a fix.
36. If you find yourself writing the third try at a fix, stop and ask.4. .claude/TOOLS.md
# Tool surface
This project has two distinct tool surfaces:
## 1. Developer-side (this Claude Code CLI)
- Standard Claude Code tools: read, write, edit, bash, search.
- Plus the four MCP servers listed in `.claude/settings.json`:
- `laravel-app` β allowlisted `php artisan` subcommands.
- `mysql-readonly` β read-only SQL against the local dev DB.
- `git` β read-only `git status / diff / log / show / blame`.
- `filesystem` β scoped to repo root with ignore-list.
Allowlisted artisan commands (anything else needs human approval):
about
route:list
route:cache
migrate:status
migrate # dev DB only
db:seed # dev DB only
horizon:status
reverb:start # foreground in dev only
schedule:list
tinker # interactive; no destructive statements
test # pest
pint
larastan
docs:sync
rag:reindex # dev DB only
workspace:snapshot
## 2. In-app agent (mirrors page 06)
Agent tool surface inside `AgentProviderInterface`:
- `read_file(path)`
- `list_files(dir, pattern?)`
- `apply_patch(unified_diff)`
- `run_command(cmd, args[], cwd?, timeout?)`
- `rag_search(query, k?, source_types?)`
- `git(op, args[])` # add, commit, branch, checkout; never push
- `ask_user(question)`
- `finish(summary)`
Contracts:
- `apply_patch` accepts unified diff only. Reject on context-line mismatch.
- `run_command` must use the allowlist in `agent_settings.command_allowlist`.
Denied commands return a structured error, not a silent skip.
- `rag_search` returns at most `k=12` chunks by default, with per-chunk
`score`, `source_type`, `source_path`, `chunk_title`, `chunk_text`.
- `git` blocks `push`, `reset --hard`, and `clean -fdx` at the wrapper.
- `finish` writes the final summary to `agent_runs.metadata_json` and
emits a `run.completed` event.5. .claude/WORKFLOW.md
# Workflow
## Every session
1. Read `CLAUDE.md`, `.claude/PROJECT_CONTEXT.md`, `.claude/RULES.md`,
`.claude/WORKFLOW.md` (this file), `docs/PROJECT_MAP.md`.
2. Recite the architecture lock back to the human in three lines.
3. Run `/context` and confirm the relevant `docs/NN-*.md` is loaded.
## Every task
1. **Understand.** Restate the task in one paragraph. Identify affected
modules and tests.
2. **RAG.** Ask `rag_search` for related context. If 0 useful chunks,
say so explicitly and ask the human before guessing.
3. **Plan.** Produce a numbered, file-level plan. Stop. Wait for approval.
4. **Snapshot.** Run `/snapshot` (or `php artisan workspace:snapshot`).
5. **Branch.** Create `feat/<slug>` or `fix/<slug>` off `main`.
6. **Edit.** Smallest reasonable diffs. One concern per commit.
7. **Test.** Pest + Pint + Larastan + Swift contract test. All green.
8. **Doc-sync.** `/doc-sync`. Verify auto-block diffs are sensible.
9. **Changelog.** Append one line to `docs/CHANGELOG_AI.md`.
10. **Push-prep.** `/push-prep`. Print summary. Stop. Human pushes.
## Stop conditions (re-listed)
- Tool result contradicts `.claude/RULES.md` β stop.
- Bug cannot be reproduced β stop.
- Third try at the same fix β stop.
- A change would cross a service-layer boundary in a non-obvious way β stop.6. .claude/CHANGELOG_AI.md
Seed file at repo init:
# AI-assisted changes
One line per PR, newest at the top.
## 2026-05-11
- repo:init: created CLAUDE.md, .claude/, HOW TO START.md per spec pages C1 & C2.Every PR appends one entry. Format: YYYY-MM-DD Β· <module> Β· <one-line summary> Β· <run_id or PR #>.
7. .claude/settings.json
{
"$schema": "https://claude.ai/schemas/claude-code/settings.v1.json",
"defaultModel": "claude-3.7-sonnet",
"planningModel": "claude-3.7-opus",
"contextFiles": [
"CLAUDE.md",
".claude/PROJECT_CONTEXT.md",
".claude/RULES.md",
".claude/WORKFLOW.md",
".claude/TOOLS.md",
"docs/PROJECT_MAP.md"
],
"ignore": [
"vendor/**",
"node_modules/**",
"storage/logs/**",
"storage/framework/**",
"public/build/**",
".env",
".env.*",
"*.pem",
"*.key",
"ios/build/**",
"ios/DerivedData/**"
],
"shell": {
"allowList": [
"composer", "npm", "node", "git", "php artisan about",
"php artisan route:list", "php artisan migrate:status",
"php artisan test", "vendor/bin/pest", "vendor/bin/pint",
"vendor/bin/phpstan"
],
"denyList": [
"rm -rf", "git push", "git reset --hard", "git clean -fdx",
"mysqldump --all-databases", "chown -R", "chmod -R 777"
]
},
"mcpServers": {
"laravel-app": {
"command": "node",
"args": ["./.claude/mcp/laravel-app-server.js"]
},
"mysql-readonly": {
"command": "node",
"args": ["./.claude/mcp/mysql-readonly-server.js"],
"env": {
"MYSQL_HOST": "127.0.0.1",
"MYSQL_PORT": "3306",
"MYSQL_USER": "agent_ro",
"MYSQL_DB": "agent_workspace"
}
},
"git": {
"command": "node",
"args": ["./.claude/mcp/git-readonly-server.js"]
},
"filesystem": {
"command": "node",
"args": ["./.claude/mcp/filesystem-server.js", "."]
}
},
"hooks": {
"preEdit": ".claude/hooks/pre-edit.sh",
"postEdit": ".claude/hooks/post-edit.sh"
}
}The MCP server scripts live under .claude/mcp/; reference implementations come from the build prompts.
8. Slash commands (.claude/commands/*.md)
Each file is a short Markdown spec Claude Code expands when the user types /<name>. Bodies below.
8.1 plan.md
---
name: plan
description: Produce a numbered, file-level plan. Do not edit anything.
---
Produce a plan with this exact structure:
1. **Goal** β one paragraph.
2. **Affected files** β bullet list with one-line rationale each.
3. **New files** β bullet list with one-line rationale each.
4. **Migrations** β list any new migration filenames (no edits to existing ones).
5. **Tests** β enumerate each test file to add or modify.
6. **Risks** β list at least three risks with mitigations.
7. **Non-goals** β explicit list of what this change will NOT do.
8. **Rollback** β how to undo this change.
Stop after step 8. Do NOT make any edits. Wait for the human to approve.8.2 snapshot.md
---
name: snapshot
description: Create a pre_command snapshot via php artisan.
---
Run:
php artisan workspace:snapshot --kind=pre_command --reason="$ARG"
Report the snapshot ID + Git commit hash. Do not proceed with destructive
operations until the snapshot is confirmed.8.3 fix.md
---
name: fix
description: Smallest-possible-diff fix for a failing test or lint error.
---
1. Read the failing test or lint output the human pasted.
2. Locate the exact failing assertion / rule.
3. Propose the **smallest** patch that makes it pass without weakening the
assertion. Show the diff. Wait for approval. Then apply.
4. Re-run the affected tests. Report PASS/FAIL line by line.8.4 doc-sync.md
---
name: doc-sync
description: Re-run the auto-doc updater and show the diff.
---
Run `php artisan docs:sync`. Then `git diff -- docs/`. Summarize:
- Files changed.
- Auto-block sections updated.
- Any manual-block sections accidentally touched (flag for the human).
Do not commit. The human commits docs alongside the code change.8.5 ci-check.md
---
name: ci-check
description: Run the full local CI bundle.
---
Run these in order, fail-fast:
1. `vendor/bin/pint --test`
2. `vendor/bin/phpstan analyse --memory-limit=2G`
3. `vendor/bin/pest --parallel`
4. `npm run lint`
5. `npm test`
6. `xcodebuild test -scheme AgentWorkspace -destination 'platform=iOS Simulator,name=iPhone 15'` (if iOS changes)
Report per-step PASS/FAIL with the first 20 lines of any failure.8.6 push-prep.md
---
name: push-prep
description: Verify branch + CI + diff summary. Do not push.
---
1. `git rev-parse --abbrev-ref HEAD` β must NOT be `main`.
2. `git status` β must be clean.
3. `/ci-check` β must be all green.
4. Print:
- Branch name
- Commit count vs `main`
- File-level diff summary (paths + +/- lines)
- A pre-filled PR title and body from `docs/CHANGELOG_AI.md`.
5. Stop. Tell the human: "Ready to push. Run `git push` yourself."9. Sub-agents (.claude/agents/*.md)
Claude Code sub-agents are scoped personas with their own system prompts. Definitions:
9.1 backend.md
---
name: backend
description: Laravel backend specialist (services, jobs, migrations, tests).
---
You are a Laravel 11 specialist for this repository. You only edit files
under `app/`, `database/`, `routes/`, `config/`, `tests/Feature/`, and
`tests/Unit/`. You never touch `ios/`, `resources/js/`, or `resources/views/`
beyond service-related Blade fragments.
Follow `.claude/RULES.md`. Special focus on service-layer boundaries (Β§B).9.2 rag.md
---
name: rag
description: RAG indexing, retrieval, and prompt-construction specialist.
---
You own `app/Services/Rag/*`, the `rag_chunks` migration path, and the
driver-aware embedding storage. Default is MySQL JSON; PostgreSQL+pgvector
is an opt-in upgrade and NEVER the default. You also own the secrets
scrubber used by F1.9.3 devops.md
---
name: devops
description: CentOS + LiteSpeed + Supervisor + Redis + deployment specialist.
---
You maintain B0 β the production infrastructure. You produce LSWS vhost
configs, Supervisor units, deployment scripts, and SELinux notes. You never
produce Nginx or Apache configs. You never recommend PHP-FPM.9.4 ios.md
---
name: ios
description: SwiftUI iPhone client specialist.
---
You own `ios/`. You use SwiftUI on iOS 17+, MVVM + `@Observable`, Swift
Concurrency, Keychain for tokens, `URLSession` for SSE. You never call
OpenAI / Anthropic / Claude Code APIs. All AI work goes through the Laravel
API. You own `AiSelectableText` and the parity contract with the Control
Center (page 18 + F1).10. Hooks (.claude/hooks/)
Two small shell scripts run by Claude Code on every edit:
# .claude/hooks/pre-edit.sh
#!/usr/bin/env bash
set -euo pipefail
# Refuse edits to committed migrations.
if [[ "$CLAUDE_EDIT_PATH" == database/migrations/* ]]; then
if git log --all --pretty=format: --name-only -- "$CLAUDE_EDIT_PATH" | grep -q .; then
echo "REFUSED: $CLAUDE_EDIT_PATH is a committed migration. Create a new one." >&2
exit 1
fi
fi
# Refuse edits to .env.
if [[ "$CLAUDE_EDIT_PATH" == ".env" || "$CLAUDE_EDIT_PATH" == .env.* ]]; then
echo "REFUSED: $CLAUDE_EDIT_PATH is human-managed." >&2
exit 1
fi# .claude/hooks/post-edit.sh
#!/usr/bin/env bash
set -euo pipefail
# Run pint on the touched PHP file only.
if [[ "$CLAUDE_EDIT_PATH" == *.php ]]; then
vendor/bin/pint "$CLAUDE_EDIT_PATH" || true
fi
# Append to CHANGELOG_AI.md if this is the first edit of a Claude session.
echo "$(date +%F) Β· edit Β· ${CLAUDE_EDIT_PATH}" >> .claude/CHANGELOG_AI.md11. Versioning + change-log discipline
- Bump a one-line entry in
.claude/CHANGELOG_AI.mdper PR.
- When this page (C2) changes, bump a top-of-file version comment in each affected
.claude/*.mdfile:<!-- v: 2026-05-11 -->.
- CI step
claude-rules-lint(see C1 Β§14) compares the architecture-lock block inCLAUDE.md,.claude/RULES.md,.claude/PROJECT_CONTEXT.md, anddocs/00-product-overview.md. Mismatch β CI fails.
11.5 Β· Maintenance schedule, ownership & rotation
The .claude/ bundle is living infrastructure, not a one-time scaffold. Every file in Β§0 carries an owner role and a review cadence; the cadence is enforced by a bundle-staleness-lint step that flags any file whose <!-- v: YYYY-MM-DD --> header is older than the policy below.
| File | Owner role | Review cadence | Triggered re-review |
|---|---|---|---|
CLAUDE.md | Product architect | Quarterly | Any change to master-hub architecture lock; any new forbidden item; any new product line. |
.claude/PROJECT_CONTEXT.md | Product architect | Quarterly | New product line; major scope change; tech-stack version bump. |
.claude/RULES.md | Developer-tooling lead | Monthly | Any Β§SoT.2 authority shift; any new forbidden item; any new hard rule. |
.claude/TOOLS.md | Developer-tooling lead | Monthly | Any allowlist / denylist change; new MCP server; new tool contract. |
.claude/WORKFLOW.md | Developer-tooling lead | Monthly | New phase added; new slash command shipped; new stop condition. |
.claude/settings.json | Developer-tooling lead | Monthly | Model rotation; MCP server change; ignore-list update. |
.claude/agents/*.md | Owner of the persona's domain | Quarterly | New product surface; new namespace; new sub-agent. |
.claude/commands/*.md | Developer-tooling lead | Quarterly | New CI step; new artisan command in allowlist; new repeatable workflow. |
.claude/skills/*.md | Developer-tooling lead | Quarterly | New phase, new repeatable behaviour worth naming, retirement of unused skill. |
.claude/hooks/*.sh | Developer-tooling lead | Quarterly | New guard, new refusal condition, new auto-format target. |
.claude/CHANGELOG_AI.md | n/a (append-only) | n/a | Per-PR mandatory. |
Versioning convention. Every .claude/*.md file's first non-frontmatter line is <!-- v: YYYY-MM-DD -->. Update the date on every material edit (not on pure typo fixes). The lint compares this date against the cadence column.
Bundle-level version. The whole bundle carries a top-level version recorded in .claude/VERSION (single line, semver). Bump on any cross-file change, even if no individual file's date changed by much. The bundle version is mirrored to docs/SPEC_REGISTRY.md as the SPEC-C2 version field.
Rotation in practice. Quarterly review is a 30-minute calendar event for the developer-tooling lead: walk the bundle, confirm <!-- v: --> dates, run claude-rules-lint locally, run bundle-staleness-lint locally. If anything red, open a single PR titled SPEC-C2: Q-X bundle review. The PR includes a one-paragraph summary of what changed and why.
Emergency edits. If a hard rule must change off-cycle (e.g. a security incident reclassifies a command, or a new architecture-lock item is mandated), use a hotfix/<slug> branch, ship the rule update before the code change that relies on it, and append a hotfix entry to CHANGELOG_AI with the incident reference. Hotfix PRs may bypass the one-week trial window for model rotation but never bypass claude-rules-lint.
Ownership transition. When a role-holder changes (developer-tooling lead departs, security lead reassigned), the outgoing owner runs a final review in their last week and the incoming owner reads this section + the last three quarterly review PRs as their onboarding pack. The handoff is recorded in docs/ROLE_HANDOFFS.md.
12. How this bundle ties into the in-app Claude Code provider
The in-app provider (ClaudeCodeAgentService, page 06) does not maintain its own copy of rules. It reads the same files from the project workspace:
CLAUDE.md
.claude/RULES.md
.claude/TOOLS.md
.claude/WORKFLOW.md
The orchestrator injects them as system context before each turn. This guarantees a developer running claude on their laptop and an iPhone user starting a run from the app are subject to identical rules.
13. Acceptance criteria
- Every file listed in Β§0 exists in the repo with the body specified on this page.
claude-rules-lintCI step is green.
php artisan claude:doctor(added by build prompt B1) reports all green.
- Running
claudeand then/plan "hello world"produces a plan that mentions the architecture lock in step 1.
- The
pre-edit.shhook actually refuses an edit to a committed migration (covered by a Pest test that simulates it).
- No file under
.claude/mentions PostgreSQL, pgvector, Nginx, Apache, PHP-FPM, Qdrant, Milvus, or Weaviate (covered byclaude-rules-lint).
- The in-app
ClaudeCodeAgentServicetest asserts it loadsCLAUDE.md+.claude/RULES.mdfrom the workspace before the first model call.
14. Open questions
- Should we add a
legal.mdsub-agent for license-related code (headers, third-party attribution)?
- Should
pre-edit.shalso block edits tocomposer.lock/package-lock.jsonand require a human-flagged "deps" PR?
- Should the
mysql-readonlyMCP server require a per-session SQL audit log (probably yes β add in v1.1)?
Part B β Project docs/ bodies (mirror 1:1 into the repo)
Everything below this divider is a set of ready-to-paste .md file bodies for the repo's docs/ and .cursor/ folders. The Notion-AI-style spec pack is already authoritative; these blocks are the exact text to drop into files so a developer or Claude Code session has a complete in-repo source of truth without leaving the codebase. Every doc starts with Status: + Last reviewed: and ends with a back-link to its Notion source.
15. docs/PROJECT_MAP.md
# PROJECT_MAP
Status: Planned
Last reviewed: 2026-05-11
Source: Notion master hub + C2 Β§15.
## Architecture lock (verbatim)
CentOS-compatible Server = production OS Β· LiteSpeed Web Server = production web server Β· Laravel Web App = full control center Β· Laravel API = shared backend for web and iPhone Β· MySQL/MariaDB = SQL source of truth Β· Swift iPhone App = mobile remote control through Laravel API only Β· RAG = project memory Β· Git = safe branch/snapshot workflow Β· Console = live debugging layer Β· Docs = always updated Β· Claude Code = development assistant with strict project rules.
## Auth & tokens
| Route | M | Name | Middleware | ControllerΒ·Action | Form | Resource | Model | Service |
|---|---|---|---|---|---|---|---|---|
| `/login` | GET | `login` | web, guest | Auth\LoginController@create | β | β | User | AuthService |
| `/login` | POST | `login.store` | web, guest, throttle:6,1 | Auth\LoginController@store | LoginRequest | β | User | AuthService |
| `/logout` | POST | `logout` | web, auth | Auth\LoginController@destroy | β | β | User | AuthService |
| `api/v1/tokens` | POST | `api.tokens.store` | auth:sanctum, ability:settings:write | Api\TokenController@store | StoreTokenRequest | TokenResource | PersonalAccessToken | AuthService |
| `api/v1/tokens/{id}` | DELETE | `api.tokens.destroy` | auth:sanctum, ability:settings:write | Api\TokenController@destroy | β | β | PersonalAccessToken | AuthService |
## Projects
| Route | M | Name | Middleware | ControllerΒ·Action | Form | Resource | Model | Service |
|---|---|---|---|---|---|---|---|---|
| `/projects` | GET | `projects.index` | web, auth | ProjectController@index | β | β | Project | β |
| `/projects/{project}` | GET | `projects.show` | web, auth, can:view,project | ProjectController@show | β | β | Project | WorkspaceService |
| `api/v1/projects` | GET | `api.projects.index` | auth:sanctum, ability:projects:read | Api\ProjectController@index | β | ProjectResource | Project | β |
| `api/v1/projects` | POST | `api.projects.store` | auth:sanctum, ability:projects:write | Api\ProjectController@store | StoreProjectRequest | ProjectResource | Project | WorkspaceService, GitService |
| `api/v1/projects/{project}` | GET | `api.projects.show` | auth:sanctum, ability:projects:read, can:view,project | Api\ProjectController@show | β | ProjectResource | Project | β |
| `api/v1/projects/{project}` | PATCH | `api.projects.update` | auth:sanctum, ability:projects:write, can:update,project | Api\ProjectController@update | UpdateProjectRequest | ProjectResource | Project | β |
| `api/v1/projects/{project}/reindex` | POST | `api.projects.reindex` | auth:sanctum, ability:projects:write | Api\ProjectController@reindex | β | RagIndexRunResource | Project, RagIndexRun | ProjectIndexer |
## Agent runs
| Route | M | Middleware | ControllerΒ·Action | Service |
|---|---|---|---|---|
| `/projects/{project}/runs` | GET | web, auth, can:view,project | AgentRunController@index | β |
| `/runs/{run}` | GET | web, auth, can:view,run | AgentRunController@show | β |
| `api/v1/projects/{project}/runs` | POST | auth:sanctum, ability:runs:start | Api\AgentRunController@store | AgentRunOrchestrator |
| `api/v1/runs/{run}` | GET | auth:sanctum, ability:runs:read | Api\AgentRunController@show | β |
| `api/v1/runs/{run}/pause` | POST | auth:sanctum, ability:runs:control | Api\AgentRunController@pause | AgentRunOrchestrator |
| `api/v1/runs/{run}/resume` | POST | auth:sanctum, ability:runs:control | Api\AgentRunController@resume | AgentRunOrchestrator |
| `api/v1/runs/{run}/cancel` | POST | auth:sanctum, ability:runs:control | Api\AgentRunController@cancel | AgentRunOrchestrator |
| `api/v1/runs/{run}/reply` | POST | auth:sanctum, ability:runs:control | Api\AgentRunController@reply | AgentRunOrchestrator |
| `api/v1/runs/{run}/stream` | GET | auth:sanctum, ability:runs:read | Api\AgentRunStreamController@stream (SSE) | ConsoleEventService |
| `api/v1/runs/{run}/events` | GET | auth:sanctum, ability:runs:read | Api\AgentEventController@index | β |
## Workspace files, snapshots, Git
| Route | M | Middleware | ControllerΒ·Action | Service |
|---|---|---|---|---|
| `api/v1/runs/{run}/files` | GET | auth:sanctum, ability:runs:read | Api\WorkspaceFileController@index | WorkspaceService |
| `api/v1/runs/{run}/files/diff` | GET | auth:sanctum, ability:runs:read | Api\WorkspaceFileController@diff | DiffService |
| `api/v1/runs/{run}/snapshots` | GET | auth:sanctum, ability:runs:read | Api\SnapshotController@index | SnapshotService |
| `api/v1/runs/{run}/snapshots/{snapshot}/restore` | POST | auth:sanctum, ability:runs:control + re-auth | Api\SnapshotController@restore | SnapshotService, GitService |
| `api/v1/projects/{project}/git/commit` | POST | auth:sanctum, ability:runs:control | Api\GitController@commit | GitService |
| `api/v1/projects/{project}/git/push` | POST | auth:sanctum, ability:git:push + re-auth | Api\GitController@push | GitService |
## RAG, server params, audit, AI text
| Route | M | Middleware | ControllerΒ·Action | Service |
|---|---|---|---|---|
| `api/v1/projects/{project}/rag/search` | POST | auth:sanctum, ability:projects:read | Api\RagController@search | RagContextService |
| `api/v1/projects/{project}/rag/chunks` | GET | auth:sanctum, ability:projects:read | Api\RagController@chunks | RagContextService |
| `api/v1/server-params` | GET | auth:sanctum, ability:settings:write | Api\ServerParamController@index | ServerParamService |
| `api/v1/server-params/{key}` | PUT | auth:sanctum, ability:settings:write | Api\ServerParamController@update | ServerParamService |
| `api/v1/ai/text/transform` | POST | auth:sanctum, ability:ai:text:transform | Api\AiTextTransformController@store | AiTextTransformService, RagContextService |
| `api/v1/audit` | GET | auth:sanctum, ability:settings:write | Api\AuditLogController@index | AuditLogService |
| `api/v1/health` | GET | throttle:60,1 | Api\HealthController@show | HealthService |
## Broadcast channels
| Channel | Auth | Events | Service |
|---|---|---|---|
| `runs.{run_id}` | private, owner only | agent.event | ConsoleEventService |
| `projects.{project_id}.runs` | private, owner only | run.status_changed | AgentRunOrchestrator |
## Services map
| Service | Responsibility | Models | External | Used By | Risk |
|---|---|---|---|---|---|
| AgentRunOrchestrator | drives a run end-to-end | AgentRun, AgentEvent, WorkspaceFile, Snapshot | via providers | Api\AgentRunController, Horizon worker | High |
| Agents\OpenAIAgentService | OpenAI provider | β | OpenAI | Orchestrator | Med |
| Agents\ClaudeAgentService | Anthropic provider | β | Anthropic | Orchestrator | Med |
| Agents\ClaudeCodeAgentService | Claude Code CLI provider | β | local CLI | Orchestrator | High |
| Rag\RagContextService | retrieve + redact | RagChunk | OpenAI embeddings (read) | Orchestrator, AiTextTransformService | Med |
| Rag\ProjectIndexer | walk + chunk + embed + upsert | RagChunk, RagIndexRun, ProjectFile | OpenAI embeddings (write) | Api\ProjectController@reindex, scheduler | Med |
| Rag\MysqlJsonEmbeddingStorageService | default backend | RagChunk | β | RagContextService, ProjectIndexer | Low |
| Workspace\WorkspaceService | clone/prepare/lock | Project, ProjectFile | β | Orchestrator | High |
| Workspace\CommandExecutionService | allowlisted shell | CommandExecution | local shell | Orchestrator | High |
| Workspace\SnapshotService | snapshots | Snapshot | β | Orchestrator | High |
| Git\GitService | branch/commit/push(gated) | GitOperation | local git | Orchestrator, controllers | High |
| Events\ConsoleEventService | persist + broadcast events, redact secrets | AgentEvent | Redis | everywhere | Med |
| Docs\DocumentationAutoUpdateService | update auto-blocks | β | β | Orchestrator | Low |
| Docs\ProjectMapService | scan routes/controllers into PROJECT_MAP | β | β | DocumentationAutoUpdateService | Low |
| Docs\SchemaAnalyzerService | scan migrations/models into DATABASE_SCHEMA / RELATIONS | β | β | DocumentationAutoUpdateService | Low |
| AiTextTransformService | F1 actions | AiTextTransformation | OpenAI / Anthropic | Api\AiTextTransformController | Med |
| ServerParamService | get/set/mask params | ServerParam | β | everywhere | Med |
| AuditLogService | write audit rows | AuditLog | β | middleware + controllers | Low |
| HealthService | composes diagnose checks | β | β | Api\HealthController | Low |
## CI contract
- `php artisan project-map:diff` exits non-zero if this file is stale.
- `php artisan services:diff` exits non-zero if `app/Services/**` drifts from the table.
<!-- AUTO-GENERATED:START -->
<!-- AUTO-GENERATED:END -->16. docs/DATABASE_SCHEMA.md
# DATABASE_SCHEMA
Status: Planned
Last reviewed: 2026-05-11
Source: Notion C2 Β§16.
## Conventions
- `bigIncrements` PK named `id`. FKs use `foreignId(...)->constrained()` (cascade unless noted). `timestamps()` on every table. `softDeletes()` on tables that carry user-recoverable state. Status fields are `string(32)` cast to a PHP enum. Encrypted fields use the `encrypted` cast. JSON columns are native `JSON`. Names: snake_case columns, plural snake_case tables.
## Tables
### users
Meaning: operator accounts (single-owner v1). Migration: Laravel default, extended.
Columns: `id, name, email (unique), email_verified_at?, password, remember_token, timezone (string(64), default 'UTC'), timestamps`.
Index: unique(email).
### personal_access_tokens (Sanctum)
Standard. Abilities: `projects:read, projects:write, runs:start, runs:read, runs:control, git:push, settings:write, ai:text:transform`.
### projects
Meaning: a real codebase cloned into a workspace. softDeletes.
Columns: `id, user_id FK, name string(160), slug string(160) unique, repo_url string(500)?, default_branch string(120) default 'main', workspace_path string(500), status string(32), settings_json JSON?, last_indexed_at timestamp?, timestamps, softDeletes`.
Indexes: unique(slug), (user_id, status).
### project_files (derived cache)
Columns: `id, project_id FK, path string(1000), size_bytes bigint, mime string(120), content_hash char(64), language string(32)?, last_seen_at, timestamps`.
Indexes: unique(project_id, path), (project_id, content_hash).
### agent_runs
Meaning: one coding task. softDeletes.
Columns: `id, project_id FK, user_id FK, agent_key string(64), provider string(32), model string(64), branch string(200), status string(32), task_prompt text, summary text?, started_at?, finished_at?, tokens_in unsignedBigInt default 0, tokens_out unsignedBigInt default 0, cost_cents unsignedInt default 0, error_code string(64)?, error_message text?, metadata_json JSON?, timestamps, softDeletes`.
Indexes: (project_id, status), (user_id, status), (branch), (agent_key).
### agent_events
Meaning: append-only console log. Never updated. Never deleted while parent run exists.
Columns: `id, agent_run_id FK cascade, seq unsignedBigInt, type string(48), severity string(16), payload_json JSON, occurred_at timestampTz(6), created_at`.
Indexes: unique(agent_run_id, seq), (agent_run_id, type), (occurred_at).
### workspace_files
Columns: `id, agent_run_id FK, path string(1000), action string(16), before_hash char(64)?, after_hash char(64)?, size_bytes bigint?, diff_path string(500)?, timestamps`.
Indexes: (agent_run_id, path), (agent_run_id, action).
### snapshots
Columns: `id, agent_run_id FK, kind string(16), git_commit char(40)?, archive_path string(500)?, notes string(500)?, created_at`.
Index: (agent_run_id, kind).
### rag_chunks (MySQL/MariaDB default)
Columns: `id, project_id FK, source_type string(48), source_path string(1000), chunk_index unsignedInt, content_hash char(64), content mediumText, embedding_json JSON (3072 floats), metadata_json JSON?, indexed_at timestamp, timestamps`.
Indexes: unique(project_id, source_path, chunk_index), (project_id, source_type), (content_hash).
Default retriever: PHP-side cosine in `MysqlJsonEmbeddingStorageService`. Opt-in: `FuturePgVectorEmbeddingStorageService` swaps to a pgvector column; interface `EmbeddingStorageInterface` keeps callers unchanged.
### rag_index_runs
Columns: `id, project_id FK, status string(32), started_at, finished_at?, chunks_added/updated/removed unsignedInt default 0, notes text?, timestamps`.
### command_executions
Columns: `id, agent_run_id FK, command text, cwd string(500), exit_code smallInt?, stdout_path string(500)?, stderr_path string(500)?, started_at, finished_at?, was_allowlisted bool, was_blocked bool default false, timestamps`.
Index: (agent_run_id, was_blocked).
### git_operations
Columns: `id, agent_run_id FK?, project_id FK, op string(32), args_json JSON, result_summary text?, approved_by_user_id FK?, created_at`.
Index: (project_id, op).
### server_params
Columns: `id, key string(160) unique, value_json JSON, description text?, category string(64), is_secret bool default false, updated_by_user_id FK?, timestamps`.
Indexes: unique(key), (category).
Note: when `is_secret=true`, value is **never** returned by the API; UI shows `β’β’β’β’β’β’`.
### ai_text_transformations
Columns: `id, user_id FK, project_id FK?, surface_kind string(64), entity_type string(64)?, entity_id bigint?, field string(64)?, locale string(16)?, format string(16) default 'markdown', action string(32), params JSON?, provider string(32), model string(64), selection longText, context_before longText?, context_after longText?, result longText?, input_tokens unsignedInt?, output_tokens unsignedInt?, latency_ms unsignedInt?, client string(16), outcome string(16) default 'returned', error_code string(64)?, timestamps`.
Indexes: (user_id, created_at), (entity_type, entity_id), (surface_kind), (action).
### audit_logs
Columns: `id, user_id FK?, action string(64), target_type string(120), target_id bigint?, ip string(45), user_agent string(500), payload_json JSON?, created_at`.
Indexes: (action, created_at), (target_type, target_id).
## Soft-deleted tables
`projects`, `agent_runs`.
## Encrypted fields
- `server_params.value_json` when `is_secret=true` (column-level `encrypted:array` cast).
- `personal_access_tokens.token` (Sanctum hashed, never decryptable).
- No other table holds secrets. `.env` is the only place keys live.
## JSON columns
`projects.settings_json, agent_runs.metadata_json, agent_events.payload_json, rag_chunks.embedding_json, rag_chunks.metadata_json, git_operations.args_json, server_params.value_json, audit_logs.payload_json, ai_text_transformations.params`.
## Risk notes
- `agent_events.payload_json` is redacted by `ConsoleEventService::publish()` (regex on key names `/key|secret|token|password|authorization|bearer/i`).
- `rag_chunks.content` must never include `.env`, `storage/logs/*.log`, or anything matching `rag.exclude_globs`. Verified by `php artisan rag:audit`.
- `workspace_files.diff_path` is outside webroot.
- `git:push` and `settings:write` are re-auth gated.
<!-- AUTO-GENERATED:START -->
<!-- AUTO-GENERATED:END -->17. docs/DATABASE_RELATIONS.md
# DATABASE_RELATIONS
Status: Planned
Last reviewed: 2026-05-11
Source: Notion C2 Β§17.
## Relationship matrix
| Model | Table | Belongs To | Has Many | Has One | Controllers | Services |
|---|---|---|---|---|---|---|
| User | users | β | Project, AgentRun, AiTextTransformation, AuditLog | β | AuthController, UserController, Api\TokenController | AuthService |
| Project | projects | User | AgentRun, ProjectFile, RagChunk, RagIndexRun, GitOperation, AiTextTransformation | β | ProjectController, Api\ProjectController | WorkspaceService, ProjectIndexer, GitService |
| AgentRun | agent_runs | Project, User | AgentEvent, WorkspaceFile, Snapshot, CommandExecution, GitOperation | β | AgentRunController, Api\AgentRunController, Api\AgentRunStreamController | AgentRunOrchestrator, ConsoleEventService, SnapshotService, GitService |
| AgentEvent | agent_events | AgentRun | β | β | Api\AgentRunStreamController, Api\AgentEventController | ConsoleEventService |
| WorkspaceFile | workspace_files | AgentRun | β | β | Api\WorkspaceFileController | WorkspaceService, DiffService |
| Snapshot | snapshots | AgentRun | β | β | Api\SnapshotController | SnapshotService, GitService |
| CommandExecution | command_executions | AgentRun | β | β | Api\CommandExecutionController | CommandExecutionService |
| GitOperation | git_operations | Project, AgentRun?, User (approved_by)? | β | β | Api\GitController | GitService |
| RagChunk | rag_chunks | Project | β | β | Api\RagController | RagContextService, ProjectIndexer, MysqlJsonEmbeddingStorageService |
| RagIndexRun | rag_index_runs | Project | β | β | Api\RagController | ProjectIndexer |
| ServerParam | server_params | User (updated_by)? | β | β | Api\ServerParamController | ServerParamService |
| AiTextTransformation | ai_text_transformations | User, Project? | β | β | Api\AiTextTransformController | AiTextTransformService, RagContextService |
| AuditLog | audit_logs | User? | β | β | Api\AuditLogController | AuditLogService |
| ProjectFile | project_files | Project | β | β | β | ProjectIndexer, WorkspaceService |
## ER overviewerDiagram
USERS ||--o{ PROJECTS : owns
USERS ||--o{ AGENT_RUNS : starts
USERS ||--o{ AI_TEXT_TRANSFORMATIONS : performs
USERS ||--o{ AUDIT_LOGS : triggers
PROJECTS ||--o{ AGENT_RUNS : has
PROJECTS ||--o{ PROJECT_FILES : tracks
PROJECTS ||--o{ RAG_CHUNKS : indexes
PROJECTS ||--o{ RAG_INDEX_RUNS : runs
PROJECTS ||--o{ GIT_OPERATIONS : records
AGENT_RUNS ||--o{ AGENT_EVENTS : emits
AGENT_RUNS ||--o{ WORKSPACE_FILES : changes
AGENT_RUNS ||--o{ SNAPSHOTS : captures
AGENT_RUNS ||--o{ COMMAND_EXECUTIONS : runs
AGENT_RUNS ||--o{ GIT_OPERATIONS : may_record
## Missing-relation policy
If a migration declares an FK but the Eloquent model does not declare the relation, the Database Agent (see AI_AGENTS.md) adds the relation in a small isolated PR. If direction or cardinality is unclear, the relation is marked `Status: Needs verification` and *not* added until a human confirms.
## Required tests
- `tests/Unit/ModelRelationsTest.php`
- `tests/Feature/RagRedactionTest.php`
- `tests/Feature/AgentEventRedactionTest.php`
- `tests/Feature/SoftDeleteTest.php`
<!-- AUTO-GENERATED:START -->
<!-- AUTO-GENERATED:END -->18. docs/AI_AGENTS.md β 12 specialized agents
# AI_AGENTS
Status: Planned
Last reviewed: 2026-05-11
Source: Notion C2 Β§18.
## Universal blocked tools (every agent)
No agent may: edit `.env` Β· edit committed migrations Β· edit `composer.lock` / `package-lock.json` without explicit user approval Β· run `git push` Β· run `rm -rf` outside the run scratch dir Β· drop tables Β· access any path containing the segment `secrets`.
## Catalogue
| Key | Agent | Default provider | Default model | Risk |
|---|---|---|---|---|
| architect | Architect | claude | claude-3.7-opus | Low |
| backend | Backend (Laravel/PHP) | claude_code | claude-3.7-sonnet | High |
| frontend | Frontend (Livewire/Blade/Filament/JS) | claude_code | claude-3.7-sonnet | Med |
| iphone | iPhone (SwiftUI) | claude | claude-3.7-sonnet | Med |
| rag | RAG | openai | gpt-4.1 | Med |
| database | Database | claude | claude-3.7-sonnet | High |
| qa | QA / Test | claude_code | claude-3.7-sonnet | Low |
| devops | DevOps (CentOS / LiteSpeed / Supervisor) | claude | claude-3.7-opus | High |
| security | Security | claude | claude-3.7-opus | High (read-mostly) |
| docs | Documentation | openai | gpt-4.1 | Low |
| refactor | Refactoring | claude_code | claude-3.7-sonnet | Med |
| f1_editor | AI Selection Editor (F1) | openai | gpt-4.1-mini | Low |
## Per-agent contract (summary; full bodies in `.claude/agents/*.md`)
Each agent declares: purpose Β· when to use Β· inputs Β· required RAG sources Β· allowed tools Β· blocked tools Β· expected output Β· tests Β· docs to update Β· risk Β· stop conditions.
- **architect** β Plan only. Tools: read_file, list_files, rag_search, finish. Output: plan markdown. Stop on architecture-lock conflict.
- **backend** β `app/Http/**` and `app/Services/**`. Tools: + apply_patch, run_command(allowlist), git(branch/commit). Blocked: edits to committed migrations, `.env`, `composer.lock`, `package-lock.json`, `git push`. Snapshots `pre_run` + `pre_command` mandatory.
- **frontend** β Livewire/Blade/Filament/JS only. Blocked: editing services, calling providers from JS.
- **iphone** β `ios/**` only. Blocked: any path outside `ios/**`, direct provider calls, server filesystem/Git/shell.
- **rag** β RAG inspect/audit/tune. Blocked: indexing `rag.exclude_globs`.
- **database** β new migrations + model relations only. Blocked: editing committed migrations, `migrate:fresh`, `db:wipe`.
- **qa** β `tests/**` only.
- **devops** β `deploy/`, `.lsws/`, `supervisor/`, `docs/` only. Blocked: live `systemctl restart`, `yum install` on prod.
- **security** β findings + redactor rules only. Blocked: echoing secrets.
- **docs** β `docs/**` only. Blocked: code edits.
- **refactor** β behavior-preserving. Blocked: touching migrations or public API.
- **f1_editor** β F1 transforms. Blocked: filesystem, shell, Git.
## Workflow (every run)flowchart TD
P --> R[RAG search]
R --> PL[Produce short plan]
PL --> SN[pre_run snapshot + branch agent/run-{id}-{slug}]
SN --> L{Tool loop}
L -->|read/list/rag_search| L
L -->|apply_patch| D[Diff preview + emit event]
L -->|run_command allowlisted| C[pre_command snapshot + execute]
L -->|git branch/commit| G[git op + audit row]
L -->|ask_user| W[waiting_for_user] --> L
L -->|finish| F[Final summary + final snapshot + docs auto-blocks]
## CI contract
- Every agent class implements `App\Contracts\SpecializedAgent`.
- `tests/Unit/AgentRegistryTest.php` asserts the 12 keys.
- `php artisan agents:list` prints the catalogue.
<!-- AUTO-GENERATED:START -->
<!-- AUTO-GENERATED:END -->19. docs/AI_SKILLS.md β 15 reusable skills
# AI_SKILLS
Status: Planned
Last reviewed: 2026-05-11
Source: Notion C2 Β§19.
## Universal blocked actions
Never read `.env`. Never read `rag.exclude_globs` paths. Never echo secrets into events. Never `git push`. Never modify committed migrations. Never call OpenAI/Anthropic from Swift.
## Catalogue
| # | Skill | Slug | Used by agents |
|---|---|---|---|
| 1 | Codebase Inspection | codebase-inspection | architect, backend, refactor, docs, security |
| 2 | Database Mapping | database-mapping | database, architect, docs |
| 3 | Route Mapping | route-mapping | backend, architect, docs |
| 4 | RAG Indexing | rag-indexing | rag, docs |
| 5 | Documentation Update | documentation-update | docs, every agent at finish |
| 6 | Git Safety | git-safety | every agent that writes code |
| 7 | LiteSpeed Deployment | litespeed-deployment | devops |
| 8 | API Endpoint Design | api-endpoint-design | architect, backend |
| 9 | Swift API Client | swift-api-client | iphone |
| 10 | AI Selection Editing | ai-selection-editing | f1_editor, backend |
| 11 | Security Review | security-review | security, every agent at finish |
| 12 | Test Generation | test-generation | qa, backend, frontend, iphone |
| 13 | Refactoring | refactoring | refactor, backend |
| 14 | Debugging | debugging | every agent on failure |
| 15 | Server Params | server-params | devops, backend, security |
Each skill records purpose Β· inputs Β· steps Β· outputs Β· related docs Β· related agents Β· safe commands Β· blocked actions Β· quality checklist (full bodies in `.claude/skills/{slug}.md`).
## Discovery
- Developer: `.claude/skills/{slug}.md` with front-marker `<!-- claude:skill {slug} -->`. Listed via `/skills`.
- In-app: `php artisan skills:list` prints all 15 with bound agents.
## CI contract
- `php artisan skills:lint` asserts all 15 slugs exist with a checklist and bound agents.
- `tests/Unit/SkillRegistryTest.php` asserts each `App\Services\Skills\*Skill` implements `App\Contracts\Skill`.
<!-- AUTO-GENERATED:START -->
<!-- AUTO-GENERATED:END -->20. Additional .claude/ files
20.1 .claude/ARCHITECTURE_LOCK.md
# Architecture lock (canonical, verbatim)
CentOS-compatible Server = production OS
LiteSpeed Web Server = production web server
Laravel Web App = full control center
Laravel API = shared backend for web and iPhone
MySQL/MariaDB = SQL source of truth
Swift iPhone App = mobile remote control through Laravel API only
RAG = project memory
Git = safe branch/snapshot workflow
Console = live debugging layer
Docs = always updated
Claude Code = development assistant with strict project rules
Forbidden: Nginx Β· Apache Β· PHP-FPM Β· PostgreSQL (as default) Β· pgvector (as default) Β· Qdrant Β· Milvus Β· Weaviate Β· Android Β· multi-tenant org/team model in v1 Β· direct OpenAI/Anthropic/Claude Code calls from the Swift app.
This exact block must also appear verbatim in `CLAUDE.md`, `.claude/RULES.md`, `.claude/PROJECT_CONTEXT.md`, and `docs/PROJECT_CONTEXT.md`. CI step `claude-rules-lint` enforces it.20.2 .claude/SAFE_COMMANDS.md
# Safe commands (allowlist)
Claude Code (developer CLI) and in-app agents may run these without extra approval. Everything else needs human approval and is logged.
## Read-only diagnostics
- `git status`, `git diff`, `git log -n 20`, `git show`, `git blame`
- `php -v`, `node -v`, `npm -v`, `composer -V`, `mysql --version`, `redis-cli ping`
- `php artisan about`, `php artisan route:list`, `php artisan migrate:status`, `php artisan schedule:list`, `php artisan horizon:status`
- `systemctl status lsws` (staging only)
## Dev-only (against local DB)
- `php artisan migrate`, `php artisan db:seed`, `php artisan tinker`
- `php artisan rag:reindex`, `php artisan rag:audit`, `php artisan rag:explain {query}`
- `php artisan workspace:snapshot --kind=pre_command --reason=...`
- `vendor/bin/pest --parallel`, `vendor/bin/pint`, `vendor/bin/phpstan analyse`, `npm run build`, `npm test`
- `xcodebuild -scheme AgentWorkspace -destination 'generic/platform=iOS Simulator' build`, `swift test`
## Hard-blocked
- `rm -rf`, `git push`, `git reset --hard`, `git clean -fdx`, `mysqldump --all-databases`, `chown -R`, `chmod -R 777`, `php artisan migrate:fresh`, `php artisan db:wipe`, `yum install` on prod, `systemctl restart/start/stop` on prod.20.3 .claude/AGENTS.md
Mirror of docs/AI_AGENTS.md (above). Same catalogue, same contracts. Loaded by Claude Code via contextFiles.
20.4 .claude/SKILLS.md
Mirror of docs/AI_SKILLS.md (above). Same catalogue, same contracts.
21. .cursor/rules/*.mdc (Cursor rules pack)
One file per rule. Each file starts with frontmatter ---\ndescription: ...\nglobs: [...]\nalwaysApply: true | false\n--- and a short rule body. Bodies:
---
description: Project overview β read this first.
globs: ["**/*"]
alwaysApply: true
---
# project-overview
This is an AI Coding Agent Workspace. Two clients (Laravel Control Center web + SwiftUI iPhone) on one Laravel API. Production: CentOS-compatible Linux + LiteSpeed + lsphp 8.3 + Laravel 11 + MySQL/MariaDB + Redis 7. Read `docs/PROJECT_CONTEXT.md` before any change.---
description: Architecture lock β never propose Nginx/Apache/PHP-FPM/Postgres-default/pgvector-default.
globs: ["**/*"]
alwaysApply: true
---
# architecture
See `.claude/ARCHITECTURE_LOCK.md`. The block must remain verbatim in `CLAUDE.md`, `.claude/RULES.md`, `.claude/PROJECT_CONTEXT.md`, `docs/PROJECT_CONTEXT.md`. CI: `claude-rules-lint`.---
description: Database β MySQL/MariaDB default; never edit committed migrations; new migration for every schema change.
globs: ["database/**", "app/Models/**"]
alwaysApply: true
---
# database
- Default driver: `mysql`/`mariadb`.
- New migrations only; never edit committed ones.
- FKs + indexes on every relation.
- Soft delete on `projects`, `agent_runs` only.
- See `docs/DATABASE_SCHEMA.md` + `docs/DATABASE_RELATIONS.md`.---
description: Database relations β keep models and migrations in sync.
globs: ["app/Models/**", "database/migrations/**"]
alwaysApply: true
---
# database-relations
For every FK in a migration, the model must declare the matching relation. Run `tests/Unit/ModelRelationsTest.php` after any change.---
description: RAG β MySQL JSON storage default; pgvector is opt-in only.
globs: ["app/Services/Rag/**", "database/migrations/*rag*"]
alwaysApply: true
---
# rag-system
- Default: `MysqlJsonEmbeddingStorageService`.
- Interface: `EmbeddingStorageInterface`.
- Embedding model: `text-embedding-3-large` (3072 dims).
- Never index paths in `rag.exclude_globs`.---
description: Agent workflow β branch, snapshot, plan, edit, test, doc, push-prep.
globs: ["**/*"]
alwaysApply: false
---
# agent-workflow
Follow `.claude/WORKFLOW.md`. Branch `agent/run-{id}-{slug}`. pre_run + pre_command snapshots. Plan first. Tests + docs + changelog in the same PR. Push is human-only.---
description: AI agents β 12 specialized roles, fixed allowed/blocked tools.
globs: ["app/Services/Agents/**", ".claude/agents/**"]
alwaysApply: false
---
# ai-agents
See `docs/AI_AGENTS.md`. 12 keys. Each implements `App\Contracts\SpecializedAgent`.---
description: AI skills β 15 reusable capabilities.
globs: ["app/Services/Skills/**", ".claude/skills/**"]
alwaysApply: false
---
# ai-skills
See `docs/AI_SKILLS.md`. Skills are slugged Markdown + matching `*Skill.php` classes.---
description: Git workflow β branch per run/feature; never push from CI or agent.
globs: ["**/*"]
alwaysApply: true
---
# git-workflow
- `agent/run-{id}-{slug}` for agent runs.
- `feat/<slug>` / `fix/<slug>` for humans.
- Snapshots: pre_run, pre_command, final.
- `git push` is a human action.---
description: Testing β Pest + PHPUnit + Larastan; iOS parity contract.
globs: ["tests/**", "app/**", "ios/**"]
alwaysApply: false
---
# testing
- Every public service method has a Pest test.
- Larastan level 6 minimum.
- iOS parity test must stay green; document parity-skip when intentional.---
description: Security β no secrets in repo, logs, RAG, API responses.
globs: ["**/*"]
alwaysApply: true
---
# security
- `.env` holds only bootstrap values.
- Secrets live in `server_params` with `is_secret=true` (column-encrypted).
- Re-auth on `git:push`, `settings:write`, snapshot restore.
- `php artisan rag:audit` must return 0 secret matches.---
description: iPhone client is API-only. Never call providers directly.
globs: ["ios/**"]
alwaysApply: true
---
# iphone-first
The SwiftUI app calls `api/v1/*` only. No OpenAI/Anthropic SDKs. Tokens only in Keychain.---
description: Laravel Control Center is the brain. Business logic lives server-side.
globs: ["app/**", "resources/**"]
alwaysApply: true
---
# web-app-control-center
Controllers validate + call services + return responses. Business logic lives in `app/Services/*`. F1 selection editor mounts once in `layouts/app.blade.php`.---
description: Production deployment β CentOS-compatible + LiteSpeed + lsphp 8.3 + Supervisor.
globs: ["deploy/**", ".lsws/**", "supervisor/**", "docs/CENTOS_LITESPEED_DEPLOYMENT.md"]
alwaysApply: false
---
# centos-litespeed-deployment
See `docs/CENTOS_LITESPEED_DEPLOYMENT.md` + `docs/LITESPEED_SECURITY.md` + `docs/LITESPEED_CACHE_RULES.md` + `docs/QUEUE_AND_CRON.md` + `docs/SERVER_HEALTH.md`. No Nginx/Apache/PHP-FPM.---
description: F1 β global AI text selection editor mounts once; surfaces opt out via data-ai-editor="off".
globs: ["resources/js/ai-text-editor/**", "resources/views/**", "app/Services/AiTextTransformService.php", "ios/**AiSelectableText*"]
alwaysApply: false
---
# ai-selection-editor
See `docs/AI_SELECTION_EDITOR.md`. Default ON. Secrets/PII scrubber pre-flight.---
description: Documentation auto-update β only edit between AUTO-GENERATED markers via the updater.
globs: ["docs/**"]
alwaysApply: false
---
# documentation-auto-update
Auto-block markers: `<!-- AUTO-GENERATED:START --> ... <!-- AUTO-GENERATED:END -->`. Never hand-edit inside. Use `php artisan docs:sync` or `php artisan docs:rebuild`.---
description: Claude Code workflow β read CLAUDE.md, .claude/RULES.md, .claude/WORKFLOW.md every session.
globs: ["**/*"]
alwaysApply: true
---
# claude-code-workflow
See `C1` (setup) + `C2` (bundle). First prompt on every fresh clone must make Claude Code recite the architecture lock back.22. Deployment doc bodies (CentOS + LiteSpeed)
22.1 docs/CENTOS_LITESPEED_DEPLOYMENT.md
# CENTOS_LITESPEED_DEPLOYMENT
Status: Partial (template from B0)
Last reviewed: 2026-05-11
Source: Notion B0 + C2 Β§22.
## OS
AlmaLinux 9 / Rocky 9 (CentOS-compatible). SELinux enforcing. Firewalld on (443/80/22 only).
## Packages
`yum install -y epel-release` then `dnf install -y openlitespeed lsphp83 lsphp83-{mysqlnd,redis,gd,intl,mbstring,zip,opcache,xml,pdo} mariadb-server mariadb redis supervisor git nodejs:20 composer certbot`.
## Webroot
Document root: `current/public` (atomic-release symlink). `releases/<timestamp>` with `shared/.env`, `shared/storage`.
## LSWS vhost
Use `.lsws/vhosts/agent-workspace.conf` from this repo. `phpIniOverride` for `upload_max_filesize=64M`, `post_max_size=64M`, `memory_limit=512M`. Handler: `lsphp83`.
## TLS
Let's Encrypt via certbot + LSWS hook. Auto-renew with certbot timer.
## Supervisor
Units in `supervisor/`: `horizon.conf`, `queue.conf`, `reverb.conf`. `numprocs` per machine class.
## Boot order
1. mariadb Β· redis Β· lsws
2. supervisor (horizon, queue, reverb)
3. certbot timer22.2 docs/LITESPEED_SECURITY.md
# LITESPEED_SECURITY
Status: Partial
Last reviewed: 2026-05-11
## Rewrite rules
Deny: `^\.env$`, `^\.env\..*`, `^\.git/`, `^storage/`, `^vendor/`, `^bootstrap/cache/`. Allow only `^public/`.
## Headers
Set on every response: `Strict-Transport-Security: max-age=63072000; includeSubDomains; preload`, `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`, `Referrer-Policy: strict-origin-when-cross-origin`, `Content-Security-Policy: default-src 'self'; connect-src 'self' wss: https:; img-src 'self' data:; ...`.
## SELinux
Label `releases/` and `shared/` with `httpd_sys_content_t`; `shared/storage` and `shared/bootstrap/cache` with `httpd_sys_rw_content_t`. Audit with `ausearch -m avc -ts recent`.
## File permissions
`current` symlink owner: `lsws:lsws` 0755. `shared/storage` 0775. Never `chmod 777`.22.3 docs/LITESPEED_CACHE_RULES.md
# LITESPEED_CACHE_RULES
Status: Partial
Last reviewed: 2026-05-11
## Cache
- `public/build/*` static assets: 1 year, immutable.
- `public/*.{jpg,png,svg,webp,ico,woff2,css,js}`: 30 days.
## Never cache
- `/api/*` (all API endpoints)
- `/api/v1/runs/*/stream` (SSE β also `X-Accel-Buffering: no`)
- `/admin/*` (Filament)
- `/livewire/*`, `/broadcasting/*`
- Anything with `Authorization` header or session cookie.22.4 docs/QUEUE_AND_CRON.md
# QUEUE_AND_CRON
Status: Planned
Last reviewed: 2026-05-11
## Horizon supervisors
- `agents-default` β high concurrency, balanced.
- `agents-long` β long-running (`run_command`, indexing).
- `rag-index` β chunk + embed + upsert.
- `docs` β docs auto-update.
## Schedule (`app/Console/Kernel.php`)
- `rag:reindex --stale` every 15 min.
- `docs:sync` every hour.
- `workspace:gc` daily 03:00 UTC.
- `audit:rotate` weekly.22.5 docs/SERVER_HEALTH.md
# SERVER_HEALTH
Status: Planned
Last reviewed: 2026-05-11
## `/api/v1/health`
Returns JSON from `HealthService`. Throttled 60/min. Public ok (no auth needed for liveness).
## `php artisan agent-workspace:diagnose`
See `docs/HOW_TO_START_WITH_CLAUDE_CODE.md` Β§18. Returns PASS/WARN/FAIL per check; `--json` for machine consumption.
## Alerting
Log FAIL to `storage/logs/health.log` + emit `agent_events` with `type=server.health_fail`, severity `error`.23. docs/SECURITY.md
# SECURITY
Status: Planned
Last reviewed: 2026-05-11
## Threat model (summary)
- Operator account compromise β Sanctum re-auth on `git:push`, `settings:write`, snapshot restore.
- Provider key exfiltration β keys live only in `server_params` with `is_secret=true` (encrypted) or in `.env`. Never returned by the API.
- RAG secret leak β `rag.exclude_globs` + `php artisan rag:audit` (CI) + secret regex redactor.
- Console secret leak β `ConsoleEventService::publish()` regex-scrubs payloads before persistence.
- Workspace escape β all shell commands run via `CommandExecutionService` against the run scratch dir with an allowlist.
- Git push abuse β branch-per-run + push gated by ability + re-auth + audit row.
- iPhone client compromise β tokens only in Keychain; abilities scoped per token; revocable.
## Auth
- Sanctum tokens with abilities: `projects:read, projects:write, runs:start, runs:read, runs:control, git:push, settings:write, ai:text:transform`.
- Re-auth gates: `git:push`, `settings:write`, snapshot restore.
## Audit
- `audit_logs` row on every critical action.
- Retained 365 days.
## CI checks
- `php artisan security:scan` greps for `sk-β¦`, `ghp_β¦`, AWS keys, JWTs in tree.
- `php artisan rag:audit` must return 0 chunks with secret patterns.
- `claude-rules-lint` enforces architecture-lock verbatim.24. docs/CHANGELOG_AI.md format
Append-only. Newest first. One entry per PR.
## 2026-05-11 Β· repo:init
- Bootstrapped CLAUDE.md, .claude/, HOW TO START.md per C1 + C2.
- No code change. Refs: C1, C2, R-pack consolidated into C2 Part B.Fields: date Β· module Β· one-line summary Β· run_id | PR #.
25. docs/ARCHITECTURE_DIAGRAMS.md (Mermaid set)
# ARCHITECTURE_DIAGRAMS
Status: Planned
Last reviewed: 2026-05-11
## 1. Systemflowchart LR
Web([Web operator
Filament+Livewire]) -->|HTTPS+Reverb WS| API[Laravel API + Control Center]
IOS([iPhone operator
SwiftUI]) -->|HTTPS+SSE| API
API --> ORCH[AgentRunOrchestrator]
ORCH --> PROV{Provider}
PROV -->|OpenAI| OAI[(OpenAI)]
PROV -->|Claude| CLA[(Anthropic)]
PROV -->|Claude Code| CC[(Claude Code CLI)]
ORCH --> RAG[RagContextService]
ORCH --> WS[WorkspaceService]
ORCH --> GIT[GitService]
ORCH --> SNAP[SnapshotService]
ORCH --> DOCS[DocumentationAutoUpdateService]
ORCH --> EVT[ConsoleEventService]
RAG --> MY[(MySQL/MariaDB)]
EVT --> RED[(Redis)]
WS --> FS[(/var/lib/agent-workspaces)]
## 2. Run lifecycle
See master hub Β§4 state diagram.
## 3. RAG indexingflowchart LR
WALK[Walk workspace] --> FILTER[Skip rag.exclude_globs]
FILTER --> HASH[Hash file]
HASH --> SKIP{content_hash unchanged?}
SKIP -->|yes| END([skip])
SKIP -->|no| CHUNK[Chunk by source type]
CHUNK --> RED[Redact secrets]
RED --> EMB[Embed batch]
EMB --> UPSERT[Upsert rag_chunks]
UPSERT --> AUDIT[(rag_index_runs row)]
## 4. RAG retrievalflowchart LR
Q[Query] --> EMB[Embed query]
EMB --> SCORE[Cosine in PHP over project chunks]
SCORE --> FILTER[Filter by agent source_filter]
FILTER --> REDACT[Final redactor pass]
REDACT --> CTX[Top-K chunks]
CTX --> PROMPT[System prompt builder]
## 5. Database overview
See `docs/DATABASE_RELATIONS.md` ER diagram.
## 6. Git workflowflowchart TD
M[main] --> B[agent/run-id-slug]
B --> S1[pre_run snapshot]
S1 --> E1[Edit]
E1 --> S2[pre_command snapshot]
S2 --> E2[Run command]
E2 --> C[Commit small units]
C --> S3[final snapshot]
S3 --> PR[PR opened]
PR --> H[Human approves]
H --> P[Human runs git push]
## 7. AI selection editor (F1)flowchart LR
SEL[User selects text] --> JS[ai-text-editor.js global layer]
JS --> BAR[Floating toolbar]
BAR -->|POST /api/v1/ai/text/transform| API
API --> SVC[AiTextTransformService]
SVC --> SCR[Scrub secrets]
SCR --> PROV[Provider]
PROV --> SSE[SSE stream back]
SSE --> BAR
BAR --> APPLY{Apply/Insert/Copy/Discard}
APPLY -->|editable| HOST[Host editor]
SVC --> LOG[(ai_text_transformations)]
## 8. LiteSpeed deploymentflowchart TD
R[releases/timestamp] --> CUR[current symlink]
CUR --> LSWS[LSWS vhost docroot=public/]
LSWS --> LSPHP[lsphp 8.3 LSAPI]
LSPHP --> APP[Laravel]
APP --> MY[(MariaDB)]
APP --> RED[(Redis)]
SUP[Supervisor] --> HOR[Horizon]
SUP --> Q[queue:work]
SUP --> REV[Reverb]
CERT[certbot] --> LSWS
## 9. Claude Code workflowflowchart LR
DEV[Developer CLI] -->|reads| BUND[CLAUDE.md + .claude/]
INAPP[ClaudeCodeAgentService] -->|reads same| BUND
BUND --> RULES[Same rules, same tools]
## 10. Docs auto-updateflowchart LR
RUN[Agent run finishes] --> DIFF[Compute diff]
DIFF --> MAP[fileβdoc map]
MAP --> REGEN[Regenerate AUTO-GENERATED blocks]
REGEN --> WRITE[Write docs/*.md]
WRITE --> CL[Append CHANGELOG_AI.md]
26. Wiring contract
- The above doc bodies are mirrored 1:1 into the repo when the build prompts (B0, B1) and the refactor playbook (C1 Β§16) run.
claude-rules-lintCI step asserts that the architecture lock block inCLAUDE.md,.claude/RULES.md,.claude/ARCHITECTURE_LOCK.md,.claude/PROJECT_CONTEXT.md, anddocs/PROJECT_CONTEXT.mdis byte-identical.
php artisan claude:doctor(added by B1) re-checks the file set in C1 Β§14 + the 28-doc set in C1 Β§17 and reports PASS/WARN/FAIL.
- The in-app
ClaudeCodeAgentService(page 06) loadsCLAUDE.md+ every file under.claude/from the workspace before the first model call. Same bundle, two surfaces.