"""FR-170 P0 拆卡底表：design.md §5、Notion spec、工作坊四欄都從這張表產，改一處三處一致。

每張卡：id／批／repo／標題／reuse-extend-new／改哪些既有檔／新開哪些檔／依賴／驗收／model／effort／理由。
批（Stage）＝能同時跑的那一組：不改同一檔、不依賴對方產出、不搶同一外部資源。
登記型檔各只讓一張卡碰：Agent `pyproject.toml`（1-3）、`core/local_db/migrations.py`（1-2）、
jedi-common `pyproject.toml`（1-1）、jedi-iam `pyproject.toml`（1-4）。
repo 值：agent、jedi-packages、main（主 repo 只在結案卡 3-2 出現）。

無人值守原則（決策者 2026-10-29：半夜放著跑）：單卡不得停下來等人——套件單卡發 **beta 版**（PEP 440 `bN`），
正式版與主系統釘版集中在結案卡；凡要 sudo、要真機、要人看畫面的驗證一律改成測試或留給「測試期」段由首腦操作。
"""

REPO_NAME = {"agent": "evidence-agent", "jedi-packages": "jedi 套件"}

CARDS = [
# ───────── 批 1：相依斷層，兩 repo 平行 ─────────
dict(id="1-1", stage=1, repo="jedi-packages", model="opus", effort="medium",
  title="jedi-common：psycopg 改選配 extras、sql_exception lazy import、SQLite dialect 測試；發 beta 1.3.7b1",
  ren="extend：`handler/sql_exception.py` 頂層 `from psycopg import ...`（第 3-4 行）改成 try-import，psycopg 類別不存在時 `_SQL_ERROR_MAP` 只留 SQLAlchemy 通用例外；補 `sqlite3.IntegrityError` 對照（走 SQLAlchemy `IntegrityError` 即可，驗證就好）。`pyproject.toml` 把 `psycopg`／`psycopg-binary` 移到 `[project.optional-dependencies] postgres`。`jsonb_extras.py` 的 `_setup_extras` 在非 Postgres dialect 下拋 `RuntimeError(\"JSONB extras 只支援 PostgreSQL\")`（fail loud，不靜默）。不動 `db.py`：它已有 `_is_postgresql` 判斷",
  edit=["jedi-common/jedi_common/handler/sql_exception.py", "jedi-common/jedi_common/session/database/repository/jsonb_extras.py（_setup_extras 加 dialect 檢查）", "jedi-common/pyproject.toml（psycopg → extras postgres；version 1.3.7b1）", "jedi-common/README.md（安裝段：主系統裝 jedi-common[postgres]，Agent 裝純版）"],
  new=["jedi-common/tests/test_sqlite_dialect.py（SQLite 下 init_db／session_scope／transaction／register_error_handlers 跑得通；sql_exception 在無 psycopg 環境 import 得起來；_setup_extras 在 SQLite 拋錯）"],
  deps=[], why="動的是 25 支套件共用的地基，要能判斷哪些 Postgres 寫法該留、哪些該包；但探路已證明範圍只三處",
  accept="套件 venv 內 `pip uninstall psycopg psycopg-binary` 後 `python -c 'import jedi_common.handler.sql_exception'` 成功；tests 全綠含新檔；Nexus 上有 `jedi-common 1.3.7b1`（beta）；**不動主系統 repo**（主系統 `pyproject.toml` 本來就直接列 psycopg 兩行，純版 jedi-common 對它無影響；主系統改釘正式版在結案卡 3-2）；回寫寫明「動了 jedi-common、發了 beta 1.3.7b1、正式版等結案卡」"),

dict(id="1-2", stage=1, repo="agent", model="opus", effort="medium",
  title="Agent 單租戶系統身分（每請求、每背景執行緒都裝）＋local_db v7：upload_files 補 tenant_id／owner_user_id，舊 agent.db 可升",
  ren="new：`core/agent_identity.py`——`AGENT_TENANT_ID = 1` 常數、`system_user_context() -> UserContextDTO`（`id=None`、`login_name=\"system:agent\"`、`tenant_id=1`、其餘字串欄給 `None`；**0.0.31 的 `UserContextDTO` 沒有 `is_system_context` 欄位，不要傳**，1-3 升到 1.3.x 後再加 `is_system_context=True`——用 `dataclasses.fields` 判斷欄位存不存在，兩版都能跑）、`install_system_context(app)`（掛 `@app.before_request` 每個請求 `set_user_context(system_user_context())`）、`with_system_context(fn)` 裝飾器（背景執行緒入口用）。🔴 **為什麼不能只在啟動時設一次**：jedi-common 的身分是 `contextvars.ContextVar`，Flask 每個請求、每條 `threading.Thread` 都是新的 context，啟動時設的值請求裡拿不到——首腦實測 `python -c` 單執行緒會過、真 app 上傳照樣撞 NOT NULL。三個會寫 `upload_files` 的背景入口都要包：`core/task_sinks.py:_upload_report`（任務報告）、`core/netdiag/report.py:146`（網路診斷報告）、`core/console/files.py:136` 走請求已被 before_request 蓋到。extend：`core/local_db/migrations.py` `MIGRATIONS` 尾巴加 `(7, _v7_upload_files_tenant)`——對 `upload_files` 用既有 `add_missing_columns` 補 `tenant_id INTEGER NOT NULL DEFAULT 1` 與 `owner_user_id INTEGER`（🔴 SQLite NOT NULL 必帶 DEFAULT，見該檔第 15-17 行註解）。**本卡仍在 jedi-file-upload 0.0.22 上做**（0.0.22 model 沒這兩欄，補欄先行、升版在 1-3），所以 v7 的欄位清單寫死不讀 model",
  edit=["core/app_factory.py（create_app 內 init_db 後加一行 install_system_context(app)）", "core/task_sinks.py（_upload_report 加 @with_system_context）", "core/netdiag/report.py（上傳函式加 @with_system_context）", "core/local_db/migrations.py（MIGRATIONS 加 v7；_UPLOAD_FILES_HEAL_COLUMNS 不動）", "test/test_local_db.py（v7 case）"],
  new=["core/agent_identity.py", "test/test_agent_identity.py"],
  deps=[], why="要讀懂 jedi-common before_flush 掛鉤與 RLS 區塊在 SQLite 下的行為，判斷固定身分該帶哪些欄位才不會踩 fail-closed",
  accept="`pytest test/test_local_db.py test/test_agent_identity.py` 綠且含：空庫 upgrade 到 7 後 `upload_files` 有兩新欄；用 v6 schema 預先塞三列再 upgrade，三列 `tenant_id` 皆為 1；test client 打一個請求，handler 內 `get_user_context().tenant_id == 1`；`with_system_context` 包的函式在新 thread 內拿得到 tenant_id=1、thread 外仍是 None；`python main_app.py` 啟動 log 有 `schema_version=7`；`curl` 上傳一檔後 `SELECT tenant_id, owner_user_id FROM upload_files` 為 `1, NULL`；突變：①把 v7 的 DEFAULT 拿掉，「v6 有資料升 v7」case 紅；②把 before_request 拿掉，「請求內有身分」case 紅"),

# ───────── 批 2：升 pin（依賴批 1 兩張）＋抽 kernel 庫（獨立） ─────────
dict(id="1-3", stage=2, repo="agent", model="sonnet", effort="medium",
  title="Agent 升 jedi-common==1.3.7b1、jedi-file-upload==1.5.0；config.py 三行 DTO import；系統身分補 is_system_context；全量測試；舊 agent.db 升級排練",
  ren="extend：`pyproject.toml` 第 11-12 行 pin 改 `jedi-file-upload==1.5.0`（Nexus 現行最新，1.3.8 之後又發了 1.4.0／1.5.0，差異只在下載守門與 download_responder 兩個**選填**掛點，Agent 不注入即維持原行為）、`jedi-common==1.3.7b1`（1-1 發的 beta；純版，不帶 [postgres]；`poetry` 要吃 beta 需在 pin 寫完整版號，不用範圍）；`config/config.py` 第 5-7 行三個 DTO import 從 `jedi_file_upload.ports.dto.*` 改 `from jedi_file_upload.domain.ports import LocalFileUploadConfigDTO, MinioUploadConfigDTO, SeaweedfsUploadConfigDTO`；`poetry update jedi-common jedi-file-upload`（指定套件名，不裸跑）。`core/agent_identity.py` 的 `system_user_context()` 在新版 DTO 上多帶 `is_system_context=True`（1-2 已用 `dataclasses.fields` 判斷，本卡只驗、必要時補）。順手：`pyproject.toml` 第 34、39 行關於「釘的 jedi-common 0.0.31 升不動」的註解刪掉（過期脈絡，理由進 commit message）。不動 jedi-ldap／jedi-masking／jedi-file-protect pin",
  edit=["pyproject.toml", "poetry.lock", "config/config.py（第 5-7 行）", "core/agent_identity.py（只在需要時補 is_system_context）"],
  new=[],
  deps=["1-1", "1-2"], why="形狀已由探路定死（三行 import、兩個 pin），機械性；風險在排練不在寫碼",
  accept="Agent venv 內 `pip show jedi-common` 1.3.7b1、`pip show jedi-file-upload` 1.5.0、`pip show psycopg` 回「not found」；`pytest test/` 1,655＋綠；`python main_app.py` 起得來、health 200、上傳與下載一檔成功、`SELECT tenant_id FROM upload_files` 新列為 1；**升級排練**：從 188 以 `sqlite3 .backup` 唯讀抓 agent.db 複本到本機（容器名用 `docker ps` 查、不猜；WAL 模式不能只 cat 主檔），指 `DATABASE_URL` 起 app，log 顯示 v6→v7、既有列 tenant_id=1、既有檔可下載；排練結果（來源機、容器名、列數、耗時）貼回寫。188 抓不到就用本機 `data/agent.db` 走 v6 → v7 並回寫說明，不卡"),

dict(id="1-4", stage=2, repo="jedi-packages", model="opus", effort="medium",
  title="抽 jedi-password：密碼政策與 argon2id 雜湊成零資料表零 Flask 的 kernel 庫；jedi-iam 改引用；兩支發 beta",
  ren="new：照 `jedi-masking/` 形狀開 `jedi-password/`（`pyproject.toml` 相依只有 `argon2-cffi`；**不得**出現 Flask／SQLAlchemy／jedi-common）。`policy.py`：把 jedi-iam `common/utils/common_util.py:32 validate_password_policy(password, policy)` 與 `PasswordPolicy` dataclass 搬過來（函式簽名不變）。`hashing.py`：`hash_password(pw) -> str`、`verify_password(pw, hashed) -> bool`、`needs_rehash(hashed)`，用 argon2id RFC 9106 低記憶體組（參數照 Agent `core/local_credentials.py:40` 的 `PasswordHasher()` 預設）。extend：jedi-iam `common_util.py` 的 `validate_password_policy` 改成 `from jedi_password.policy import validate_password_policy` re-export（**不動 jedi-iam 的 bcrypt 登入雜湊**——那是既有使用者資料的格式，換演算法是另一個案子）；jedi-iam `pyproject.toml` 加相依。Agent 側改接在 P1 搬 identity 時做，本卡不碰 Agent",
  edit=["jedi-iam/jedi_iam/common/utils/common_util.py（validate_password_policy 改 re-export）", "jedi-iam/pyproject.toml（加 jedi-password 相依；version 1.11.0b1）"],
  new=["jedi-password/pyproject.toml", "jedi-password/jedi_password/__init__.py", "jedi-password/jedi_password/policy.py", "jedi-password/jedi_password/hashing.py", "jedi-password/tests/test_policy.py（搬 jedi-iam 既有 policy 測試）", "jedi-password/tests/test_hashing.py", "jedi-password/README.md", "jedi-password/publish.sh"],
  deps=[], why="要判斷哪些東西屬 kernel（政策、雜湊）、哪些留 jedi-iam（bcrypt 使用者雜湊、DB 綁定），切錯會把資料格式帶進 kernel",
  accept="`pip show jedi-password` 相依只有 argon2-cffi；tests 綠含：政策四條規則各一正一反、`verify_password` 對 Agent 既有 argon2 雜湊字串（從 `core/local_credentials.py` 測試抄一組）回 True、突變：把 policy 大寫檢查拿掉測試紅；jedi-iam tests 綠（re-export 後行為不變）；主系統 `grep -rn validate_password_policy app/ api/` 四處呼叫不用改；Nexus 上有 `jedi-password 0.1.0b1` 與 `jedi-iam 1.11.0b1`（兩支都 beta；jedi-iam 相依寫 `jedi-password>=0.1.0b1,<0.2`，否則 pip 不會解到預發版）；**不動主系統 repo**（主系統釘 jedi-iam 正式版在結案卡 3-2）；回寫明講兩支套件動了、發了哪兩個 beta 版號"),

# ───────── 批 3：宿主殼（單張；守衛要等它搬完 local_db／app_factory 才能凍結清單） ─────────
dict(id="2-2", stage=3, repo="agent", model="opus", effort="high",
  title="guidant_agent/platform/ 宿主殼：bootstrap、DI 根容器、模組 register 掛點、migration 串接；core/app_factory.py 退成一行",
  ren="new：`guidant_agent/__init__.py`、`guidant_agent/platform/bootstrap.py`（`create_app()` 本體：init_db→sqlite tune→系統身分→migrations→error handlers→CORS→根容器→依序 `register()` 各模組→啟動背景元件）；`guidant_agent/platform/container.py`（根 `DeclarativeContainer`，把 `di_containers/containers.py` 39 行搬進來成 `files` 子容器，之後各模組一個子容器）；`guidant_agent/platform/registry.py`（`ModuleSpec(name, register: Callable[[Flask, Container], None], migrations: list[tuple[int, Callable]])` 與 `MODULES` 有序清單；migration 由 platform 把各模組申報的步驟依全域版本號串成一條，**本卡先只有一個「legacy」模組申報現有 v1～v7**）；`guidant_agent/platform/local_db/`（`core/local_db/` 用 `git mv` 搬入，舊路徑留 re-export 殼）。extend：`core/app_factory.py` 全部內容搬到 bootstrap，原檔只剩 `from guidant_agent.platform.bootstrap import create_app`（≤ 30 行）；`main_app.py` import 不變。**不搬任何業務模組**，`_setup_agent_auth` 等現有接線原樣搬進 bootstrap，只是換地方住",
  edit=["core/app_factory.py（退成 re-export）", "di_containers/containers.py（退成 re-export 到 platform.container）", "core/local_db/__init__.py、migrations.py（git mv 到 platform/local_db/，舊路徑 re-export）", "test/conftest.py（import 路徑跟著改）"],
  new=["guidant_agent/__init__.py", "guidant_agent/platform/__init__.py", "guidant_agent/platform/bootstrap.py", "guidant_agent/platform/container.py", "guidant_agent/platform/registry.py", "guidant_agent/platform/local_db/（搬入）", "test/test_platform_bootstrap.py"],
  deps=["1-3"], why="這張決定後面每個模組怎麼掛上來；register 掛點與 migration 串接的形狀錯了，P1、P2 每張卡都要繞",
  accept="`wc -l core/app_factory.py` ≤ 30；`pytest test/` 全綠（守衛在下一批 2-1 才進來）；`python main_app.py` 起得來、health 200、上傳下載、主控台登入、心跳（demo 模式）行為與改前一致；`guidant_agent/platform/registry.py` 的 `MODULES` 只有 legacy 一項；突變：把 legacy 模組從 `MODULES` 拿掉，啟動時 `schema_version` 查不到表 → 測試紅；回寫附「下一個模組怎麼掛」三行範例"),

# ───────── 批 4：守衛（依賴 2-2：凍結清單以搬完後的 HEAD 盤） ─────────
dict(id="2-1", stage=4, repo="agent", model="opus", effort="high",
  title="Agent 範圍憲章 docs/ARCHITECTURE.md ＋ 六支守衛測試（import 方向、全域、SQL 位置、禁新檔進舊 core、行數、背景元件）",
  ren="new：`docs/ARCHITECTURE.md`——七模組清單與白話職責、依賴方向（console → 業務模組 → platform → shared，五個業務模組彼此不直接 import）、模組內五層規則、「永遠不進 Agent」清單（多人協作、RBAC 矩陣、報表產生）、`product.display_name` 兩層命名。new：`test/test_module_boundaries.py` 照主系統同名檔形狀（AST 掃 import）：①`guidant_agent.<業務模組>` 不 import 另一個業務模組（白名單空）；②`*/domain/` 不 import sqlalchemy／flask／requests／httpx；③`*/api/` 不 import `*/adapters/`。`test/test_no_module_globals.py`：模組層級 `_x: dict = {}`／`set()`／`threading.Lock()` 賦值，**現況 27 處進白名單，白名單只能變短**。`test_sql_only_in_adapters.py`：`text(`／`.execute(` 只准在 `adapters/`、`platform/local_db/`、`migrations.py`，現況 36 支白名單。`test_no_new_flat_core.py`：`core/*.py` 檔名清單凍結為開工當時 HEAD 實況（2-2 已把 local_db 搬走、FR-169 加了兩支，不是 45），多一支紅。`test_file_and_function_size.py`：800／60 行，現況超標 5 檔 22 函式白名單。`test_background_components.py`：`threading.Thread(` 只准出現在實作 `BackgroundComponent` 的類別內（2-3 落地前先以白名單 9 支過）",
  edit=[],
  new=["docs/ARCHITECTURE.md", "test/test_module_boundaries.py", "test/test_no_module_globals.py", "test/test_sql_only_in_adapters.py", "test/test_no_new_flat_core.py", "test/test_file_and_function_size.py", "test/test_background_components.py", "docs/oversized-files.md（超標清單，拆完劃掉）"],
  deps=["2-2"], why="守衛的判準寫錯會變成假綠或擋到正常程式，要讀懂現有 45 支檔的真實依賴才定得出白名單",
  accept="六支守衛對現況 HEAD 全綠（靠白名單）；每支各做一個突變：①在 `core/ldap_source.py` 加 `from core import outbox` 守衛①紅（過渡期以 `core/` 當模組邊界，對照表在測試檔頂端）；②在任一 `core/*.py` 加 `_cache = {}` 守衛②紅；③新開 `core/zzz.py` 守衛④紅；④白名單任一項刪掉、對應檔沒改，該守衛紅（證明白名單有牙齒）；`docs/ARCHITECTURE.md` `grep -n border-left` 無關、`grep -nE 'CM-1[0-9]|原本|改判|第 [0-9] 棒'` 為 0"),

# ───────── 批 5：背景元件協定（單張：與 2-4 都改 bootstrap.py，不同批） ─────────
dict(id="2-3", stage=5, repo="agent", model="opus", effort="high",
  title="BackgroundComponent 協定：心跳、outbox、profile_sync、vulndb_sync 改實作 start／stop／health，由 platform 統一起停",
  ren="new：`guidant_agent/platform/background.py`——`class BackgroundComponent(Protocol): name; start(); stop(timeout); health() -> dict` 與 `ComponentRegistry`（有序 start、逆序 stop、聚合 health 給 `/health` 與主控台總覽）。extend：`core/enroll.py` 的 `start_heartbeat` 改成 `HeartbeatComponent` 類別（迴圈體不動，thread 的建立與停止搬進 start／stop，observers 清單維持第 355-370 行原順序）；`core/outbox.py`、`core/profile_sync.py`、`core/vulndb_sync.py` 三支的模組層級 thread／lock 收進各自的 Component 類別，模組層級全域變數隨之刪除（`test_no_module_globals` 白名單對應四支劃掉）。bootstrap 建 registry、app teardown 時 `stop_all()`；`local_ops` 升級前的「取消執行中工作」改呼叫 registry。其餘 5 支開 thread 的檔（browser_plane、data_plane、console/ops、netdiag/probes、task_executor）本卡**不動**，P1／P2 搬到所屬模組時再改。白名單劃掉幾支以開工當時 HEAD 實況為準（FR-169 新增的 `core/file_protect_sync.py` 若有 thread 也不動）",
  edit=["core/enroll.py", "core/outbox.py", "core/profile_sync.py", "core/vulndb_sync.py", "core/local_ops.py", "guidant_agent/platform/bootstrap.py（建 registry、teardown stop_all）", "test/test_no_module_globals.py（白名單劃掉四支）", "test/test_background_components.py（白名單劃掉四支）"],
  new=["guidant_agent/platform/background.py", "test/test_background_registry.py"],
  deps=["2-1"], why="心跳是 Agent 與主系統唯一的生命線，改它的執行緒模型要能推理拔網路、斷主系統、重啟三種時序",
  accept="`pytest test/` 全綠；`test_background_registry`：start 後四支 thread 存在、`stop_all()` 後 `threading.enumerate()` 內無這四支、health 回四筆；**三個時序全部用測試證明、不做真機**（demo 模式不起心跳、半夜沒人能拔網路）：①起停測試 log 無 traceback；②用 `test_heartbeat_loop_timing.py` 既有 patch 手法讓 `_heartbeat_once` 連續丟 `ConnectionError` 再恢復，斷言心跳迴圈沒死、恢復後 `outbox.drain` 被呼叫；③`subprocess.Popen([sys.executable, \"main_app.py\"], env=demo)` 等 health 200 後 `send_signal(SIGTERM)`，`wait(timeout=5)` 回傳碼非 None；突變：把 `HeartbeatComponent.stop` 的 join 拿掉，「stop 後無殘留 thread」case 紅。真機拔網路留測試期由首腦在 DEV Agent 做"),

dict(id="2-4", stage=6, repo="agent", model="sonnet", effort="medium",
  title="產品顯示名設定值 product.display_name；三處寫死改讀設定；Agent CLAUDE.md 改寫成新定位、新結構、新規則",
  ren="new：`guidant_agent/platform/product.py`——`display_name() -> str`，讀 `AGENT_DISPLAY_NAME` env，缺省「Guidant AI Agent」（之後主控台設定頁再接寫入，本卡只做讀）。extend：`main_app.py:1` docstring、`core/app_factory.py`（已退成一行，改 bootstrap 內 `logger.info` 那行）、`core/local_state_cli.py:150` 的 `prog=` 三處「evidence-agent」字串改讀 `display_name()`；主控台 `/console/api/overview` 回應加 `product_name` 欄（FE 之後接）。extend：`CLAUDE.md` 整檔改寫——「專案定位」段改成第二宿主、「架構與開發規範立場」段刪掉「不建三層」整段改指 `docs/ARCHITECTURE.md`、加「新程式碼只進 `guidant_agent/`，`core/` 只准減」、加命名兩層規則；**只寫現況不寫沿革**（`grep -nE 'CM-1[0-9]|原本|改判|第 [0-9] 棒|當初'` 為 0）；舊 FR-039／FR-066 的硬規則（不連雲端 DB、SQLite 不可遺失、不 fork jedi-file-upload）保留",
  edit=["main_app.py", "guidant_agent/platform/bootstrap.py（log 字串）", "core/local_state_cli.py", "core/console/overview.py（加 product_name）", "CLAUDE.md", ".env.example（加 AGENT_DISPLAY_NAME 一行，值留空）"],
  new=["guidant_agent/platform/product.py", "test/test_product_name.py"],
  deps=["2-3"], why="改字串與改文件，形狀已定；CLAUDE.md 改寫要對照 ARCHITECTURE.md 不自創規則",
  accept="`grep -rn 'evidence-agent' --include='*.py' .` 只剩 build／install 腳本與升級相容路徑（回寫列出）；`AGENT_DISPLAY_NAME='測試名' python main_app.py` log 第一行含「測試名」，不設時含「Guidant AI Agent」；`curl /console/api/overview` 有 `product_name`；CLAUDE.md 新成員照它 10 分鐘能說出「新檔該放哪、哪些不准做」；`grep -nE 'CM-1[0-9]|原本|改判|第 [0-9] 棒|當初' CLAUDE.md` 為 0"),

# ───────── 批 7：結案（套件轉正式版）；批 8：Agent 改釘正式版（要等 7 的 Nexus 產物） ─────────
dict(id="3-1", stage=7, repo="jedi-packages", model="sonnet", effort="low",
  title="結案（套件側）：jedi-common 1.3.7、jedi-password 0.1.0、jedi-iam 1.11.0 由 beta 轉正式版並發 Nexus",
  ren="reuse：照 `jedi-package-dev` skill 發版流程；三支套件在本案 beta 期間沒有再改程式，只把 `version` 由 `bN` 改成正式號、`jedi-iam` 的 `jedi-password` 相依下限改 `>=0.1.0,<0.2`、跑各自 tests、`poetry publish --build -r nexus`。🔴 **本卡的「發版前決策者確認」在批 7 開跑那一刻已成立**：決策者按下批 7 就是確認套件清單（jedi-common／jedi-password／jedi-iam 三支，清單寫在母卡），runner 不再停下來問。若 Nexus 上已有同正式版號（有人先發了），不覆蓋、bump patch 號並回寫",
  edit=["jedi-common/pyproject.toml（version 1.3.7）", "jedi-password/pyproject.toml（version 0.1.0）", "jedi-iam/pyproject.toml（version 1.11.0；jedi-password 相依改正式範圍）"],
  new=[],
  deps=["2-4"], why="機械性：改三個版號、跑測試、發版；判斷點只有「同號已存在」",
  accept="Nexus 上有 `jedi-common 1.3.7`、`jedi-password 0.1.0`、`jedi-iam 1.11.0` 三個正式版；三支套件 tests 綠；`pip download jedi-iam==1.11.0 --no-deps` 的 METADATA 相依列 `jedi-password` 正式範圍；回寫列三個版號與 commit"),

dict(id="3-2", stage=8, repo="agent", model="sonnet", effort="low",
  title="結案（Agent 側）：pin 由 beta 改釘正式版 jedi-common==1.3.7；全量測試；主系統釘 jedi-iam 1.11.0 另由首腦開卡",
  ren="extend：Agent `pyproject.toml` 第 12 行 `jedi-common==1.3.7`（1-3 釘的是 1.3.7b1），`poetry update jedi-common`，`pytest test/` 全量。**只動 Agent repo**；主系統把 `jedi-common==1.3.6`、`jedi-iam==1.10.0` 改釘 1.3.7／1.11.0 是主 repo 的卡，與本案其他主系統進版合併排，由首腦在 P0 驗收後另開（主系統本身直接列 psycopg，不升也能跑，不急）。本卡排在 3-1 的下一批：`poetry update` 要抓得到 Nexus 上的 1.3.7 正式版；開工第一步 `pip index versions jedi-common` 看不到 1.3.7 就回寫「3-1 沒發成」、狀態留 In progress",
  edit=["pyproject.toml（第 12 行）", "poetry.lock"],
  new=[],
  deps=["2-4", "3-1"], why="機械性：一個 pin、一次 update、一輪測試",
  accept="Agent venv `pip show jedi-common` 1.3.7（不帶 b）；`pyproject.toml` 內無任何 `b1` 字樣；`pytest test/` 全綠；`python main_app.py` health 200"),
]
