## Phase 0(文件層,全部 Accepted) - ADR-106/107:AwoooP 平台架構 + 儲存策略 - ADR-111~118:Bootstrap → RLS 七項核心 ADR - ADR-119~124:SAGA → Singleton Decomposition 六項 ADR - ADR-UI-01~04:Operator Console 四個 UI ADR ## Phase 1(DB schema + migration) - awooop_phase1_control_plane_2026-05-04.sql:7 張新表 + trigger + RLS - Step 1:三角色(platform_admin/migration BYPASSRLS,awooop_app 受 RLS) - Step 13:GRANT awooop_app 最小權限(7 條) - Step 14:RLS fail-closed,移除 __platform__ 後門 - awooop_phase1_batch1_rls_2026-05-04.sql:高流量四表三步式 ADD COLUMN - awooop_phase1_batch1_backfill.py:SKIP LOCKED 分批回填腳本 - awooop_models.py:7 個 SQLAlchemy 2.x models ## Critic 修正(4 Critical + 3 Major) - C-1:ADD CONSTRAINT IF NOT EXISTS → DO 塊 + pg_constraint 查詢 - C-2:__mapper_args__ 字串 list → primary_key=True on mapped_column - C-3:__platform__ RLS 後門 → 全移除,改用 BYPASSRLS role - C-4:awooop_app role 從未建立 → Step 1 + 7 條 GRANT - M-1:active_pointer_guard SECURITY DEFINER(FORCE RLS 跨租戶保護) - M-2:pg_partman create_parent 加冪等防護 - M-3:immutability trigger 新增身份欄位保護(project_id/family/contract_id) ## Task 1.2 修補 - agent_loader.py:硬編碼 Mac 路徑 → AGENTS_DIR 環境變數 - Dockerfile:補 COPY .claude/agents/ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
42 lines
1.4 KiB
Python
42 lines
1.4 KiB
Python
"""載入 .claude/agents/*.md 並解析 system prompt(ADR-095)
|
||
|
||
2026-04-24 Claude Sonnet 4.6 (WS4 Hermes NL)
|
||
2026-05-04 Claude Sonnet 4.6 (Task 1.2): 移除本機絕對路徑,改用 AGENTS_DIR 環境變數
|
||
"""
|
||
from __future__ import annotations
|
||
import os
|
||
import pathlib
|
||
from functools import lru_cache
|
||
|
||
# 本機預設: /Users/ogt/awoooi/.claude/agents(由 AGENTS_DIR 覆蓋)
|
||
# K8s 容器預設: /app/.claude/agents(Dockerfile COPY .claude/agents/ ./.claude/agents/)
|
||
_AGENTS_DIR = pathlib.Path(os.getenv("AGENTS_DIR", "/app/.claude/agents"))
|
||
|
||
|
||
def _parse_agent_md(path: pathlib.Path) -> str:
|
||
"""去除 YAML frontmatter,回傳 body 作為 system prompt"""
|
||
text = path.read_text(encoding="utf-8")
|
||
# --- frontmatter ---
|
||
if text.startswith("---"):
|
||
end = text.find("---", 3)
|
||
if end != -1:
|
||
return text[end + 3:].strip()
|
||
return text.strip()
|
||
|
||
|
||
@lru_cache(maxsize=None)
|
||
def get_agent_system_prompt(agent_name: str) -> str | None:
|
||
"""
|
||
回傳指定 agent 的 system prompt。
|
||
若 .md 不存在回傳 None(caller 決定是否 fallback)。
|
||
"""
|
||
path = _AGENTS_DIR / f"{agent_name}.md"
|
||
if not path.exists():
|
||
return None
|
||
return _parse_agent_md(path)
|
||
|
||
|
||
def list_available_agents() -> list[str]:
|
||
"""回傳目前存在的 agent 名稱列表(無副檔名)"""
|
||
return [p.stem for p in sorted(_AGENTS_DIR.glob("*.md"))]
|