feat(system): clean hook separation, AIPASS.md as project prompt (#37)
- Move general hooks (sounds, auto-fix) to Anthropic settings only - Remove duplicated hooks from project settings (don't fire from subdirs) - Project settings now permissions-only, no hooks - Identity injector: remove apps/ requirement (fixes devpulse detection) - Identity injector: support traits[] array from passport - AIPASS.md: renamed to Project Prompt, silent startup, CWD-relative paths - /memo: read passport first for identity re-injection - README: reflects actual working setup with clear Anthropic/project split Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
08988ecead
commit
4f87b5c044
+54
-62
@@ -1,83 +1,75 @@
|
||||
# .claude/ — Claude Code Configuration
|
||||
|
||||
This directory configures Claude Code for the AIPass repo. It controls hooks (prompt injection, auto-fix, recovery), custom commands, permissions, and sound effects.
|
||||
This directory configures Claude Code for the AIPass project.
|
||||
|
||||
## How It's Wired
|
||||
## Settings — Two Files, Clear Separation
|
||||
|
||||
There are **two** settings files that matter:
|
||||
| File | Name | What it does |
|
||||
|------|------|-------------|
|
||||
| `~/.claude/settings.json` | **Anthropic settings** | All hooks, sounds, statusline, plugins. Fires everywhere. |
|
||||
| `.claude/settings.json` (this dir) | **Project settings** | Permissions only. No hooks. |
|
||||
|
||||
| File | Scope | Paths |
|
||||
|------|-------|-------|
|
||||
| `.claude/settings.json` (this dir) | Project-level, checked into git | Relative paths (e.g. `.claude/hooks/...`) |
|
||||
| `~/.claude/settings.json` | Global, per-machine | Absolute paths (e.g. `/home/coder/workspace/AIPass/.claude/hooks/...`) |
|
||||
**Why this split:** Claude Code project settings only fire when launched from the repo root. We launch from branch subdirectories (`src/aipass/{name}/`), so hooks must live in Anthropic settings with absolute paths.
|
||||
|
||||
**Why both?** Project settings only fire when Claude launches from the repo root. Since citizens launch from branch subdirectories (`src/aipass/{name}/`), the global config duplicates the hook definitions with absolute paths so they always fire regardless of CWD.
|
||||
Project settings = permissions. That's it.
|
||||
|
||||
After a container rebuild, the global config needs to be restored. The project config is the source of truth (checked into git).
|
||||
## Hooks — Two Locations, No Duplication
|
||||
|
||||
### Anthropic hooks (`~/.claude/hooks/`)
|
||||
|
||||
General-purpose scripts that work on any project.
|
||||
|
||||
| Script | Event | What it does |
|
||||
|--------|-------|-------------|
|
||||
| `tool_use_sound.py` | PreToolUse | Plays keypress sound on tool calls |
|
||||
| `auto_fix_diagnostics.py` | PostToolUse | Syntax check after file edits |
|
||||
| `stop_sound.py` | Stop | Sound on stop |
|
||||
| `notification_sound.py` | Notification | Sound on notification |
|
||||
|
||||
### Project hooks (`AIPass/.claude/hooks/`)
|
||||
|
||||
AIPass-specific scripts. Referenced from Anthropic settings with absolute paths.
|
||||
|
||||
| Script | Event | What it does |
|
||||
|--------|-------|-------------|
|
||||
| `branch_prompt_loader.py` | UserPromptSubmit | Finds `.aipass/aipass_local_prompt.md` from CWD, injects branch context |
|
||||
| `identity_injector.py` | UserPromptSubmit | Reads `.trinity/passport.json`, injects role/traits/purpose |
|
||||
| `email_notification.py` | UserPromptSubmit | Checks `.ai_mail.local/inbox.json` for unread messages |
|
||||
| `pre_compact.py` | PreCompact | Saves session context before compaction |
|
||||
|
||||
The global prompt (`aipass_global_prompt.md`) is injected via a `cat` command in Anthropic settings — no script needed.
|
||||
|
||||
## What Gets Injected Every Turn
|
||||
|
||||
1. **Global Prompt** — `cat .aipass/aipass_global_prompt.md` (system context, terminology, rules)
|
||||
2. **Branch Prompt** — `branch_prompt_loader.py` (branch-specific instructions from CWD)
|
||||
3. **Identity** — `identity_injector.py` (compact passport summary: role, traits, purpose)
|
||||
4. **Email** — `email_notification.py` (notification only if unread mail exists)
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
.claude/
|
||||
├── CLAUDE.md # Project instructions for Claude (auto-loaded)
|
||||
├── settings.json # Project-level hook config + permissions
|
||||
├── hooks/ # Hook scripts (Python)
|
||||
│ ├── branch_prompt_loader.py # Injects branch-local system prompt
|
||||
│ ├── identity_injector.py # Injects citizen identity from passport
|
||||
│ ├── email_notification.py # Alerts on unread AI Mail
|
||||
│ ├── auto_fix_diagnostics.py # Auto-fixes Python/JSON issues after edits
|
||||
│ ├── pre_compact.py # Saves recovery context before compaction
|
||||
│ ├── tool_use_sound.py # Sound on tool use (needs audio device)
|
||||
│ ├── notification_sound.py # Sound on notification (needs audio device)
|
||||
│ └── stop_sound.py # Sound on stop (needs audio device)
|
||||
├── commands/ # Custom slash commands
|
||||
│ └── memo.md # /memo — triggers memory update workflow
|
||||
└── sounds/ # Audio files for sound hooks
|
||||
├── mixkit-achievement-bell-600.wav
|
||||
├── mixkit-atm-cash-machine-key-press-2841.wav
|
||||
└── mixkit-clear-announce-tones-2861.wav
|
||||
├── settings.json # Permissions only (no hooks)
|
||||
├── hooks/ # AIPass-specific hook scripts
|
||||
│ ├── branch_prompt_loader.py
|
||||
│ ├── identity_injector.py
|
||||
│ ├── email_notification.py
|
||||
│ └── pre_compact.py
|
||||
├── commands/
|
||||
│ └── memo.md # /memo — memory update workflow
|
||||
├── sounds/ # Audio files (symlinked from ~/.claude/sounds)
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## Hooks
|
||||
## Adding a New Hook
|
||||
|
||||
### UserPromptSubmit (fires on every prompt)
|
||||
|
||||
1. **Global prompt** — `cat .aipass/aipass_global_prompt.md` — injects system-wide context (terminology, commands, rules)
|
||||
2. **Branch prompt** — `branch_prompt_loader.py` — finds nearest `.aipass/branch_system_prompt.md` from CWD and injects it
|
||||
3. **Identity injector** — `identity_injector.py` — reads `.trinity/passport.json` and injects citizen identity (role, purpose, principles)
|
||||
4. **Email notification** — `email_notification.py` — checks `.ai_mail.local/inbox.json` for unread messages
|
||||
|
||||
### PostToolUse (fires after file edits)
|
||||
|
||||
5. **Auto-fix diagnostics** — `auto_fix_diagnostics.py` — runs Python syntax check and JSON validation on edited files, outputs fix suggestions
|
||||
|
||||
### PreCompact (fires before context compaction)
|
||||
|
||||
6. **Pre-compact recovery** — `pre_compact.py` — saves session context so the citizen can recover after compaction
|
||||
|
||||
### PreToolUse / Stop / Notification (sound hooks)
|
||||
|
||||
7-9. Sound effects — **require audio device** (`--device /dev/snd` in Docker run). Currently non-functional in container.
|
||||
|
||||
## Custom Commands
|
||||
|
||||
- `/memo` — Triggers memory update workflow. Reads `.trinity/` files, updates local.json, observations.json, and optionally passport.json and README.md.
|
||||
1. Create the script in `.claude/hooks/`
|
||||
2. Add one entry to `~/.claude/settings.json` (Anthropic settings) with the absolute path
|
||||
3. Done — no changes to project settings
|
||||
|
||||
## Permissions
|
||||
|
||||
Defined in `settings.json`:
|
||||
- **Denied**: `git reset`, `git rebase`, `git config`, `git push --force`, `EnterPlanMode`
|
||||
- **Default mode**: `acceptEdits`
|
||||
|
||||
## Related Files
|
||||
|
||||
- `.aipass/aipass_global_prompt.md` — The global system prompt (at repo root, NOT in .claude/)
|
||||
- `src/aipass/{name}/.aipass/branch_system_prompt.md` — Per-branch system prompts
|
||||
- `src/aipass/{name}/.trinity/passport.json` — Citizen identity (read by identity_injector)
|
||||
- `src/aipass/{name}/.ai_mail.local/inbox.json` — Mailbox (read by email_notification)
|
||||
|
||||
## After Container Rebuild
|
||||
|
||||
1. `pip install -e . --break-system-packages`
|
||||
2. Symlink CLI tools: `ln -sf ~/.local/bin/drone /usr/local/bin/drone && ln -sf ~/.local/bin/seedgo /usr/local/bin/seedgo`
|
||||
3. Copy project settings to global: restore `~/.claude/settings.json` with absolute paths (see global config format above)
|
||||
|
||||
@@ -4,9 +4,10 @@ Purpose: Update branch memory files after completing work this session.
|
||||
|
||||
## Execution
|
||||
|
||||
1. Review what was done this session (context, recent changes, key decisions)
|
||||
2. Update each file below as needed
|
||||
3. Confirm completion — list files updated
|
||||
1. Read `.trinity/passport.json` first — re-absorb your identity, role, and principles before writing memories
|
||||
2. Review what was done this session (context, recent changes, key decisions)
|
||||
3. Update each file below as needed
|
||||
4. Confirm completion — list files updated
|
||||
|
||||
## What to Update
|
||||
|
||||
|
||||
@@ -1,193 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Silent Auto-fix Hook - Runs validation and tells Claude to fix silently.
|
||||
|
||||
Runs real linters (ruff, py_compile), validates JSON, checks Python patterns.
|
||||
Outputs via additionalContext so Claude sees errors and fixes them without
|
||||
announcing.
|
||||
|
||||
Version: 1.0.0
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
EDIT_TOOLS = ["Edit", "Write", "MultiEdit", "NotebookEdit"]
|
||||
LAST_FILE_PATH = Path(__file__).parent / ".last_diagnostics_file"
|
||||
SKIP_EXTENSIONS = {".md", ".txt", ".log", ".csv", ".html"}
|
||||
|
||||
PYTHON_PATTERNS = {
|
||||
"bad_optional": {
|
||||
"pattern": ": str = None",
|
||||
"message": "Optional param should use 'str | None = None' pattern",
|
||||
},
|
||||
"open_no_encoding": {
|
||||
"pattern": "open(",
|
||||
"requires_missing": "encoding=",
|
||||
"message": "open() without encoding='utf-8'",
|
||||
},
|
||||
}
|
||||
|
||||
JSON_CORRUPTION_CHARS = ["\ufffd", "\x00"]
|
||||
|
||||
|
||||
def run_python_checks(file_path: str) -> list[str]:
|
||||
"""Run actual Python validation."""
|
||||
errors = []
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-m", "py_compile", file_path],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
errors.append(f"SYNTAX: {result.stderr.strip()}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["ruff", "check", "--select=E,F,W", "--output-format=text", file_path],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10,
|
||||
)
|
||||
if result.stdout.strip():
|
||||
for line in result.stdout.strip().split("\n")[:5]:
|
||||
errors.append(f"LINT: {line}")
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
content = Path(file_path).read_text(encoding="utf-8")
|
||||
for check in PYTHON_PATTERNS.values():
|
||||
pattern = check["pattern"]
|
||||
message = check["message"]
|
||||
requires_missing = check.get("requires_missing")
|
||||
|
||||
if requires_missing:
|
||||
if pattern in content and requires_missing not in content:
|
||||
errors.append(f"PATTERN: {message}")
|
||||
continue
|
||||
|
||||
for line in content.split("\n"):
|
||||
stripped = line.strip()
|
||||
if stripped.startswith(("#", '"', "'")):
|
||||
continue
|
||||
if f'"{pattern}' in line or f"'{pattern}" in line:
|
||||
continue
|
||||
if pattern in line:
|
||||
errors.append(f"PATTERN: {message}")
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def run_json_checks(file_path: str) -> list[str]:
|
||||
"""Run actual JSON validation."""
|
||||
errors = []
|
||||
|
||||
try:
|
||||
content = Path(file_path).read_text(encoding="utf-8")
|
||||
|
||||
for char in JSON_CORRUPTION_CHARS:
|
||||
if char in content:
|
||||
errors.append(
|
||||
f"CORRUPTION: Found corrupted character '{repr(char)}'"
|
||||
)
|
||||
break
|
||||
|
||||
try:
|
||||
json.loads(content)
|
||||
except json.JSONDecodeError as e:
|
||||
errors.append(f"JSON SYNTAX: {e.msg} at line {e.lineno}")
|
||||
|
||||
except Exception as e:
|
||||
errors.append(f"READ ERROR: {e!s}")
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def should_skip_file(file_path: str) -> bool:
|
||||
"""Check if file should be skipped."""
|
||||
if not file_path:
|
||||
return True
|
||||
ext = Path(file_path).suffix.lower()
|
||||
return ext in SKIP_EXTENSIONS
|
||||
|
||||
|
||||
def is_same_file_as_last(file_path: str) -> bool:
|
||||
"""Smart batching - skip if same file as last check."""
|
||||
try:
|
||||
if LAST_FILE_PATH.exists():
|
||||
last = LAST_FILE_PATH.read_text(encoding="utf-8").strip()
|
||||
if last == file_path:
|
||||
return True
|
||||
LAST_FILE_PATH.write_text(file_path, encoding="utf-8")
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""Main hook entry point."""
|
||||
try:
|
||||
input_data = json.load(sys.stdin)
|
||||
tool_name = input_data.get("tool_name", "")
|
||||
tool_input = input_data.get("tool_input", {})
|
||||
file_path = tool_input.get("file_path", "")
|
||||
|
||||
if tool_name not in EDIT_TOOLS:
|
||||
return
|
||||
|
||||
if should_skip_file(file_path):
|
||||
return
|
||||
|
||||
if is_same_file_as_last(file_path):
|
||||
return
|
||||
|
||||
errors = []
|
||||
file_type = ""
|
||||
|
||||
if file_path.endswith(".py"):
|
||||
file_type = "Python"
|
||||
errors = run_python_checks(file_path)
|
||||
elif file_path.endswith(".json"):
|
||||
file_type = "JSON"
|
||||
errors = run_json_checks(file_path)
|
||||
else:
|
||||
return
|
||||
|
||||
if errors:
|
||||
error_text = "\n".join(f" - {e}" for e in errors)
|
||||
context = f"""[SILENT-FIX] {file_type} errors in {Path(file_path).name}:
|
||||
{error_text}
|
||||
|
||||
Fix these issues silently. Do not announce or explain fixes - just fix and continue."""
|
||||
|
||||
output = {
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PostToolUse",
|
||||
"additionalContext": context,
|
||||
},
|
||||
"systemMessage": f"[diagnostics] {len(errors)} issue(s)",
|
||||
}
|
||||
print(json.dumps(output))
|
||||
else:
|
||||
output = {"systemMessage": "[diagnostics] ok"}
|
||||
print(json.dumps(output))
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -30,11 +30,10 @@ def find_branch_root() -> Path | None:
|
||||
|
||||
search_path = cwd
|
||||
while search_path >= repo_root:
|
||||
has_apps = (search_path / "apps").is_dir()
|
||||
has_trinity = (search_path / ".trinity").is_dir()
|
||||
has_id = list(search_path.glob("*.id.json"))
|
||||
|
||||
if has_apps and (has_trinity or has_id):
|
||||
if has_trinity or has_id:
|
||||
return search_path
|
||||
|
||||
if search_path == repo_root:
|
||||
@@ -70,8 +69,12 @@ def format_identity(data: dict) -> str:
|
||||
identity = data.get("identity", {})
|
||||
if identity.get("role"):
|
||||
lines.append(f"Role: {identity['role']}")
|
||||
if identity.get("traits"):
|
||||
lines.append(f"Traits: {identity['traits']}")
|
||||
traits = identity.get("traits") or data.get("traits")
|
||||
if traits:
|
||||
if isinstance(traits, list):
|
||||
lines.append("Traits: " + " | ".join(traits))
|
||||
else:
|
||||
lines.append(f"Traits: {traits}")
|
||||
if identity.get("purpose"):
|
||||
lines.append(f"Purpose: {identity['purpose']}")
|
||||
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Notification Hook — Plays sound when AI needs permission."""
|
||||
|
||||
import json
|
||||
import sys
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
SOUNDS_DIR = Path(__file__).parent.parent / "sounds"
|
||||
SOUND_FILE = SOUNDS_DIR / "mixkit-clear-announce-tones-2861.wav"
|
||||
|
||||
|
||||
def play_sound() -> None:
|
||||
if not SOUND_FILE.exists():
|
||||
return
|
||||
try:
|
||||
subprocess.Popen(
|
||||
["aplay", "-q", str(SOUND_FILE)],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
hook_data = json.loads(sys.stdin.read())
|
||||
if hook_data.get("hook_event_name") == "Notification":
|
||||
play_sound()
|
||||
except Exception:
|
||||
pass
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,38 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Stop Hook — Plays achievement bell when AI finishes responding."""
|
||||
|
||||
import json
|
||||
import sys
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
SOUNDS_DIR = Path(__file__).parent.parent / "sounds"
|
||||
SOUND_FILE = SOUNDS_DIR / "mixkit-achievement-bell-600.wav"
|
||||
|
||||
|
||||
def play_sound() -> None:
|
||||
if not SOUND_FILE.exists():
|
||||
return
|
||||
try:
|
||||
subprocess.Popen(
|
||||
["aplay", "-q", str(SOUND_FILE)],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
hook_data = json.loads(sys.stdin.read())
|
||||
if hook_data.get("hook_event_name") == "Stop":
|
||||
if not hook_data.get("stop_hook_active", False):
|
||||
play_sound()
|
||||
except Exception:
|
||||
pass
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,40 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Tool Use Hook — Plays key press sound when AI uses tools."""
|
||||
|
||||
import json
|
||||
import sys
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
SOUNDS_DIR = Path(__file__).parent.parent / "sounds"
|
||||
SOUND_FILE = SOUNDS_DIR / "mixkit-atm-cash-machine-key-press-2841.wav"
|
||||
|
||||
SOUND_TOOLS = ["Bash", "Edit", "MultiEdit", "Write", "Read", "Grep", "Glob"]
|
||||
|
||||
|
||||
def play_sound() -> None:
|
||||
if not SOUND_FILE.exists():
|
||||
return
|
||||
try:
|
||||
subprocess.Popen(
|
||||
["aplay", "-q", str(SOUND_FILE)],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
hook_data = json.loads(sys.stdin.read())
|
||||
if hook_data.get("hook_event_name") == "PreToolUse":
|
||||
if hook_data.get("tool_name", "") in SOUND_TOOLS:
|
||||
play_sound()
|
||||
except Exception:
|
||||
pass
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+1
-59
@@ -10,63 +10,5 @@
|
||||
],
|
||||
"defaultMode": "acceptEdits"
|
||||
},
|
||||
"skipDangerousModePermissionPrompt": true,
|
||||
"hooks": {
|
||||
"UserPromptSubmit": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "/home/patrick/.claude/hook_logger.sh global_prompt && cat /home/patrick/Projects/AIPass/.aipass/aipass_global_prompt.md 2>/dev/null || true"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "/home/patrick/.claude/hook_logger.sh branch_prompt && python3 /home/patrick/Projects/AIPass/.claude/hooks/branch_prompt_loader.py"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "/home/patrick/.claude/hook_logger.sh identity && python3 /home/patrick/Projects/AIPass/.claude/hooks/identity_injector.py"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "/home/patrick/.claude/hook_logger.sh email_check && python3 /home/patrick/Projects/AIPass/.claude/hooks/email_notification.py"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PreCompact": [
|
||||
{
|
||||
"matcher": "manual",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "/home/patrick/.claude/hook_logger.sh pre_compact && python3 /home/patrick/Projects/AIPass/.claude/hooks/pre_compact.py",
|
||||
"timeout": 60
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "auto",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "/home/patrick/.claude/hook_logger.sh pre_compact && python3 /home/patrick/Projects/AIPass/.claude/hooks/pre_compact.py",
|
||||
"timeout": 60
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
"skipDangerousModePermissionPrompt": true
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
/home/patrick/Projects/AIPass/CLAUDE.md
|
||||
# AIPass — Project Prompt
|
||||
|
||||
# AIPass — System Prompt
|
||||
|
||||
Injected every turn. Keep this lean — details live in README, --help, and .trinity/ memories.
|
||||
Project-level instructions loaded by Claude Code. Persists in context for the entire conversation.
|
||||
Details live in README, --help, .trinity/ memories, and the Global Prompt (.aipass/aipass_global_prompt.md).
|
||||
|
||||
## Startup
|
||||
|
||||
Greetings (`hi`, `hello`, `yo`, `hey`, `sup`, `good morning`, `good evening`, `what's up`) trigger startup protocol. Everything else is a direct task.
|
||||
On any greeting, silently read these files from CWD and run the commands — no narration, no announcing steps. Just do it and respond with the status.
|
||||
|
||||
**On startup, read:** `.trinity/passport.json`, `local.json`, `observations.json`, `DASHBOARD.local.json`, `dev.local.md`, `README..md`
|
||||
**Then run:** `git status`, `drone systems`
|
||||
**Read:** `.trinity/passport.json`, `.trinity/local.json`, `.trinity/observations.json`, `STATUS.local.md`, `README.md`
|
||||
**Run:** `git status`, `drone systems`
|
||||
|
||||
## Navigation
|
||||
|
||||
|
||||
Reference in New Issue
Block a user