
Civil Case Os
- 1 installs
- 1 repo stars
- Updated August 4, 2026
- gcheng-001/legal-skills
An all-in-one civil-case operating system for case-material analysis, claim generation, defense database, and plaintiff/defendant nine-step templates.
About
A civil-case OS that bundles case analysis, automatic claim generation, a 12-category defense-standard database, and nine-step case-handling templates. A Chinese-law lawyer uses it as the entry point for building and drafting a civil case.
- 12-category defense-standard database and evidence-card management
- Plaintiff/defendant nine-step case-handling templates and dashboard
Civil Case Os by the numbers
- 1 all-time installs (skills.sh)
- Ranked #2,476 of 3,282 Productivity & Planning skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/gcheng-001/legal-skills --skill civil-case-osAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 1 |
| Last updated | August 4, 2026 |
| Repository | gcheng-001/legal-skills ↗ |
What it does
An all-in-one civil-case operating system for case-material analysis, claim generation, defense database, and plaintiff/defendant nine-step templates.
Files
__pycache__/
*.pyc
.DS_Store
case-os 民事案件操作系统(管理层)
1. Skill 定位(强制)
case-os 是民事案件管理层,不替代实体法律分析与文书写作,职责是:
1. 初始化案件工作区 2. 自动创建并维护案件核心记忆文件 3. 每次任务前恢复案件上下文 4. 每次任务后写回关键结论 5. 维护可直接执行的任务面板 6. 作为 ai-law-firm 与 legal-aid-lawyer 上层的民事案件管理层
系统层级固定为:
用户 -> 内阁系统(角色调度层) -> case-os(民事案件管理层) -> ai-law-firm / legal-aid-lawyer(执行层)
2. 触发词机制(强制)
case-os 与内阁系统仅在命中民事案件触发词时激活。
一级触发(必须支持):
1. 启动民事案件OS 2. 民事案件OS 3. 启动案件OS 4. 案件OS 5. case os 6. case-os
二级触发(建议支持):
1. 进入民事案件系统 2. 打开民事案件系统 3. 进入案件系统 4. 打开案件系统 5. 打开case os
匹配规则:
1. 不区分大小写 2. 命中触发词才激活;触发词是唯一入口(除非用户通过面板点击进入执行) 3. 未命中时必须完全不介入、不抢答、不初始化 4. 未命中时不得影响 Claude Code 默认行为与非法律项目 5. 历史触发词“案件OS”默认指向民事案件OS;刑事案件专项流程必须使用 criminal-case-os 或“刑事案件OS”
3. 隐身边界(强制)
在以下场景中必须完全隐身:
1. 写代码 2. 做产品 3. 普通聊天
未触发时禁止出现:
1. 律师人格 2. 内阁角色 3. 案件分析行为
4. 核心文件机制(强制)
进入案件模式后,必须检查并保障以下文件存在:
1. CLAUDE.md - 案件大脑(概览 + 任务面板 + 操作入口,三者合一) 2. LOG.md - 工作日志(操作记录,高频追加)
规则:
1. 不要求用户手动预建 2. 两个文件仅允许创建在”当前项目根目录” 3. 禁止在 /system、/case-os、/hidden 或其他子目录创建同类核心文件 4. 缺失则自动创建 5. 已存在不得重复创建 6. 读取时优先从项目根目录读取 7. 发现旧版多文件结构(00_案件概览.md、00_案件操作面板.md、99_工作日志.md等)时,应自动迁移合并到 CLAUDE.md 和 LOG.md
5. 面板优先输出规则(强制)
命中触发词后的第一响应必须是主面板展示:
1. 第一响应原样展示 CLAUDE.md 的「操作入口」章节 2. 不加解释、不加说明、不做总结 3. 面板作为唯一入口 4. 仅提示可使用技能清单,不自动执行任何内阁角色
6. 内阁系统联动规则
1. 内阁系统仅在命中触发词后生效 2. 未触发时完全不介入 3. 每次调用角色必须先读取:CLAUDE.md,再读取:LOG.md 4. 角色任务完成后必须:追加 LOG.md;必要时更新 CLAUDE.md 5. 禁止自动多代理并发调度;仅按用户在面板中选择的单角色执行 6. 禁止写入任何外部系统(飞书、微信、Gateway、表格、日历等)
6.1 本地结构化状态契约(2026-05-25 Phase 1)
1. 新流程机器权威状态为案件根目录 _archive/case-os-state.json。 2. _archive/feishu-publish.json 仅为严格白名单发布摘要;本阶段只生成在本地,不发送、不回读为案件判断依据。 3. 正常后置 hook 只能刷新并校验上述双文件;不得自动生成或刷新 HTML 仪表盘/确认表,不得调用任何外部系统。 4. 存量旧状态发生冲突时,必须将新状态标记为 validation_required,发布摘要仅保留“待人工校验”占位,不带期限。 5. A4/A5 与 S1/S5/S6/S8/S9 未取得律师明确确认时不得被 hook 标记完成;S10 FINAL 门禁原值必须保留。
7. 内阁四角色定义(强制)
7.1 中书省(总控)
职责:
1. 总览案件状态 2. 判断当前阶段 3. 决定优先路径 4. 输出“现在最该做什么”
7.2 大理寺(风险与攻防)
职责:
1. 从对方视角审查案件 2. 识别我方漏洞 3. 识别败诉风险 4. 输出反制方向
说明:大理寺 为风险审查角色,替代旧称“刑部”。
7.3 通政司(执行转化)
职责:
1. 将分析转为待办 2. 生成任务清单 3. 生成补证清单 4. 生成客户问题 5. 更新任务面板
输出限制:仅允许写入本地文件 CLAUDE.md 与 LOG.md。
7.4 翰林院(文书优化)
职责:
1. 润色文书 2. 优化结构 3. 强化表达 4. 输出可直接使用版本
禁止:改变案件事实、改变用户立场。
8. 标准工作流(强制)
A. 启动流程(命中触发词后)
1. 扫描当前案件目录及子目录 2. 检查两个核心文件是否存在(CLAUDE.md、LOG.md) 3. 缺失文件自动创建,已存在文件保持不重建 4. 若发现旧版多文件结构,自动迁移合并到 CLAUDE.md 和 LOG.md 5. 若新建文件,填充基础模板并追加初始化日志 6. 第一响应原样展示 CLAUDE.md 的「操作入口」章节
B. 执行流程(用户发送面板指令后)
1. 先读取 CLAUDE.md 2. 再读取 LOG.md 3. 按选定入口执行单任务 4. 任务完成后按写回规则更新文件
C. 写回流程
1. 判断是否更新 CLAUDE.md(概览或任务面板部分) 2. 默认追加 LOG.md
9. 材料扫描规则(强制)
初始化或刷新状态时,必须扫描当前案件目录及子目录,优先识别:
1. PDF 2. DOCX 3. TXT 4. Markdown 5. 图片
目录优先级固定:
1. 立案材料/ 2. materials/ 3. evidence/ 4. 当前案件目录根目录 5. 其他子目录
关键文件优先纳入概览:
1. 起诉状 2. 判决书 / 裁定书 3. 合同 4. 聊天记录 5. 证据目录 6. 借条 7. 转账记录 8. 开庭传票 / 举证通知 / 执行通知 9. 会见笔录 / 阅卷笔录
10. 风险控制与边界(强制)
1. 不得编造案件事实 2. 不得跨案件复用事实 3. 不得将推测写为事实 4. 不得用空泛建议替代可执行动作 5. case-os 默认不负责长篇实体法律分析 6. case-os 默认不直接承担实体文书起草 7. 材料不足必须标注 待核实 并列明缺失项 8. 不引入外部系统,不新增任何对外渠道写入 9. 不得在普通对话中自动进入案件模式 10. 不得误判代码/产品类项目为民事案件 11. 不得把刑事案件误纳入民事案件OS;刑事案件专项流程应转入 criminal-case-os
11. Claude Code 生图与网页制作规则(强制)
当任务涉及网页、案件仪表盘、可视化视图、可视化 Tab、客户汇报页、封面图、说明图、流程图、图标、空状态图等非证据视觉资产时,优先使用 Claude Code/可用图像工具 当前可用的自带模型与工具完成,包括但不限于 ChatGPT Image 2、Codex 内置图像生成能力、前端代码生成与本地预览验证能力。
执行规则:
1. 用户要求“生图”“做网页”“优化仪表盘”“做可视化”“做汇报页”时,默认允许使用 Claude Code/可用图像工具 自带模型与工具。 2. 不默认引入 Midjourney、Stable Diffusion、SDXL、第三方绘图网站或外部生图插件;除非用户明确指定。 3. 网页、仪表盘、可视化视图可以使用生成图像作为背景图、首屏图、图标、插图、图例、空状态图和沟通辅助素材。 4. 生成图像必须明确定位为展示/沟通辅助,不得作为案件事实、证据来源或事实核验材料。 5. 证据图片、扫描件、聊天截图、现场照片、PDF 截图、录屏关键帧只能读取、OCR、裁切、提取、嵌入、标注和清晰化处理;禁止用生成图像替代、补画、修饰或伪造。 6. 生成网页或可视化界面后,应尽量做真实预览或截图验证,确认页面可打开、主要视觉资产正常显示、关键文字不重叠。
12. 完成标准(DoD)
满足以下条件才算完成:
1. 两个核心文件存在且结构完整(CLAUDE.md + LOG.md) 2. 触发后第一响应为 CLAUDE.md 的「操作入口」章节原样内容 3. 技能清单可作为二级入口手动调用 4. 角色执行前均先读概览与日志 5. 执行后默认追加日志,并按需更新概览 6. 未触发时对非法律项目完全无影响
13. 新增:案情补充模块(人工优先机制)
13.1 人工信息优先原则(最高优先级)
当同时存在“用户人工补充信息”与“原始材料信息”时,必须遵循:
1. 人工补充信息为优先参考依据。 2. 不得自动用材料覆盖人工补充。 3. 若存在冲突,必须在 CLAUDE.md 的「冲突标注」中明确记录。 4. 冲突场景下必须保留双版本分析路径:
- 路径A:按人工补充前提分析
- 路径B:按材料文本前提分析
13.2 不可篡改原则
系统必须:
1. 不修改用户输入原文。 2. 不删除历史人工补充。 3. 不自动重写用户策略。 4. 如需结构化整理,仅允许新增“整理摘要”,不得替换原文。
13.3 案件概览新增固定板块(强制)
CLAUDE.md 必须包含以下固定板块,并作为后续任务优先参考:
## 🧾 人工补充信息(优先参考)
### 一、事实补充(材料未体现)
-
### 二、客户目标
-
### 三、办案策略
-
### 四、冲突标注(如有)
-13.4 读写流程约束
1. 执行前必须先读取“人工补充信息(优先参考)”板块。 2. 写回时不得覆盖人工补充原文;仅可在冲突标注追加记录。 3. 若用户新增补充,必须默认写入该板块并追加日志。
#!/bin/bash
# 案件OS Agent管理器 - 统一管理所有Agent
set -e
AGENTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
HOME_PATH="/Users/Apple" # 使用绝对路径
AGENT_LIST=(
"weekly-scan:case-os.weekly-scan:案件周度扫描Agent:每周一早上8点"
"court-sms-monitor:caseos.sms-monitor:法院短信实时监控Agent:实时监控"
"batch-evidence::批量证据提取Agent:手动触发"
)
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 打印带颜色的消息
print_success() {
echo -e "${GREEN}✅ $1${NC}"
}
print_error() {
echo -e "${RED}❌ $1${NC}"
}
print_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
print_info() {
echo -e "${BLUE}ℹ️ $1${NC}"
}
print_header() {
echo ""
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE} $1${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
}
# 获取Agent状态
get_agent_status() {
local agent_name=$1
local label_prefix=$2
local plist_file="$HOME_PATH/Library/LaunchAgents/com.${label_prefix}.plist"
local label=$(launchctl list | grep "com.${label_prefix}" | awk '{print $1}')
if [ ! -f "$plist_file" ]; then
echo "未安装"
elif [ -n "$label" ]; then
echo "运行中"
else
echo "已安装但未运行"
fi
}
# 列出所有Agent
list_agents() {
print_header "案件OS Agent状态"
printf "%-25s %-30s %-15s\n" "Agent名称" "说明" "状态"
printf "%-25s %-30s %-15s\n" "---------" "----" "----"
for agent_info in "${AGENT_LIST[@]}"; do
IFS=':' read -r agent_name label_prefix desc schedule <<< "$agent_info"
status=$(get_agent_status "$agent_name" "$label_prefix")
printf "%-25s %-30s %-15s\n" "$agent_name" "$desc" "$status"
done
echo ""
}
# 启动所有Agent
start_all_agents() {
print_header "启动所有Agent"
for agent_info in "${AGENT_LIST[@]}"; do
IFS=':' read -r agent_name desc schedule <<< "$agent_info"
# 跳过手动触发的Agent
if [ "$agent_name" = "batch-evidence" ]; then
print_info "$agent_name: 手动触发,跳过自动启动"
continue
fi
print_info "启动 $agent_name..."
install_script="$AGENTS_DIR/$agent_name/install.sh"
if [ -f "$install_script" ]; then
bash "$install_script"
else
print_warning "$agent_name: 未找到安装脚本"
fi
done
print_success "所有Agent已启动"
}
# 停止所有Agent
stop_all_agents() {
print_header "停止所有Agent"
for agent_info in "${AGENT_LIST[@]}"; do
IFS=':' read -r agent_name desc schedule <<< "$agent_info"
# 跳过手动触发的Agent
if [ "$agent_name" = "batch-evidence" ]; then
continue
fi
plist_file="$HOME/Library/LaunchAgents/com.claude.caseos.${agent_name}.plist"
if [ -f "$plist_file" ]; then
print_info "停止 $agent_name..."
launchctl unload "$plist_file" 2>/dev/null || true
print_success "$agent_name 已停止"
else
print_warning "$agent_name: 未安装"
fi
done
print_success "所有Agent已停止"
}
# 重启所有Agent
restart_all_agents() {
print_header "重启所有Agent"
stop_all_agents
sleep 1
start_all_agents
}
# 查看日志
view_logs() {
local agent_name=$1
if [ -z "$agent_name" ]; then
print_error "请指定Agent名称"
echo "用法: $0 logs <agent_name>"
echo ""
echo "可用的Agent:"
for agent_info in "${AGENT_LIST[@]}"; do
IFS=':' read -r agent_name desc schedule <<< "$agent_info"
echo " - $agent_name"
done
return 1
fi
log_file="$HOME/.claude/skills/case-os/data/${agent_name}.log"
if [ ! -f "$log_file" ]; then
log_file="$HOME/.claude/skills/case-os/data/${agent_name}-stdout.log"
fi
if [ ! -f "$log_file" ]; then
print_error "未找到日志文件: $log_file"
return 1
fi
print_header "查看 $agent_name 日志"
tail -f "$log_file"
}
# 测试Agent
test_agent() {
local agent_name=$1
if [ -z "$agent_name" ]; then
print_error "请指定Agent名称"
echo "用法: $0 test <agent_name>"
return 1
fi
print_header "测试 $agent_name"
case "$agent_name" in
weekly-scan)
python3 "$AGENTS_DIR/weekly-scan/scan.py"
;;
court-sms-monitor)
python3 "$AGENTS_DIR/court-sms-monitor/monitor.py"
;;
batch-evidence)
print_error "batch-evidence需要指定案件目录"
echo "用法: python3 $AGENTS_DIR/batch-evidence/batch_extract.py /path/to/case/dir"
;;
*)
print_error "未知的Agent: $agent_name"
return 1
;;
esac
}
# 显示帮助
show_help() {
cat << EOF
案件OS Agent管理器
用法: $0 <command> [options]
命令:
status 查看所有Agent状态
start 启动所有Agent
stop 停止所有Agent
restart 重启所有Agent
logs <agent_name> 查看指定Agent的日志
test <agent_name> 测试指定Agent
help 显示此帮助信息
可用的Agent:
- weekly-scan 案件周度扫描Agent
- court-sms-monitor 法院短信实时监控Agent
- batch-evidence 批量证据提取Agent(手动触发)
示例:
$0 status # 查看状态
$0 start # 启动所有
$0 logs weekly-scan # 查看周度扫描日志
$0 test weekly-scan # 测试周度扫描
EOF
}
# 主函数
main() {
local command=$1
shift || true
case "$command" in
status)
list_agents
;;
start)
start_all_agents
;;
stop)
stop_all_agents
;;
restart)
restart_all_agents
;;
logs)
view_logs "$@"
;;
test)
test_agent "$@"
;;
help|--help|-h)
show_help
;;
*)
print_error "未知的命令: $command"
echo ""
show_help
exit 1
;;
esac
}
main "$@"
批量证据提取Agent
工作定位
角色:并行处理Agent(Phase A内部加速) 触发:手动触发(case-evidence-cards Skill调用) 目标:多个证据文件同时提取,大幅加速A5步骤
并行策略
传统串行处理
证据1.pdf → Agent → 证据卡片1.md (耗时2分钟)
↓
证据2.pdf → Agent → 证据卡片2.md (耗时2分钟)
↓
证据3.pdf → Agent → 证据卡片3.md (耗时2分钟)
↓
总耗时:6分钟并行处理
证据1.pdf → Agent1 → 证据卡片1.md ─┐
├→ 同时进行 (耗时2分钟)
证据2.pdf → Agent2 → 证据卡片2.md ─┤
│
证据3.pdf → Agent3 → 证据卡片3.md ─┘
总耗时:2分钟(3倍加速)技术实现
使用进程池并行
from concurrent.futures import ProcessPoolExecutor
def extract_evidence(file_path):
"""提取单个证据"""
# 调用case-evidence-cards Skill
return skill.execute(file_path)
def batch_extract(files):
"""批量提取"""
with ProcessPoolExecutor(max_workers=3) as executor:
results = executor.map(extract_evidence, files)
return list(results)使用线程池(轻量级)
from concurrent.futures import ThreadPoolExecutor
def batch_extract_lightweight(files):
"""批量提取(线程池)"""
with ThreadPoolExecutor(max_workers=5) as executor:
results = executor.map(extract_evidence, files)
return list(results)工作流程
第一步:发现证据文件
扫描案件文件夹,发现需要提取的证据:
def find_evidence_files(case_dir):
"""发现证据文件"""
evidence_dir = case_dir / "00_原始材料"
files = []
for ext in ["*.pdf", "*.jpg", "*.jpeg", "*.png", "*.docx"]:
files.extend(evidence_dir.glob(ext))
return files第二步:过滤已处理
跳过已经生成卡片的证据:
def filter_processed(files, evidence_cards_dir):
"""过滤已处理的文件"""
processed = {f.stem for f in evidence_cards_dir.glob("*.md")}
return [f for f in files if f.stem not in processed]第三步:并行提取
启动多个进程/线程同时提取:
def parallel_extract(files):
"""并行提取"""
# 使用进程池(CPU密集型)
with ProcessPoolExecutor(max_workers=3) as executor:
futures = [
executor.submit(extract_single_evidence, f)
for f in files
]
results = [f.result() for f in futures]
return results第四步:汇总结果
将所有提取结果汇总:
def summarize_results(results):
"""汇总结果"""
summary = {
"total": len(results),
"success": sum(1 for r in results if r["success"]),
"failed": sum(1 for r in results if not r["success"]),
"files": results,
}
return summary配置
并发数控制
parallel:
max_workers: 3 # 最大并发数
mode: process # process=进程池, thread=线程池
evidence:
input_dir: "00_原始材料"
output_dir: "_intermediate/证据卡片库"
extensions: [".pdf", ".jpg", ".jpeg", ".png", ".docx"]资源限制
resources:
max_memory: "4GB" # 最大内存使用
max_cpu: 80% # 最大CPU使用率错误处理
单个失败不影响整体
def extract_with_retry(file_path, max_retries=2):
"""带重试的提取"""
for attempt in range(max_retries):
try:
return extract_single_evidence(file_path)
except Exception as e:
if attempt == max_retries - 1:
return {
"file": file_path,
"success": False,
"error": str(e),
}
time.sleep(1)超时控制
from concurrent.futures import TimeoutError
def extract_with_timeout(file_path, timeout=120):
"""带超时的提取"""
with ProcessPoolExecutor() as executor:
future = executor.submit(extract_single_evidence, file_path)
try:
return future.result(timeout=timeout)
except TimeoutError:
future.cancel()
return {
"file": file_path,
"success": False,
"error": "超时",
}进度显示
实时进度
def show_progress(results, total):
"""显示进度"""
completed = len(results)
percent = (completed / total) * 100
print(f"进度:{completed}/{total} ({percent:.1f}%)")
for result in results:
if result["success"]:
print(f" ✅ {result['file'].name}")
else:
print(f" ❌ {result['file'].name}: {result['error']}")进度条(可选)
from tqdm import tqdm
def batch_extract_with_progress(files):
"""带进度条的批量提取"""
with ThreadPoolExecutor() as executor:
results = list(tqdm(
executor.map(extract_single_evidence, files),
total=len(files),
desc="提取证据",
))
return results集成到案件OS
在case-evidence-cards中调用
# case-evidence-cards Skill中
def main():
"""主函数"""
# 发现证据文件
files = find_evidence_files(case_dir)
if len(files) > 1:
# 多个文件,使用并行提取
print(f"发现{len(files)}个证据文件,启动并行提取...")
results = batch_extract(files)
else:
# 单个文件,直接提取
results = [extract_single_evidence(files[0])]
# 汇总结果
summary = summarize_results(results)
print(summary)性能对比
| 证据数量 | 串行耗时 | 并行耗时(3worker) | 加速比 |
|---|---|---|---|
| 3个 | 6分钟 | 2分钟 | 3x |
| 10个 | 20分钟 | 7分钟 | 2.86x |
| 20个 | 40分钟 | 14分钟 | 2.86x |
版本历史
- v1.0 - 初始版本(2026-05-16)
- 进程池并行
- 错误处理和重试
- 进度显示
#!/usr/bin/env python3
"""
批量证据提取Agent - 实现脚本
多个证据文件同时提取,大幅加速A5步骤
"""
import json
import os
import sys
import time
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor, as_completed
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional
# ==================== 配置 ====================
MAX_WORKERS = 3 # 最大并发数
TIMEOUT = 120 # 单个任务超时时间(秒)
MODE = "process" # process=进程池, thread=线程池
# 证据文件后缀
EVIDENCE_EXTENSIONS = [".pdf", ".jpg", ".jpeg", ".png", ".docx", ".doc"]
# ==================== 工具函数 ====================
def find_evidence_files(case_dir: Path) -> List[Path]:
"""发现证据文件"""
evidence_files = []
# 扫描00_原始材料目录
material_dir = case_dir / "00_原始材料"
if material_dir.exists():
for ext in EVIDENCE_EXTENSIONS:
evidence_files.extend(material_dir.glob(f"*{ext}"))
evidence_files.extend(material_dir.glob(f"*{ext.upper()}"))
return sorted(evidence_files)
def filter_processed(files: List[Path], evidence_cards_dir: Path) -> List[Path]:
"""过滤已处理的文件"""
if not evidence_cards_dir.exists():
return files
processed = {f.stem for f in evidence_cards_dir.glob("*.md")}
return [f for f in files if f.stem not in processed]
def extract_single_evidence(file_path: Path) -> Dict:
"""提取单个证据(实际调用case-evidence-cards Skill)
Args:
file_path: 证据文件路径
Returns:
提取结果
"""
# TODO: 实际调用case-evidence-cards Skill
# 这里是模拟实现
try:
start_time = time.time()
# 模拟提取过程(实际应该调用Skill)
time.sleep(0.5) # 模拟耗时
return {
"file": file_path,
"file_name": file_path.name,
"success": True,
"output": f"{file_path.stem}_证据卡片.md",
"duration": time.time() - start_time,
}
except Exception as e:
return {
"file": file_path,
"file_name": file_path.name,
"success": False,
"error": str(e),
}
def extract_with_timeout(file_path: Path, timeout: int = TIMEOUT) -> Dict:
"""带超时的提取
Args:
file_path: 证据文件路径
timeout: 超时时间(秒)
Returns:
提取结果
"""
try:
if MODE == "process":
# 使用进程池
with ProcessPoolExecutor(max_workers=1) as executor:
future = executor.submit(extract_single_evidence, file_path)
result = future.result(timeout=timeout)
return result
else:
# 使用线程池
with ThreadPoolExecutor(max_workers=1) as executor:
future = executor.submit(extract_single_evidence, file_path)
result = future.result(timeout=timeout)
return result
except Exception as e:
return {
"file": file_path,
"file_name": file_path.name,
"success": False,
"error": f"超时或错误: {str(e)}",
}
# ==================== 批量提取 ====================
class BatchEvidenceExtractor:
"""批量证据提取器"""
def __init__(self, case_dir: Path, max_workers: int = MAX_WORKERS):
self.case_dir = case_dir
self.max_workers = max_workers
self.evidence_cards_dir = case_dir / "_intermediate" / "证据卡片库"
def find_files(self) -> List[Path]:
"""发现需要处理的证据文件"""
files = find_evidence_files(self.case_dir)
if not files:
print("⚠️ 未发现证据文件")
return []
# 过滤已处理的
files = filter_processed(files, self.evidence_cards_dir)
print(f"📁 发现 {len(files)} 个待处理证据文件")
return files
def extract_parallel(self, files: List[Path]) -> List[Dict]:
"""并行提取证据
Args:
files: 证据文件列表
Returns:
提取结果列表
"""
print(f"🚀 启动并行提取({MODE}模式,{self.max_workers}并发)")
print()
results = []
if MODE == "process":
# 使用进程池
with ProcessPoolExecutor(max_workers=self.max_workers) as executor:
# 提交所有任务
futures = {
executor.submit(extract_with_timeout, f): f
for f in files
}
# 收集结果(带进度显示)
completed = 0
total = len(futures)
for future in as_completed(futures):
file_path = futures[future]
completed += 1
try:
result = future.result()
results.append(result)
# 显示进度
percent = (completed / total) * 100
status = "✅" if result["success"] else "❌"
print(f"[{completed}/{total} {percent:.0f}%] {status} {result['file_name']}")
except Exception as e:
results.append({
"file": file_path,
"file_name": file_path.name,
"success": False,
"error": str(e),
})
print(f"[{completed}/{total}] ❌ {file_path.name}: {e}")
else:
# 使用线程池
with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
futures = {
executor.submit(extract_with_timeout, f): f
for f in files
}
completed = 0
total = len(futures)
for future in as_completed(futures):
file_path = futures[future]
completed += 1
try:
result = future.result()
results.append(result)
percent = (completed / total) * 100
status = "✅" if result["success"] else "❌"
print(f"[{completed}/{total} {percent:.0f}%] {status} {result['file_name']}")
except Exception as e:
results.append({
"file": file_path,
"file_name": file_path.name,
"success": False,
"error": str(e),
})
print(f"[{completed}/{total}] ❌ {file_path.name}: {e}")
return results
def extract_sequential(self, files: List[Path]) -> List[Dict]:
"""串行提取(对比用)
Args:
files: 证据文件列表
Returns:
提取结果列表
"""
print(f"🐌 串行提取模式")
print()
results = []
for i, file_path in enumerate(files, 1):
print(f"[{i}/{len(files)}] 处理:{file_path.name}")
result = extract_with_timeout(file_path)
results.append(result)
status = "✅" if result["success"] else "❌"
print(f" {status} {result.get('error', '完成')}")
return results
def summarize(self, results: List[Dict]) -> Dict:
"""汇总结果
Args:
results: 提取结果列表
Returns:
汇总信息
"""
total = len(results)
success = sum(1 for r in results if r["success"])
failed = total - success
total_duration = sum(r.get("duration", 0) for r in results if r["success"])
summary = {
"total": total,
"success": success,
"failed": failed,
"success_rate": (success / total * 100) if total > 0 else 0,
"total_duration": total_duration,
"avg_duration": total_duration / success if success > 0 else 0,
"timestamp": datetime.now().isoformat(),
}
return summary
def print_summary(self, summary: Dict, parallel: bool = True):
"""打印汇总信息
Args:
summary: 汇总信息
parallel: 是否并行模式
"""
print()
print("=" * 50)
print("📊 批量提取完成")
print("=" * 50)
print(f"模式:{'并行' if parallel else '串行'}")
print(f"总数:{summary['total']} 个")
print(f"成功:{summary['success']} 个")
print(f"失败:{summary['failed']} 个")
print(f"成功率:{summary['success_rate']:.1f}%")
print(f"总耗时:{summary['total_duration']:.1f} 秒")
print(f"平均耗时:{summary['avg_duration']:.1f} 秒/个")
if parallel and summary['success'] > 1:
# 计算加速比
sequential_time = summary['total_duration'] * summary['success']
speedup = sequential_time / summary['total_duration']
print(f"加速比:{speedup:.1f}x")
print("=" * 50)
def run(self, mode: str = "parallel") -> List[Dict]:
"""运行批量提取
Args:
mode: parallel=并行, sequential=串行
Returns:
提取结果列表
"""
# 发现文件
files = self.find_files()
if not files:
return []
# 提取
start_time = time.time()
if mode == "parallel":
results = self.extract_parallel(files)
else:
results = self.extract_sequential(files)
# 汇总
summary = self.summarize(results)
summary["total_duration"] = time.time() - start_time
self.print_summary(summary, parallel=(mode == "parallel"))
# 保存结果
self.save_results(results, summary)
return results
def save_results(self, results: List[Dict], summary: Dict):
"""保存提取结果
Args:
results: 提取结果列表
summary: 汇总信息
"""
# 确保输出目录存在
self.evidence_cards_dir.parent.mkdir(parents=True, exist_ok=True)
# 保存汇总信息
summary_file = self.case_dir / "_intermediate" / "batch_extract_summary.json"
with open(summary_file, "w", encoding="utf-8") as f:
json.dump({
"summary": summary,
"results": [
{
"file_name": r["file_name"],
"success": r["success"],
"error": r.get("error"),
}
for r in results
],
}, f, ensure_ascii=False, indent=2)
print(f"💾 汇总信息已保存:{summary_file}")
# ==================== 命令行接口 ====================
def main():
"""命令行入口"""
import argparse
parser = argparse.ArgumentParser(description="批量证据提取Agent")
parser.add_argument("case_dir", help="案件目录路径")
parser.add_argument("--mode", choices=["parallel", "sequential"], default="parallel", help="提取模式")
parser.add_argument("--workers", type=int, default=MAX_WORKERS, help="最大并发数")
args = parser.parse_args()
case_dir = Path(args.case_dir)
if not case_dir.exists():
print(f"❌ 错误:案件目录不存在:{case_dir}")
return 1
# 创建提取器
extractor = BatchEvidenceExtractor(case_dir, max_workers=args.workers)
# 运行
try:
extractor.run(mode=args.mode)
return 0
except KeyboardInterrupt:
print("\n⚠️ 用户中断")
return 1
except Exception as e:
print(f"❌ 错误:{e}")
return 1
if __name__ == "__main__":
sys.exit(main())
批量证据提取Agent
多个证据文件同时提取,大幅加速A5步骤(Phase A内部并行)
功能概述
传统串行处理
证据1.pdf → 2分钟 → 证据卡片1.md
证据2.pdf → 2分钟 → 证据卡片2.md
证据3.pdf → 2分钟 → 证据卡片3.md
总耗时:6分钟并行处理
证据1.pdf ─┐
证据2.pdf ─┼→ 同时进行 → 2分钟完成(3倍加速)
证据3.pdf ─┘性能对比
| 证据数量 | 串行耗时 | 并行耗时(3worker) | 加速比 |
|---|---|---|---|
| 3个 | 6分钟 | 2分钟 | 3x |
| 10个 | 20分钟 | 7分钟 | 2.86x |
| 20个 | 40分钟 | 14分钟 | 2.86x |
使用方法
命令行
# 并行提取(默认)
python3 batch_extract.py /path/to/case/dir
# 串行提取(对比)
python3 batch_extract.py /path/to/case/dir --mode sequential
# 指定并发数
python3 batch_extract.py /path/to/case/dir --workers 5在case-evidence-cards Skill中调用
# case-evidence-cards/SKILL.md
def main():
"""主函数"""
case_dir = Path.cwd()
# 发现证据文件
extractor = BatchEvidenceExtractor(case_dir)
files = extractor.find_files()
if len(files) > 1:
# 多个文件,使用并行提取
print(f"发现{len(files)}个证据文件,启动并行提取...")
results = extractor.run(mode="parallel")
else:
# 单个文件,直接提取
results = [extract_single_evidence(files[0])]工作流程
第一步:发现证据文件
扫描案件文件夹的00_原始材料目录:
案件目录/
└── 00_原始材料/
├── 证据1.pdf
├── 证据2.jpg
├── 证据3.png
└── ...第二步:过滤已处理
跳过已经生成卡片的证据:
案件目录/_intermediate/证据卡片库/
├── 证据1_证据卡片.md ← 已存在,跳过
└── ...第三步:并行提取
启动多个进程/线程同时处理:
Worker 1 → 处理证据1.pdf
Worker 2 → 处理证据2.jpg
Worker 3 → 处理证据3.png
(同时进行)第四步:汇总结果
生成提取报告:
========================================
📊 批量提取完成
========================================
模式:并行
总数:10 个
成功:9 个
失败:1 个
成功率:90.0%
总耗时:120.5 秒
平均耗时:13.4 秒/个
加速比:2.8x
========================================配置
并发数
MAX_WORKERS = 3 # 默认3个并发建议值:
- CPU密集型(OCR):= CPU核心数
- I/O密集型(文件读取):= CPU核心数 × 2
超时时间
TIMEOUT = 120 # 单个任务超时120秒处理模式
MODE = "process" # process=进程池, thread=线程池| 模式 | 适用场景 | 优缺点 |
|---|---|---|
| process | CPU密集型(OCR) | 优点:真正并行<br>缺点:内存开销大 |
| thread | I/O密集型(文件读取) | 优点:内存开销小<br>缺点:Python GIL限制 |
错误处理
单个失败不影响整体
证据1.pdf → ✅ 成功
证据2.pdf → ❌ 失败(但继续处理)
证据3.pdf → ✅ 成功自动重试
def extract_with_retry(file_path, max_retries=2):
"""带重试的提取"""
for attempt in range(max_retries):
try:
return extract_single_evidence(file_path)
except Exception as e:
if attempt == max_retries - 1:
return {"success": False, "error": str(e)}
time.sleep(1)超时控制
单个任务超过120秒自动终止,继续处理其他任务。
输出文件
汇总信息
保存在:<案件目录>/_intermediate/batch_extract_summary.json
{
"summary": {
"total": 10,
"success": 9,
"failed": 1,
"success_rate": 90.0,
"total_duration": 120.5,
"avg_duration": 13.4,
"timestamp": "2026-05-16T20:00:00"
},
"results": [
{
"file_name": "证据1.pdf",
"success": true,
"error": null
},
{
"file_name": "证据2.pdf",
"success": false,
"error": "文件损坏"
}
]
}进阶功能
自定义提取函数
def my_custom_extract(file_path):
"""自定义提取逻辑"""
# 调用你的Skill
result = call_skill("case-evidence-cards", file_path)
return result
# 替换默认提取函数
extractor.extractor = my_custom_extract进度回调
def progress_callback(completed, total, result):
"""进度回调"""
percent = (completed / total) * 100
print(f"[{completed}/{total} {percent:.0f}%] {result['file_name']}")
extractor.run(progress_callback=progress_callback)故障排除
问题:内存占用过高
解决:减少并发数
python3 batch_extract.py /path/to/case --workers 2问题:OCR速度慢
解决:使用更快的OCR引擎
- Tesseract(默认)
- Apple Vision(更快,但仅限macOS)
- 在线OCR API
问题:某些文件处理失败
查看: 1. 检查batch_extract_summary.json 2. 单独处理失败的文件 3. 检查文件格式是否支持
技术细节
进程池 vs 线程池
# 进程池(CPU密集型)
with ProcessPoolExecutor(max_workers=3) as executor:
futures = [executor.submit(extract, f) for f in files]
# 线程池(I/O密集型)
with ThreadPoolExecutor(max_workers=5) as executor:
futures = [executor.submit(extract, f) for f in files]超时处理
future = executor.submit(extract, file_path)
try:
result = future.result(timeout=120) # 120秒超时
except TimeoutError:
future.cancel()
result = {"success": False, "error": "超时"}版本历史
- v1.0 - 初始版本(2026-05-16)
- 进程池并行
- 错误处理和重试
- 进度显示
- 性能对比
Claude Code agents safety note
This agents/ subtree was synced from goacheng001/case-os main on 2026-05-19 and path-adapted from Claude Code to Claude Code.
The files are installed as opt-in source files only. Do not run install.sh, agent_manager.sh start, or load any LaunchAgent unless the user explicitly asks to enable that specific agent.
Sensitive boundaries:
court-sms-monitorreads~/Library/SMS/sms.db.court-sms-monitorcan send data to Feishu whenFEISHU_BOT_WEBHOOKor Feishu table credentials are configured.weekly-scancan create a scheduled LaunchAgent.- Feishu tokens must come from environment variables; no default app token should be kept in source.
法院短信实时监控Agent
工作定位
角色:实时监控Agent(监控iOS短信数据库) 触发:实时(通过fswatch监控SMS数据库变化) 目标:发现法院短信后立即提醒,并同步到飞书多维表格
监控内容
关键词识别
| 类型 | 关键词 | 提取信息 |
|---|---|---|
| 开庭通知 | 开庭、庭审、传票、出庭 | 开庭时间、法院、案号、法庭 |
| 举证通知 | 举证、证据交换、证据材料 | 举证截止日期、提交方式 |
| 缴费通知 | 缴费、诉讼费、受理费 | 缴费截止日期、缴费金额、方式 |
| 判决送达 | 判决、裁定、送达 | 送达日期、上诉期限(15天) |
数据来源
iOS短信数据库:
~/Library/SMS/sms.db注意:需要用户授权访问短信数据库
工作流程
第一步:监控短信数据库
使用fswatch监控SMS数据库变化:
fswatch -o ~/Library/SMS/sms.db | xargs -n1 -I{} python3 monitor.py第二步:解析新短信
读取新增短信,识别法院相关内容:
def parse_court_sms(content):
"""解析法院短信"""
if contains_keywords(content, ["开庭", "庭审"]):
return extract_court_info(content, type="hearing")
elif contains_keywords(content, ["举证", "证据"]):
return extract_deadline(content, type="evidence")
# ...第三步:写入案件档案
将解析结果写入对应案件的_archive/court-sms.json:
{
"messages": [
{
"id": "msg_001",
"type": "开庭通知",
"content": "原告XXX诉被告XXX...",
"extracted": {
"court": "XX法院",
"date": "2026-05-20 14:00",
"case_number": "(2026)京01民初123号"
},
"received_at": "2026-05-16 10:30",
"deadline": "2026-05-20 14:00"
}
]
}第四步:同步到飞书多维表格
调用飞书API,写入飞书日程任务中枢:
def sync_to_feishu(court_info):
"""同步到飞书多维表格"""
feishu_api.add_record({
"案件名称": court_info["case_name"],
"提醒类型": "开庭通知",
"截止时间": court_info["date"],
"详情": f"{court_info['court']} - {court_info['case_number']}",
"状态": "待处理"
})第五步:发送提醒
多渠道提醒: 1. 桌面通知:macOS原生通知 2. 飞书消息:推送到指定群 3. 案件仪表盘:更新仪表盘状态
提醒规则
紧急程度分类
| 类型 | 紧急度 | 提前提醒频率 |
|---|---|---|
| 开庭通知 | 🚨 最高 | 提前3天、提前1天、当天早上 |
| 举证期限 | ⚠️ 高 | 提前3天、提前1天、截止当天 |
| 缴费通知 | ⚠️ 高 | 提前3天、提前1天、截止当天 |
| 判决送达(上诉期) | ⚠️ 高 | 收到当天、第10天、第13天 |
上诉期特别监控
判决书送达后,自动计算上诉期:
def calculate_appeal_period(delivery_date):
"""计算上诉期"""
appeal_deadline = delivery_date + timedelta(days=15)
return {
"delivery_date": delivery_date,
"appeal_deadline": appeal_deadline,
"remaining_days": 15
}案件匹配
自动识别案件
通过短信内容识别案件:
def identify_case(sms_content):
"""识别短信属于哪个案件"""
# 方法1:通过案号匹配
case_number = extract_case_number(sms_content)
# 方法2:通过当事人名称匹配
parties = extract_parties(sms_content)
# 方法3:通过法院名称匹配
court = extract_court(sms_content)
return find_matching_case(case_number, parties, court)未匹配短信处理
如果无法识别案件,创建"未分类"记录并人工确认:
{
"type": "unmatched",
"content": "短信原文",
"received_at": "2026-05-16 10:30",
"needs_review": true
}配置文件
# config/feishu.yaml
feishu:
app_token: "<FEISHU_APP_TOKEN>" # 飞书日程任务中枢
table_id: "tblxxxxxx"
view_id: "vewxxxxxx"
sms_monitor:
watch_path: "~/Library/SMS/sms.db"
ignore_keywords: ["验证码", "优惠", "促销"]
notifications:
desktop: true
feishu_group: "ou_xxxxxx" # 飞书群ID错误处理
- 短信数据库锁定:等待后重试
- 解析失败:记录原文,人工复核
- 飞书API失败:本地缓存,稍后重试
- 案件匹配失败:创建未分类记录
版本历史
- v1.0 - 初始版本(2026-05-16)
飞书配置说明
飞书机器人Webhook获取
1. 创建飞书机器人
1. 打开飞书 2. 进入群聊(或创建新群) 3. 点击群设置 → 群机器人 → 添加机器人 4. 选择"自定义机器人" 5. 设置机器人名称和头像 6. 获取Webhook URL
2. 配置Webhook
将Webhook URL添加到环境变量:
# 方式1:临时设置(当前会话)
export FEISHU_BOT_WEBHOOK="https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# 方式2:永久设置(写入shell配置)
echo 'export FEISHU_BOT_WEBHOOK="https://open.feishu.cn/open-apis/bot/v2/hook/xxx"' >> ~/.zshrc
source ~/.zshrc飞书多维表格配置
1. 获取App Token
1. 打开飞书多维表格 2. 点击表格右上角"..." 3. 选择"获取表格链接" 4. 从链接中提取App Token:
https://example.feishu.cn/base/APP_TOKEN/...2. 配置App Token
export FEISHU_APP_TOKEN="<FEISHU_APP_TOKEN>"3. 创建表格字段(可选)
在多维表格中创建以下字段:
| 字段名 | 类型 | 说明 |
|---|---|---|
| 案件名称 | 文本 | 案件名称 |
| 提醒类型 | 单选 | 开庭通知/举证期限/缴费通知/判决送达 |
| 截止时间 | 日期 | 截止日期时间 |
| 法院 | 文本 | 法院名称 |
| 案号 | 文本 | 案件编号 |
| 短信内容 | 文本 | 短信原文 |
| 收到时间 | 日期 | 收到短信的时间 |
| 状态 | 单选 | 待处理/已处理/已忽略 |
| 创建时间 | 日期 | 记录创建时间 |
测试配置
测试机器人消息
# 发送测试消息
curl -X POST "$FEISHU_BOT_WEBHOOK" \
-H "Content-Type: application/json" \
-d '{
"msg_type": "text",
"content": {"text": "测试消息:法院短信监控Agent已配置"}
}'测试短信监控
# 手动运行短信监控
python3 ~/.claude/skills/case-os/agents/court-sms-monitor/monitor.py故障排除
问题:机器人消息发送失败
检查: 1. Webhook URL是否正确 2. 网络连接是否正常 3. 飞书群是否存在
解决:
# 测试Webhook
curl -v -X POST "$FEISHU_BOT_WEBHOOK" \
-H "Content-Type: application/json" \
-d '{"msg_type":"text","content":{"text":"测试"}}'问题:多维表格写入失败
原因:多维表格写入需要飞书开放平台API权限
当前状态:基础版本中,多维表格写入功能为预留接口
后续实现:
- 飞书开放平台应用认证
- OAuth授权流程
- API调用实现
临时解决方案
如果不配置飞书,Agent仍然可以:
- ✅ 保存到案件档案(
_archive/court-sms.json) - ✅ 发送macOS桌面通知
- ❌ 不发送飞书消息(跳过)
#!/usr/bin/env python3
"""
飞书通知模块 - 通过飞书机器人发送消息和写入多维表格
"""
import json
import os
import requests
from datetime import datetime
from typing import Dict, List, Optional
# 飞书配置(从环境变量读取)
FEISHU_BOT_WEBHOOK = os.getenv("FEISHU_BOT_WEBHOOK", "") # 飞书机器人Webhook
FEISHU_APP_TOKEN = os.getenv("FEISHU_APP_TOKEN", "") # 飞书多维表格App Token
FEISHU_TABLE_ID = os.getenv("FEISHU_TABLE_ID", "") # 表格ID(可选,默认使用第一个表格)
class FeishuNotifier:
"""飞书通知器"""
def __init__(self):
self.webhook = FEISHU_BOT_WEBHOOK
self.app_token = FEISHU_APP_TOKEN
self.table_id = FEISHU_TABLE_ID
def send_card(self, title: str, content: Dict, urgent: bool = False):
"""发送飞书卡片消息
Args:
title: 卡片标题
content: 卡片内容
urgent: 是否紧急(红色标签)
"""
if not self.webhook:
print("⚠️ 飞书机器人Webhook未配置,跳过发送")
return
# 构建卡片内容
card = {
"msg_type": "interactive",
"card": {
"header": {
"title": {
"tag": "plain_text",
"content": title,
},
"template": urgent and "red" or "yellow",
},
"elements": [
{
"tag": "div",
"text": {
"tag": "lark_md",
"content": self._format_content(content),
}
}
],
}
}
# 发送
try:
response = requests.post(
self.webhook,
json=card,
headers={"Content-Type": "application/json"},
)
response.raise_for_status()
print(f"✅ 飞书消息已发送:{title}")
except Exception as e:
print(f"❌ 飞书消息发送失败:{e}")
def _format_content(self, content: Dict) -> str:
"""格式化卡片内容为Markdown"""
lines = []
for key, value in content.items():
if isinstance(value, list):
lines.append(f"**{key}**")
for item in value:
lines.append(f" • {item}")
else:
lines.append(f"**{key}**:{value}")
return "\n\n".join(lines)
def write_to_table(self, record: Dict) -> bool:
"""写入飞书多维表格
Args:
record: 记录数据
Returns:
是否成功
"""
if not self.app_token:
print("⚠️ 飞书多维表格App Token未配置,跳过写入")
return False
# TODO: 实现飞书多维表格写入
# 需要使用飞书开放平台API
print(f"📝 写入飞书多维表格:{record.get('案件名称', '未知案件')}")
return True
class CourtSMSFeishuNotifier(FeishuNotifier):
"""法院短信飞书通知器"""
def send_court_sms(self, sms_info: Dict, case_name: str):
"""发送法院短信通知
Args:
sms_info: 短信信息
case_name: 案件名称
"""
extracted = sms_info.get("extracted", {})
msg_type = self._get_type_label(sms_info["type"])
# 判断紧急程度
urgent = sms_info["type"] in ["hearing", "payment"]
# 构建卡片内容
content = {
"案件": case_name,
}
if extracted.get("court"):
content["法院"] = extracted["court"]
if extracted.get("case_number"):
content["案号"] = extracted["case_number"]
if extracted.get("date"):
date_text = extracted["date"]
if extracted.get("appeal_deadline"):
date_text = f"上诉期截止:{extracted['appeal_deadline']}"
content["时间"] = date_text
if extracted.get("datetime"):
content["具体时间"] = extracted["datetime"]
# 短信原文摘要
content["短信摘要"] = sms_info["content"][:100] + "..."
# 发送卡片
title = f"🏛️ 法院短信提醒:{msg_type}"
self.send_card(title, content, urgent=urgent)
# 写入多维表格
self._write_court_sms_to_table(sms_info, case_name)
def _write_court_sms_to_table(self, sms_info: Dict, case_name: str):
"""写入法院短信到多维表格"""
extracted = sms_info.get("extracted", {})
record = {
"案件名称": case_name,
"提醒类型": self._get_type_label(sms_info["type"]),
"短信类型": sms_info["type"],
"截止时间": extracted.get("deadline") or extracted.get("datetime") or "",
"日期": extracted.get("date") or "",
"法院": extracted.get("court") or "",
"案号": extracted.get("case_number") or "",
"短信内容": sms_info["content"],
"收到时间": sms_info["received_at"],
"状态": "待处理",
"创建时间": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
}
# 如果有上诉期,单独记录
if extracted.get("appeal_deadline"):
record["上诉期截止"] = extracted["appeal_deadline"]
self.write_to_table(record)
def _get_type_label(self, msg_type: str) -> str:
"""获取类型标签"""
labels = {
"hearing": "开庭通知",
"evidence": "举证期限",
"payment": "缴费通知",
"judgment": "判决送达",
}
return labels.get(msg_type, "其他")
#!/bin/bash
# 法院短信实时监控Agent - 安装脚本
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
AGENT_DIR="$SCRIPT_DIR"
MONITOR_SCRIPT="$AGENT_DIR/monitor.py"
PLIST_FILE="$HOME/Library/LaunchAgents/com.claude.caseos.sms-monitor.log"
echo "🔧 安装法院短信实时监控Agent..."
# 检查Python3
if ! command -v python3 &> /dev/null; then
echo "❌ 错误:未找到python3"
exit 1
fi
# 检查fswatch
if ! command -v fswatch &> /dev/null; then
echo "📦 正在安装fswatch..."
if command -v brew &> /dev/null; then
brew install fswatch
else
echo "❌ 错误:未找到Homebrew,请手动安装fswatch"
echo " 安装命令:brew install fswatch"
exit 1
fi
fi
# 确保脚本可执行
chmod +x "$MONITOR_SCRIPT"
# 创建日志目录
LOG_DIR="$HOME/.claude/skills/case-os/data"
mkdir -p "$LOG_DIR"
# 创建监控脚本(使用fswatch)
WATCH_SCRIPT="$LOG_DIR/sms-monitor-wrapper.sh"
cat > "$WATCH_SCRIPT" << 'EOFSCRIPT'
#!/bin/bash
# 法院短信监控包装脚本 - 由LaunchAgent调用
SMS_DB_PATH="$HOME/Library/SMS/sms.db"
LOG_DIR="$HOME/.claude/skills/case-os/data"
MONITOR_SCRIPT="$HOME/.claude/skills/case-os/agents/court-sms-monitor/monitor.py"
echo "=== 法院短信监控Agent启动 ===" >> "$LOG_DIR/sms-monitor.log"
echo "启动时间:$(date)" >> "$LOG_DIR/sms-monitor.log"
echo "监控目标:$SMS_DB_PATH" >> "$LOG_DIR/sms-monitor.log"
# 监控短信数据库变化
fswatch -o "$SMS_DB_PATH" | while read event; do
echo "=== 检测到短信数据库变化 ===" >> "$LOG_DIR/sms-monitor.log"
echo "时间:$(date)" >> "$LOG_DIR/sms-monitor.log"
# 调用监控脚本
python3 "$MONITOR_SCRIPT" >> "$LOG_DIR/sms-monitor.log" 2>&1
echo "=== 处理完成 ===" >> "$LOG_DIR/sms-monitor.log"
done
EOFSCRIPT
chmod +x "$WATCH_SCRIPT"
# 创建LaunchAgent配置文件
PLIST_FILE="$HOME/Library/LaunchAgents/com.claude.caseos.sms-monitor.plist"
cat > "$PLIST_FILE" << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.claude.caseos.sms-monitor</string>
<key>ProgramArguments</key>
<array>
<string>$WATCH_SCRIPT</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>$LOG_DIR/sms-monitor-stdout.log</string>
<key>StandardErrorPath</key>
<string>$LOG_DIR/sms-monitor-stderr.log</string>
</dict>
</plist>
EOF
echo "✅ LaunchAgent配置文件已创建:$PLIST_FILE"
# 加载LaunchAgent
echo "📝 正在加载LaunchAgent..."
launchctl unload "$PLIST_FILE" 2>/dev/null || true
launchctl load "$PLIST_FILE"
echo "✅ LaunchAgent已加载"
echo ""
echo "📋 调度信息:"
echo " - 运行模式:实时监控(fswatch)"
echo " - 监控目标:~/Library/SMS/sms.db"
echo " - 日志路径:~/.claude/skills/case-os/data/sms-monitor*.log"
echo ""
echo "🧪 手动测试:"
echo " python3 $MONITOR_SCRIPT"
echo ""
echo "📝 卸载命令:"
echo " launchctl unload $PLIST_FILE"
echo " rm $PLIST_FILE"
echo " rm $WATCH_SCRIPT"
#!/usr/bin/env python3
"""
法院短信实时监控Agent - 实现脚本
监控iOS短信数据库,发现法院相关短信后实时提醒并同步到飞书
"""
import json
import os
import re
import sqlite3
import sys
from datetime import datetime, timedelta
from pathlib import Path
from typing import Dict, List, Optional, Any
# 导入飞书通知器
try:
from feishu_notifier import CourtSMSFeishuNotifier
except ImportError:
print("⚠️ 警告:未找到飞书通知器,飞书功能将不可用")
CourtSMSFeishuNotifier = None
# ==================== 配置 ====================
SMS_DB_PATH = Path.home() / "Library" / "SMS" / "sms.db"
SCAN_ROOTS = [
Path.home() / "Documents" / Documents / cases / "案件",
]
ARCHIVE_DIR = "_archive"
COURT_SMS_FILE = "court-sms.json"
STATE_FILE = Path.home() / ".claude" / "skills" / "case-os" / "data" / "sms-monitor-state.json"
# 飞书配置(从环境变量读取)
FEISHU_APP_TOKEN = os.getenv("FEISHU_APP_TOKEN", "")
# 关键词配置
KEYWORDS = {
"hearing": ["开庭", "庭审", "传票", "出庭", "听证"],
"evidence": ["举证", "证据交换", "证据材料", "提交证据"],
"payment": ["缴费", "诉讼费", "受理费", "预交"],
"judgment": ["判决", "裁定", "送达", "裁判文书"],
}
IGNORE_KEYWORDS = ["验证码", "优惠", "促销", "活动", "中奖"]
# ==================== 短信解析 ====================
class SMSParser:
"""短信解析器"""
@staticmethod
def extract_case_number(text: str) -> Optional[str]:
"""提取案号"""
patterns = [
r'[(\(]?\d{4}[)\)]?[^\d]+民[一-龥]{1,3}?\d+号',
r'\(\d{4}\)[^\d]+民[一-龥]{1,3}?\d+号',
]
for pattern in patterns:
match = re.search(pattern, text)
if match:
return match.group(0)
return None
@staticmethod
def extract_date(text: str) -> Optional[str]:
"""提取日期"""
patterns = [
r'(\d{4})年(\d{1,2})月(\d{1,2})日',
r'(\d{1,2})月(\d{1,2})日',
r'(\d{4})-(\d{1,2})-(\d{1,2})',
]
for pattern in patterns:
matches = re.findall(pattern, text)
if matches:
match = matches[0]
if len(match) == 3:
year, month, day = match
if len(year) == 4:
return f"{year}-{month.zfill(2)}-{day.zfill(2)}"
else:
current_year = datetime.now().year
return f"{current_year}-{month.zfill(2)}-{day.zfill(2)}"
return None
@staticmethod
def extract_time(text: str) -> Optional[str]:
"""提取时间"""
patterns = [
r'(\d{1,2}):(\d{2})',
r'(\d{1,2})点(\d{1,2})分',
]
for pattern in patterns:
match = re.search(pattern, text)
if match:
hour, minute = match.groups()
return f"{hour.zfill(2)}:{minute.zfill(2)}"
return None
@staticmethod
def extract_court(text: str) -> Optional[str]:
"""提取法院名称"""
court_patterns = [
r'[^,。]+?法院',
r'[^,。]+?法庭',
]
for pattern in court_patterns:
match = re.search(pattern, text)
if match:
return match.group(0)
return None
@staticmethod
def classify_sms(text: str) -> Optional[str]:
"""分类短信类型"""
for msg_type, keywords in KEYWORDS.items():
if any(keyword in text for keyword in keywords):
return msg_type
return None
class CourtSMSMonitor:
"""法院短信监控器"""
def __init__(self):
self.parser = SMSParser()
self.state = self.load_state()
self.feishu_notifier = CourtSMSFeishuNotifier() if CourtSMSFeishuNotifier else None
def load_state(self) -> Dict:
"""加载监控状态"""
if STATE_FILE.exists():
try:
return json.loads(STATE_FILE.read_text(encoding="utf-8"))
except (json.JSONDecodeError, IOError):
pass
return {"last_sms_id": 0, "processed_ids": []}
def save_state(self):
"""保存监控状态"""
STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
STATE_FILE.write_text(json.dumps(self.state, ensure_ascii=False, indent=2), encoding="utf-8")
def get_new_messages(self) -> List[Dict]:
"""获取新短信"""
if not SMS_DB_PATH.exists():
print(f"⚠️ 短信数据库不存在:{SMS_DB_PATH}")
return []
try:
conn = sqlite3.connect(str(SMS_DB_PATH))
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
# 查询新短信
query = """
SELECT rowid, date, address, text
FROM message
WHERE rowid > ?
ORDER BY date DESC
LIMIT 50
"""
cursor.execute(query, (self.state["last_sms_id"],))
messages = [dict(row) for row in cursor.fetchall()]
conn.close()
return messages
except sqlite3.Error as e:
print(f"❌ 数据库错误:{e}")
return []
def is_court_sms(self, text: str) -> bool:
"""判断是否为法院短信"""
# 检查忽略关键词
if any(keyword in text for keyword in IGNORE_KEYWORDS):
return False
# 检查法院关键词
msg_type = self.parser.classify_sms(text)
return msg_type is not None
def parse_court_sms(self, sms: Dict) -> Optional[Dict]:
"""解析法院短信"""
text = sms.get("text", "")
if not self.is_court_sms(text):
return None
msg_type = self.parser.classify_sms(text)
parsed = {
"id": f"msg_{sms['rowid']}",
"type": msg_type,
"content": text,
"sender": sms.get("address", "未知"),
"received_at": datetime.fromtimestamp(sms["date"] / 1000000000).isoformat(),
"extracted": {},
}
# 提取关键信息
case_number = self.parser.extract_case_number(text)
if case_number:
parsed["extracted"]["case_number"] = case_number
court = self.parser.extract_court(text)
if court:
parsed["extracted"]["court"] = court
date_str = self.parser.extract_date(text)
time_str = self.parser.extract_time(text)
if date_str:
if time_str:
parsed["extracted"]["datetime"] = f"{date_str} {time_str}"
else:
parsed["extracted"]["date"] = date_str
# 计算截止时间
try:
if msg_type == "judgment":
# 判决书:上诉期15天
delivery_date = datetime.strptime(date_str, "%Y-%m-%d")
deadline = delivery_date + timedelta(days=15)
parsed["extracted"]["appeal_deadline"] = deadline.strftime("%Y-%m-%d")
parsed["deadline"] = deadline.strftime("%Y-%m-%d")
else:
# 其他:使用提取的日期
parsed["deadline"] = date_str
except ValueError:
pass
return parsed
def identify_case(self, sms_info: Dict) -> Optional[Path]:
"""识别短信属于哪个案件"""
extracted = sms_info.get("extracted", {})
case_number = extracted.get("case_number")
court = extracted.get("court")
# 遍历案件文件夹
for root in SCAN_ROOTS:
if not root.exists():
continue
for case_dir in root.rglob("CLAUDE.md"):
case_path = case_dir.parent
# 读取案件信息
claude_md = case_path / "CLAUDE.md"
if claude_md.exists():
content = claude_md.read_text(encoding="utf-8")
# 匹配案号
if case_number and case_number in content:
return case_path
# 匹配法院
if court and court in content:
return case_path
return None
def save_to_archive(self, case_path: Path, sms_info: Dict):
"""保存到案件档案"""
archive_dir = case_path / ARCHIVE_DIR
archive_dir.mkdir(parents=True, exist_ok=True)
court_sms_file = archive_dir / COURT_SMS_FILE
# 读取现有数据
if court_sms_file.exists():
try:
data = json.loads(court_sms_file.read_text(encoding="utf-8"))
except (json.JSONDecodeError, IOError):
data = {"messages": []}
else:
data = {"messages": []}
# 添加新短信
data["messages"].append(sms_info)
# 保存
court_sms_file.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
def sync_to_feishu(self, sms_info: Dict, case_name: str):
"""同步到飞书(机器人消息 + 多维表格)"""
if not self.feishu_notifier:
print("⚠️ 飞书通知器未配置,跳过同步")
return
try:
# 发送飞书机器人消息(卡片形式)
# 并写入多维表格
self.feishu_notifier.send_court_sms(sms_info, case_name)
except Exception as e:
print(f"❌ 飞书同步失败:{e}")
def get_type_label(self, msg_type: str) -> str:
"""获取类型标签"""
labels = {
"hearing": "开庭通知",
"evidence": "举证期限",
"payment": "缴费通知",
"judgment": "判决送达",
}
return labels.get(msg_type, "其他")
def send_notification(self, sms_info: Dict, case_name: str):
"""发送通知"""
msg_type = self.get_type_label(sms_info["type"])
extracted = sms_info.get("extracted", {})
# 构建通知内容
title = f"🔔 法院短信提醒:{msg_type}"
message_parts = [
f"案件:{case_name}",
]
if extracted.get("court"):
message_parts.append(f"法院:{extracted['court']}")
if extracted.get("date"):
deadline_str = extracted["date"]
if extracted.get("appeal_deadline"):
deadline_str = f"上诉期截止:{extracted['appeal_deadline']}"
message_parts.append(f"时间:{deadline_str}")
if extracted.get("case_number"):
message_parts.append(f"案号:{extracted['case_number']}")
message = "\n".join(message_parts)
# 发送macOS桌面通知
os.system(f'osascript -e \'display notification "{message}" with title "{title}"\'')
print(f"\n🔔 {title}")
print(message)
print()
def process(self):
"""处理新短信"""
messages = self.get_new_messages()
if not messages:
print("✅ 无新短信")
return
print(f"📱 发现 {len(messages)} 条新短信")
court_sms_count = 0
for sms in messages:
sms_info = self.parse_court_sms(sms)
if not sms_info:
continue
court_sms_count += 1
print(f"\n{'='*50}")
print(f"🏛️ 发现法院短信:{self.get_type_label(sms_info['type'])}")
print(f"内容:{sms_info['content'][:100]}...")
print(f"{'='*50}")
# 识别案件
case_path = self.identify_case(sms_info)
if case_path:
case_name = case_path.name
print(f"✅ 匹配案件:{case_name}")
# 保存到档案
self.save_to_archive(case_path, sms_info)
# 同步到飞书
self.sync_to_feishu(sms_info, case_name)
# 发送通知
self.send_notification(sms_info, case_name)
else:
print(f"⚠️ 未找到匹配案件")
# TODO: 保存到未分类记录
# 标记为已处理
self.state["processed_ids"].append(sms["rowid"])
# 更新状态
if messages:
self.state["last_sms_id"] = max(msg["rowid"] for msg in messages)
self.save_state()
if court_sms_count > 0:
print(f"\n✅ 处理完成:发现 {court_sms_count} 条法院短信")
else:
print(f"\n✅ 处理完成:无法院短信")
def main():
"""主函数"""
print("🔍 法院短信监控Agent启动...")
print(f"📂 监控数据库:{SMS_DB_PATH}")
print(f"📂 扫描案件根目录:{SCAN_ROOTS}")
print()
monitor = CourtSMSMonitor()
monitor.process()
if __name__ == "__main__":
sys.exit(main())
法院短信实时监控Agent
实时监控iOS短信数据库,发现法院相关短信后立即提醒并同步到飞书多维表格
功能概述
监控内容
| 类型 | 关键词 | 提取信息 | 紧急度 |
|---|---|---|---|
| 开庭通知 | 开庭、庭审、传票 | 开庭时间、法院、案号 | 🚨 最高 |
| 举证期限 | 举证、证据交换 | 举证截止日期 | ⚠️ 高 |
| 缴费通知 | 缴费、诉讼费 | 缴费截止日期、金额 | ⚠️ 高 |
| 判决送达 | 判决、裁定、送达 | 送达日期、上诉期(15天) | ⚠️ 高 |
工作流程
收到新短信
↓
识别法院短信(关键词匹配)
↓
提取关键信息(案号、日期、法院)
↓
匹配案件(自动识别属于哪个案件)
↓
保存到案件档案(_archive/court-sms.json)
↓
同步到飞书多维表格
↓
发送桌面通知安装步骤
前置要求
1. Python 3 2. fswatch(文件监控工具) 3. Homebrew(用于安装fswatch)
安装命令
cd ~/.claude/skills/case-os/agents/court-sms-monitor
bash install.sh安装脚本会自动: 1. 检查并安装fswatch 2. 创建LaunchAgent配置 3. 启动实时监控
使用方法
自动运行(推荐)
LaunchAgent会在后台自动运行:
- 实时监控
~/Library/SMS/sms.db - 发现新短信立即处理
- 无需手动操作
手动测试
python3 monitor.py查看日志
# 查看完整日志
tail -f ~/.claude/skills/case-os/data/sms-monitor.log
# 查看标准输出
tail -f ~/.claude/skills/case-os/data/sms-monitor-stdout.log
# 查看错误输出
tail -f ~/.claude/skills/case-os/data/sms-monitor-stderr.log飞书同步
配置
创建环境变量:
export FEISHU_APP_TOKEN="<FEISHU_APP_TOKEN>"或写入~/.bashrc/~/.zshrc:
echo 'export FEISHU_APP_TOKEN="<FEISHU_APP_TOKEN>"' >> ~/.zshrc
source ~/.zshrc写入字段
| 字段 | 说明 |
|---|---|
| 案件名称 | 自动匹配的案件名称 |
| 提醒类型 | 开庭通知/举证期限/缴费通知/判决送达 |
| 截止时间 | 提取的日期时间 |
| 法院 | 提取的法院名称 |
| 案号 | 提取的案号 |
| 详情 | 短信内容摘要 |
| 状态 | 默认"待处理" |
卸载
# 停止LaunchAgent
launchctl unload ~/Library/LaunchAgents/com.claude.caseos.sms-monitor.plist
# 删除配置文件
rm ~/Library/LaunchAgents/com.claude.caseos.sms-monitor.plist
# 删除监控脚本
rm ~/.claude/skills/case-os/data/sms-monitor-wrapper.sh案件匹配逻辑
匹配顺序
1. 通过案号匹配:最准确 2. 通过法院名称匹配:次选 3. 通过当事人名称匹配:备选
未匹配处理
如果无法识别案件,会: 1. 在日志中记录 2. 显示"未找到匹配案件"警告 3. TODO:保存到未分类记录供人工确认
提醒规则
开庭通知
- 提前3天:第一次提醒
- 提前1天:第二次提醒
- 当天早上:第三次提醒
举证期限
- 提前3天:第一次提醒
- 提前1天:第二次提醒
- 截止当天:第三次提醒
判决送达(上诉期)
- 收到当天:立即提醒
- 第10天:提醒上诉期过半
- 第13天:最后提醒(还剩2天)
技术实现
文件监控
使用fswatch监控短信数据库变化:
fswatch -o ~/Library/SMS/sms.db | xargs -n1 python3 monitor.py短信解析
- 正则表达式提取案号、日期、时间、法院
- 关键词匹配识别短信类型
- 自动计算上诉期(判决+15天)
数据存储
- 监控状态:
~/.claude/skills/case-os/data/sms-monitor-state.json - 案件档案:
<案件路径>/_archive/court-sms.json
安全设计
只读访问
- 只读取短信数据库,不修改
- 不访问短信内容之外的敏感数据
本地处理
- 所有处理在本地完成
- 短信内容不会上传到外部服务器
权限控制
- macOS需要授权访问短信数据库
- 首次运行时系统会提示授权
故障排除
问题:无法读取短信数据库
原因:没有授权访问短信
解决: 1. 系统偏好设置 → 隐私与安全性 → 完全磁盘访问权限 2. 添加Terminal或允许Python访问
问题:fswatch未安装
解决:
brew install fswatch问题:飞书同步失败
检查: 1. 环境变量是否设置:echo $FEISHU_APP_TOKEN 2. 网络连接是否正常 3. 飞书API权限是否正确
版本历史
- v1.0 - 初始版本(2026-05-16)
- 实时监控短信数据库
- 4种短信类型识别
- 自动案件匹配
- 飞书多维表格同步
- macOS桌面通知
案件OS多Agent并行运行报告
🎯 当前运行的Agent
| Agent名称 | 类型 | 调度方式 | 状态 |
|---|---|---|---|
| weekly-scan | 案件周度扫描Agent | 每周一早上8点 | ✅ 运行中 |
| court-sms-monitor | 法院短信实时监控Agent | 实时监控(fswatch) | ✅ 运行中 |
| batch-evidence | 批量证据提取Agent | 手动触发 | 🔄 待命 |
🚀 并行运行架构
┌──────────────────────────────────────────────────────────┐
│ 案件OS多Agent系统 │
├──────────────────────────────────────────────────────────┤
│ │
│ 📅 定时监控层 │
│ ├─ weekly-scan: 每周一8点扫描所有案件 │
│ │ └─ 生成待办报告 → 桌面通知 │
│ │ │
│ 📡 实时监控层 │
│ └─ court-sms-monitor: 实时监控短信数据库 │
│ └─ 发现法院短信 → 飞书机器人 + 多维表格 + 桌面通知 │
│ │
│ ⚡ 执行层(手动触发) │
│ └─ batch-evidence: 批量证据提取(3倍加速) │
│ │
└──────────────────────────────────────────────────────────┘📋 Agent详细说明
1. 案件周度扫描Agent (weekly-scan)
功能:每周一早上8点自动扫描所有案件,生成待办报告
扫描维度:
- 🚨 开庭提醒(7天内)
- ⚠️ 证据缺口
- ⚠️ 待办事项
- ⚠️ 久未更新(7天+)
- 📄 新增材料
输出:
- 报告文件:
~/Desktop/案件周度扫描报告-YYYY-MM-DD.md - 日志:
~/.claude/skills/case-os/data/launchd-*.log
2. 法院短信实时监控Agent (court-sms-monitor)
功能:实时监控iOS短信数据库,发现法院相关短信后立即提醒
监控类型:
- 🚨 开庭通知(开庭时间、法院、案号)
- ⚠️ 举证期限(举证截止日期)
- ⚠️ 缴费通知(缴费截止日期、金额)
- ⚠️ 判决送达(送达日期、上诉期15天)
输出:
- 🤖 飞书机器人卡片消息
- 📊 飞书多维表格记录
- 🖥️ macOS桌面通知
- 📁 案件档案:
<案件路径>/_archive/court-sms.json
3. 批量证据提取Agent (batch-evidence)
功能:多个证据文件同时提取,大幅加速A5步骤
性能:
- 3个文件:6分钟 → 2分钟(3倍加速)
- 10个文件:20分钟 → 7分钟(2.86倍加速)
使用:
python3 ~/.claude/skills/case-os/agents/batch-evidence/batch_extract.py /path/to/case/dir🔧 管理命令
查看状态
# 使用Agent管理器
bash ~/.claude/skills/case-os/agents/agent_manager.sh status
# 使用别名(需要先 source ~/.zshrc)
caseos-status启动/停止/重启
# 启动所有Agent
caseos-start
# 停止所有Agent
caseos-stop
# 重启所有Agent
caseos-restart查看日志
# 周度扫描日志
tail -f ~/.claude/skills/case-os/data/launchd-stdout.log
# 短信监控日志
tail -f ~/.claude/skills/case-os/data/sms-monitor.log测试Agent
# 测试周度扫描
python3 ~/.claude/skills/case-os/agents/weekly-scan/scan.py
# 测试短信监控
python3 ~/.claude/skills/case-os/agents/court-sms-monitor/monitor.py🚨 监控覆盖
时间敏感事项
| 事项 | 监控方式 | 提前提醒 |
|---|---|---|
| 开庭通知 | 实时短信监控 | 3天、1天、当天 |
| 举证期限 | 实时短信监控 | 3天、1天、当天 |
| 缴费通知 | 实时短信监控 | 3天、1天、当天 |
| 上诉期 | 实时短信监控 | 收到当天、第10天、第13天 |
定期扫描事项
| 事项 | 扫描频率 | 发现方式 |
|---|---|---|
| 证据缺口 | 每周 | 证据卡片库扫描 |
| 久未更新 | 每周 | CLAUDE.md修改时间 |
| 待办事项 | 每周 | 阶段追踪.md检查 |
| 新增材料 | 每周 | 文件系统扫描 |
📊 工作流程
典型工作流
1. 收到法院短信
↓
2. court-sms-monitor实时发现
↓
3. 立即发送飞书机器人消息
↓
4. 写入飞书多维表格
↓
5. 发送macOS桌面通知
↓
6. 保存到案件档案1. 每周一早上8点
↓
2. weekly-scan自动运行
↓
3. 扫描所有案件状态
↓
4. 生成待办报告
↓
5. 保存到桌面1. 需要提取证据时
↓
2. 手动调用batch-evidence
↓
3. 并行处理多个证据
↓
4. 生成证据卡片
↓
5. 完成时间缩短3倍🔮 后续扩展
计划中的Agent
- [ ] 法条幻觉校验并行Agent(S2+S10)
- [ ] 文件实时监控Agent(fswatch)
- [ ] 裁判文书网监控Agent
- [ ] Agent编排器(统一管理)
---
报告生成时间:2026-05-16 20:30 Agent版本:v1.1 系统状态:🟢 正常运行
display_name: "案件OS"
short_description: "全局案件管理层:自动初始化案件三文件、恢复上下文并在任务后写回结论。"
default_prompt: |
你是“案件OS(case-os)”,用于中国律师办案流程中的全局案件管理。
你的固定职责:
1) 初始化案件工作区并补建核心文件;
2) 在任务前恢复案件上下文;
3) 在任务后写回关键结论;
4) 维护可直接执行的任务面板。
进入任一案件目录后,必须先检查并确保以下文件存在:
- 00_案件概览.md
- 01_任务面板.md
- 99_工作日志.md
如文件缺失,立即补建并写入基础内容。
初始化触发语包括但不限于:
- 初始化本案件
- 初始化这个案子
- 开始整理本案
- 建立案件工作区
- 启用案件OS
初始化必须执行:
1. 扫描当前案件目录及子目录;
2. 识别基础案情与关键材料;
3. 生成或更新三核心文件;
4. 追加第一条或最新一条工作日志。
材料扫描规则:
- 文件类型优先:PDF > DOCX > TXT > Markdown > 图片
- 目录优先:立案材料/ > materials/ > evidence/ > 根目录 > 其他子目录
- 关键文件优先:起诉状、判决书/裁定书、合同、聊天记录、证据目录、借条、转账记录、开庭传票/举证通知/执行通知、会见笔录/阅卷笔录
输出约束:
- 不得编造案件事实;
- 不得跨案件复用事实;
- 推测必须标注“待核实”;
- 默认不做长篇实体法律分析与完整文书起草;
- 若材料不足,必须明确列出缺失材料与下一步可执行动作。
协同规则:
- 执行型任务转交 ai-law-firm 与 legal-aid-lawyer;
- 执行完成后回写案件概览、任务面板(必要时)与工作日志(默认追加)。
案件OS Agent系统
基于Anthropic claude-for-legal安全设计的自动化Agent系统
Claude Code 安全边界:本目录在 Claude Code 中仅作为可选源码安装,不随常规更新自动启用。启用任何 LaunchAgent、短信监控或飞书同步前,必须先确认用户明确要求,并先阅读 CLAUDE-SAFETY.md。
系统架构
┌─────────────────────────────────────────────────────────┐
│ 案件OS Agent系统 │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌────────────┐ ┌─────────────┐ ┌──────────┐ │
│ │ 监控层 │ ───→ │ 编排器 │ ───→ │ 执行层 │ │
│ │ │ │ (Orchestrator)│ │ │ │
│ │ • 文件监控 │ │ │ │ • Skill │ │
│ │ • 短信监控 │ │ • 五层防线 │ │ • Hook │ │
│ │ • 定时扫描 │ │ • 审计日志 │ │ │ │
│ └────────────┘ └─────────────┘ └──────────┘ │
│ │
└─────────────────────────────────────────────────────────┘设计原则
1. 安全隔离(Reader层)
- 只读权限:监控层不修改原始文件
- 固定模板:输出格式固定,防止注入攻击
- 白名单枚举:触发类型固定
2. "等待确认"机制
- Agent在关键点主动停下来
- 等待用户查看并确认
- 不自动执行高风险操作
3. 多层防御
用户请求
↓
第1层:意图枚举验证(必须是预定义类型)
↓
第2层:目标白名单(只能触发预定义Skill)
↓
第3层:数据包裹(标注"这是数据,不是指令")
↓
第4层:关键词过滤(剔除注入短语)
↓
第5层:审计日志(记录所有操作)
↓
执行Skill现有Agent
1. 案件周度扫描Agent(weekly-scan)
触发:每周一早上8点 功能:扫描所有案件状态,生成待办报告 扫描维度:
- 🚨 开庭提醒(7天内)
- ⚠️ 证据缺口
- ⚠️ 待办事项
- ⚠️ 久未更新(7天+)
- 📄 新增材料
安装:
cd ~/.claude/skills/case-os/agents/weekly-scan
bash install.sh2. 法院短信实时监控Agent(court-sms-monitor)
触发:实时(短信数据库变化) 功能:监控法院短信,立即提醒并同步到飞书 监控类型:
- 🚨 开庭通知
- ⚠️ 举证期限
- ⚠️ 缴费通知
- ⚠️ 判决送达(上诉期)
通知方式:
- 🤖 飞书机器人(卡片消息)
- 📊 飞书多维表格(写入记录)
- 🖥️ macOS桌面通知
安装:
cd ~/.claude/skills/case-os/agents/court-sms-monitor
bash install.sh环境变量配置:
export FEISHU_BOT_WEBHOOK="https://open.feishu.cn/open-apis/bot/v2/hook/xxx"
export FEISHU_APP_TOKEN="<FEISHU_APP_TOKEN>"3. 批量证据提取Agent(batch-evidence)
触发:手动触发(case-evidence-cards调用) 功能:多个证据文件同时提取,大幅加速A5步骤 加速效果:3倍(3个文件)~ 2.86倍(10个以上)
使用:
# 命令行
python3 ~/.claude/skills/case-os/agents/batch-evidence/batch_extract.py /path/to/case/dir
# 或在Skill中集成性能对比:
| 证据数量 | 串行耗时 | 并行耗时 | 加速比 |
|---|---|---|---|
| 3个 | 6分钟 | 2分钟 | 3x |
| 10个 | 20分钟 | 7分钟 | 2.86x |
与案件OS的关系
定位
监控层(Agent) 发现问题
↓ 提醒
执行层(Skill) 解决问题- Agent不替代Skill:只负责监控和提醒
- Agent指导Skill调用:扫描结果告诉用户该调用哪个Skill
- Skill保持手动触发:确保律师的控制权
协作流程
1. Agent发现问题
↓
2. Agent生成报告/提醒
↓
3. 用户查看确认
↓
4. 用户手动调用对应Skill
↓
5. Skill执行具体操作扩展方向
短期(已实现)
- [x] 案件周度扫描Agent(定时监控)
- [x] 法院短信实时监控Agent(飞书机器人+多维表格)
- [x] 批量证据提取Agent(Phase A内部并行)
中期(规划中)
- [ ] Agent编排器实现
- [ ] 法条幻觉校验并行Agent(S2+S10)
- [ ] 文件实时监控Agent(fswatch)
长期(探索中)
- [ ] 裁判文书网监控Agent
- [ ] 工商信息变更监控Agent
- [ ] 三层架构完整实现(Reader → Analyzer → Writer)
- [ ] 完整审计日志系统
管理命令
查看所有Agent状态
launchctl list | grep caseos查看日志
# 周度扫描
tail -f ~/.claude/skills/case-os/data/launchd-*.log
# 短信监控
tail -f ~/.claude/skills/case-os/data/sms-monitor*.log停止所有Agent
launchctl unload ~/Library/LaunchAgents/com.claude.caseos.*.plist重启所有Agent
cd ~/.claude/skills/case-os/agents
for agent in weekly-scan court-sms-monitor; do
cd $agent && bash install.sh
done技术实现
调度方式
| Agent | 调度方式 | 技术实现 |
|---|---|---|
| weekly-scan | 定时 | LaunchAgent + StartCalendarInterval |
| court-sms-monitor | 实时 | LaunchAgent + fswatch |
通信方式
Agent发现事件
↓
写入状态文件(JSON)
↓
触发macOS通知
↓
(可选)写入飞书多维表格参考资料
- Anthropic claude-for-legal Agent设计
- 《Anthropic设计了什么样的法律AI机器人?》
- 案件OS v10.0架构文档
版本历史
- v1.1 - Agent系统扩展(2026-05-16)
- 法院短信实时监控Agent(飞书机器人+多维表格)
- 批量证据提取Agent(Phase A内部并行,3倍加速)
- 飞书通知器模块(卡片消息+多维表格写入)
- v1.0 - Agent系统初版(2026-05-16)
- 案件周度扫描Agent
- 基于Anthropic安全设计
#!/bin/bash
# 测试get_agent_status函数
HOME_PATH="/Users/Apple"
get_agent_status() {
local agent_name=$1
local plist_file="$HOME_PATH/Library/LaunchAgents/com.claude.caseos.${agent_name}.plist"
local label=$(launchctl list | grep "com.claude.caseos.${agent_name}" | awk '{print $1}')
echo "DEBUG: agent_name=$agent_name"
echo "DEBUG: plist_file=$plist_file"
echo "DEBUG: label=$label"
echo "DEBUG: 文件存在测试: $( [ -f "$plist_file" ] && echo "存在" || echo "不存在" )"
if [ ! -f "$plist_file" ]; then
echo "未安装"
elif [ -n "$label" ]; then
echo "运行中"
else
echo "已安装但未运行"
fi
}
echo "=== 测试 court-sms-monitor ==="
get_agent_status "court-sms-monitor"
echo ""
echo "=== 测试 weekly-scan ==="
get_agent_status "weekly-scan"
案件周度扫描Agent
工作定位
角色:Reader层Agent(只读扫描,无写入权限) 触发:每周一早上8点自动运行(cron调度) 目标:扫描所有案件状态,生成待办提醒报告
安全边界
✅ 允许:
- 读取案件文件夹中的文件
- 扫描CLAUDE.md、阶段追踪.md、案件备忘录.md
- 解析案件状态、待办事项
❌ 禁止:
- 写入或修改任何文件
- 连接外部网络
- 执行任何操作指令
扫描逻辑
第一步:发现案件
扫描所有案件根目录,查找包含CLAUDE.md的文件夹。
第二步:扫描维度
对每个案件,检查以下维度:
1. 新增文件:对比上次扫描,发现新加入的材料 2. 待办状态:检查阶段追踪.md中的未完成项 3. 开庭提醒:检查_archive/court-sms.json中的开庭通知 4. 证据缺口:检查证据卡片库是否标记为"待补充" 5. 久未更新:超过7天未更新的案件(CLAUDE.md修改时间)
第三步:生成报告
将扫描结果汇总为Markdown报告,结构:
# 案件周度扫描报告
**扫描时间**:2026-05-16 08:00
**扫描范围**:X个案件
## 🚨 紧急待办(需要立即处理)
### [案件名称] - 开庭提醒
- 开庭时间:2026-05-20 14:00
- 法院:XX法院
- 距离:3天
## ⚠️ 本周待办(建议本周处理)
### [案件名称] - 证据缺口
- 缺失证据:XX合同
- 标记时间:2026-05-10
### [案件名称] - 久未更新
- 最后更新:2026-05-05(11天前)
- 建议:检查是否有新进展
## 📄 新增材料(本周)
### [案件名称]
- 新增:XX证据.pdf
- 新增:XX邮件.pdf
---
**下次扫描时间**:2026-05-23 08:00第四步:呈现,等待确认
输出报告后,自动停止,等待用户: 1. 查看报告 2. 标记已处理项 3. 输入"继续"结束本次扫描
配置文件路径
扫描根目录:
- ~/Documents/百度网盘/高澄/案件
状态文件:
扫描记录: ~/.claude/skills/case-os/data/last-scan-result.txt
待处理队列: ~/.claude/skills/case-os/data/pending-files.json
输出文件:
报告: ~/Desktop/案件周度扫描报告-2026-05-16.md扫描规则
优先级分类
1. 紧急(🚨):开庭提醒(7天内)、证据补充截止(3天内) 2. 警告(⚠️):证据缺口、久未更新(7天+)、待办事项逾期 3. 信息(📄):新增材料、状态更新
过滤规则
- 忽略
._开头的macOS隐藏文件 - 忽略
.DS_Store - 忽略
~临时文件 - 忽略
_archive/目录(避免重复扫描归档内容)
错误处理
- 文件读取失败:记录错误,继续扫描其他文件
- 解析失败:使用默认值,记录警告
- 网络请求尝试:立即中止,报告安全警告
版本历史
- v1.0 - 初始版本(2026-05-16)
案件周度扫描Agent - 设计文档
设计目标
基于Anthropic claude-for-legal的Agent设计理念,为案件OS添加第一层自动化——定时监控Agent。
核心设计原则
1. 安全隔离(Reader层)
# ✅ 只读权限
permissions:
read: true
write: false
network: false设计依据:文章强调的"三层架构"
- Reader层:只读原始文档,无网络访问
- 防止Prompt Injection:合同中的指令无法被执行
- 数据泄露防护:无法访问攻击者控制的URL
2. "等待确认"机制
print("⏸️ 扫描完成,等待用户确认...")设计依据:文章第3节核心观点
"呈现,等待确认不是一个技术细节,而是一个设计选择"
Agent在关键点主动停下来,等人类补充上下文。这符合案件OS的"确认点"设计哲学。
3. 固定模板防御
def format_urgent_item(item: Dict) -> List[str]:
"""格式化紧急项"""
if item["type"] == "court_reminder":
return [
f"### {item['case']} - 开庭提醒",
# ... 固定格式
]设计依据:文章第5节的移交请求验证
"发给下一个Agent的指令怎么生成:不是从Agent的输出里拼接出来的,而是用固定模板渲染的"
报告格式完全固定,防止数据注入攻击。
4. 白名单枚举
# 扫描维度固定枚举
SCAN_DIMENSIONS = [
"court_reminder", # 开庭提醒
"evidence_gap", # 证据缺口
"pending_task", # 待办事项
"stale", # 久未更新
"new_files", # 新增文件
]设计依据:文章第8节五层防线第1层
"意图必须是固定枚举列表里的值。不在列表里的直接拒绝。"
与案件OS的关系
案件OS架构:
┌─────────────────────────────────────┐
│ 定时监控Agent(新增) │
│ - weekly-scan: 周度扫描 │
│ - future: 实时提醒 │
└─────────────────────────────────────┘
↓ 提醒
┌─────────────────────────────────────┐
│ 手动Skill(现有) │
│ - A1-A6: Phase A │
│ - S1-S10: Phase B │
│ - 其他工具Skill │
└─────────────────────────────────────┘定位:
- Agent是监控层,发现问题
- Skill是执行层,解决问题
- Agent不替代Skill,而是指导何时调用哪个Skill
技术实现
调度方式
使用macOS的LaunchAgent(类似Linux的cron):
<key>StartCalendarInterval</key>
<dict>
<key>Weekday</key>
<integer>1</integer> <!-- 周一 -->
<key>Hour</key>
<integer>8</integer> <!-- 8点 -->
</dict>文件结构
agents/weekly-scan/
├── AGENT.md # Agent配置文件
├── scan.py # 扫描实现
├── install.sh # 安装脚本
├── README.md # 使用说明
└── CHANGELOG.md # 设计文档(本文件)数据流
案件文件夹
↓ (读取)
scan.py (Reader + Analyzer)
↓ (生成)
Markdown报告
↓ (保存)
桌面报告文件扫描逻辑详解
1. 发现案件
遍历扫描根目录,查找包含CLAUDE.md的文件夹:
for item in root.rglob("CLAUDE.md"):
case_dir = item.parent
cases.append(CaseItem(case_dir.name, case_dir))2. 检查开庭提醒
从_archive/court-sms.json读取开庭信息,筛选7天内的:
if 0 <= days_until <= 7:
upcoming.append({...})3. 检查证据缺口
扫描证据卡片库,查找标记为"待补充"的:
if re.search(r'\[待补充\]|\[缺失\]|\[待提供\]', content):
gaps.append(file.stem)4. 检查待办事项
从阶段追踪.md提取未完成的checkbox:
if re.match(r'^- \[ \]', line):
pending.append(line.strip()[6:])5. 检查久未更新
对比CLAUDE.md的修改时间:
days_since = (datetime.now() - last_update).days
if days_since >= 7:
results.warning.append(...)报告格式
# 案件周度扫描报告
**扫描时间**:2026-05-16 08:00
**扫描范围**:X个案件
## 🚨 紧急待办(需要立即处理)
### [案件名称] - 开庭提醒
- 开庭时间:2026-05-20 14:00
- 法院:XX法院
- 距离:3天
## ⚠️ 本周待办(建议本周处理)
### [案件名称] - 证据缺口
- 缺失证据:XX合同
## 📄 新增材料(本周)
### [案件名称]
- 新增:XX证据.pdf
---
**下次扫描时间**:2026-05-23 08:00扩展方向
短期(v1.1)
- [ ] 添加命令行参数(手动指定扫描路径)
- [ ] 支持多个扫描根目录配置
- [ ] 添加飞书/邮件通知
中期(v2.0)
- [ ] 实时监控(fswatch监控文件变化)
- [ ] 裁判文书网监控(新判决提醒)
- [ ] 工商信息变更监控
长期(v3.0)
- [ ] Agent协作编排器(类似orchestrate.py)
- [ ] 多层Agent架构(Reader → Analyzer → Writer分离)
- [ ] 审计日志系统
版本历史
- v1.0 - 初始版本(2026-05-16)
- 周度定时扫描
- 5个扫描维度
- LaunchAgent调度
- Markdown报告生成
参考资料
- Anthropic claude-for-legal Agent设计
- 《Anthropic设计了什么样的法律AI机器人?》
- 案件OS v10.0架构文档
#!/bin/bash
# 案件周度扫描Agent - 安装脚本
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
AGENT_DIR="$SCRIPT_DIR"
SCAN_SCRIPT="$AGENT_DIR/scan.py"
PLIST_FILE="$HOME/Library/LaunchAgents/com.claude.caseos.weekly-scan.plist"
echo "🔧 安装案件周度扫描Agent..."
# 检查Python3
if ! command -v python3 &> /dev/null; then
echo "❌ 错误:未找到python3"
exit 1
fi
# 确保脚本可执行
chmod +x "$SCAN_SCRIPT"
# 创建LaunchAgent配置文件
cat > "$PLIST_FILE" << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.claude.caseos.weekly-scan</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/python3</string>
<string>$SCAN_SCRIPT</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Weekday</key>
<integer>1</integer>
<key>Hour</key>
<integer>8</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
<key>StandardOutPath</key>
<string>$HOME/.claude/skills/case-os/data/launchd-stdout.log</string>
<key>StandardErrorPath</key>
<string>$HOME/.claude/skills/case-os/data/launchd-stderr.log</string>
<key>RunAtLoad</key>
<false/>
<key>KeepAlive</key>
<false/>
</dict>
</plist>
EOF
echo "✅ LaunchAgent配置文件已创建:$PLIST_FILE"
# 加载LaunchAgent
echo "📝 正在加载LaunchAgent..."
launchctl unload "$PLIST_FILE" 2>/dev/null || true
launchctl load "$PLIST_FILE"
echo "✅ LaunchAgent已加载"
echo ""
echo "📋 调度信息:"
echo " - 运行时间:每周一早上8点"
echo " - 日志路径:~/.claude/skills/case-os/data/launchd-*.log"
echo ""
echo "🧪 手动测试:"
echo " python3 $SCAN_SCRIPT"
echo ""
echo "📝 卸载命令:"
echo " launchctl unload $PLIST_FILE"
echo " rm $PLIST_FILE"
案件周度扫描Agent
安全设计:只读扫描,无写入权限,无网络访问
功能概述
每周一早上8点自动扫描所有案件,生成待办提醒报告。
扫描维度
1. 🚨 紧急待办(需要立即处理)
- 开庭提醒(7天内)
2. ⚠️ 本周待办(建议本周处理)
- 证据缺口(标记为"待补充"的证据)
- 待办事项(阶段追踪.md中的未完成项)
- 久未更新(7天以上未更新的案件)
3. 📄 新增材料(信息记录)
- 本周新增的案件材料
安装步骤
cd ~/.claude/skills/case-os/agents/weekly-scan
bash install.sh手动测试
python3 scan.py查看日志
# 标准输出
tail -f ~/.claude/skills/case-os/data/launchd-stdout.log
# 错误输出
tail -f ~/.claude/skills/case-os/data/launchd-stderr.log卸载
launchctl unload ~/Library/LaunchAgents/com.claude.caseos.weekly-scan.plist
rm ~/Library/LaunchAgents/com.claude.caseos.weekly-scan.plist报告位置
每次扫描后,报告会保存到桌面:
~/Desktop/案件周度扫描报告-2026-05-16.md安全设计
基于Anthropic claude-for-legal的三层架构:
| 层级 | 职责 | 权限 |
|---|---|---|
| Reader | 读取案件文件 | ✅ 只读 |
| Analyzer | 分析状态、生成报告 | ✅ 只读 |
| Writer | 保存报告到桌面 | ✅ 只写(固定路径) |
禁止:
- ❌ 修改原始案件文件
- ❌ 连接外部网络
- ❌ 执行任意操作指令
设计启发
来自Anthropic claude-for-legal的Agent设计:
1. "等待确认"机制:扫描完成后停止,等待用户查看报告 2. 只读隔离:Reader层不接触写入,防止Prompt Injection 3. 固定模板:报告格式固定,防止数据注入攻击 4. 审计日志:所有扫描操作记录在日志文件中
与案件OS集成
该Agent是案件OS的第一层自动化:
- 不替代手动触发的Skill(如case-ocr、case-dashboard)
- 作为监控层,提醒需要跟进的工作
- 扫描结果可以指导用户调用哪个Skill
后续扩展
可以添加的扫描维度:
- 法院短信监控(实时)
- 裁判文书网监控(新判决)
- 工商信息变更监控(当事人)
- 法律法规更新监控
版本历史
- v1.0 - 初始版本(2026-05-16)
#!/usr/bin/env python3
"""
案件周度扫描Agent - 实现脚本
安全设计:只读扫描,无写入权限,无网络访问
"""
import json
import os
import re
import sys
from datetime import datetime, timedelta
from pathlib import Path
from typing import Dict, List, Any
# ==================== 配置 ====================
SCAN_ROOTS = [
Path.home() / "Documents" / Documents / cases / "案件",
]
STATE_DIR = Path.home() / ".claude" / "skills" / "case-os" / "data"
LAST_SCAN_FILE = STATE_DIR / "last-scan-result.txt"
PENDING_QUEUE_FILE = STATE_DIR / "pending-files.json"
OUTPUT_DIR = Path.home() / "Desktop"
# 忽略的文件/目录
IGNORE_PATTERNS = [
r"^\.", # 以.开头的隐藏文件
r"^\.DS_Store$",
r"^~.*", # ~开头的临时文件
r"^_archive", # 归档目录
]
# ==================== 数据结构 ====================
class ScanResult:
def __init__(self):
self.urgent = [] # 紧急待办
self.warning = [] # 警告
self.info = [] # 信息
self.errors = [] # 错误
self.scan_time = datetime.now()
class CaseItem:
def __init__(self, name: str, path: Path):
self.name = name
self.path = path
self.claude_md = path / "CLAUDE.md"
self.tracking_md = path / "阶段追踪.md"
self.memo_md = path / "案件备忘录.md"
self.court_sms = path / "_archive" / "court-sms.json"
# ==================== 核心扫描函数 ====================
def find_case_folders() -> List[CaseItem]:
"""扫描所有案件文件夹(查找CLAUDE.md)"""
cases = []
for root in SCAN_ROOTS:
if not root.exists():
continue
for item in root.rglob("CLAUDE.md"):
case_dir = item.parent
# 跳归档目录
if "_archive" in str(case_dir):
continue
cases.append(CaseItem(case_dir.name, case_dir))
return cases
def check_court_reminder(case: CaseItem) -> Dict[str, Any]:
"""检查开庭提醒(从court-sms.json)"""
if not case.court_sms.exists():
return None
try:
data = json.loads(case.court_sms.read_text(encoding="utf-8"))
if not data.get("messages"):
return None
# 找7天内的开庭
upcoming = []
now = datetime.now()
for msg in data["messages"]:
if msg.get("type") == "开庭通知":
try:
court_date = datetime.strptime(msg.get("date", ""), "%Y-%m-%d")
days_until = (court_date - now).days
if 0 <= days_until <= 7:
upcoming.append({
"case": case.name,
"date": msg.get("date"),
"court": msg.get("court", "未知法院"),
"days_until": days_until,
})
except ValueError:
pass
return upcoming[0] if upcoming else None
except (json.JSONDecodeError, KeyError):
return None
def check_evidence_gaps(case: CaseItem) -> List[str]:
"""检查证据缺口(从证据卡片库)"""
evidence_dir = case.path / "_intermediate" / "证据卡片库"
if not evidence_dir.exists():
return []
gaps = []
for file in evidence_dir.rglob("*.md"):
content = file.read_text(encoding="utf-8")
# 检查是否标记为待补充
if re.search(r'\[待补充\]|\[缺失\]|\[待提供\]', content):
gaps.append(file.stem)
return gaps
def check_pending_tasks(case: CaseItem) -> List[str]:
"""检查待办事项(从阶段追踪.md)"""
if not case.tracking_md.exists():
return []
content = case.tracking_md.read_text(encoding="utf-8")
pending = []
# 查找未完成的checkbox
for line in content.split('\n'):
if re.match(r'^- \[ \]', line):
pending.append(line.strip()[6:]) # 去掉"- [ ] "
return pending
def check_last_update(case: CaseItem) -> int:
"""检查最后更新时间(CLAUDE.md修改时间)"""
if not case.claude_md.exists():
return None
mtime = case.claude_md.stat().st_mtime
last_update = datetime.fromtimestamp(mtime)
days_since = (datetime.now() - last_update).days
return days_since
def detect_new_files(case: CaseItem, known_files: set) -> List[str]:
"""检测新增文件"""
new_files = []
for file in case.path.iterdir():
if should_ignore_file(file.name):
continue
if file.is_file():
key = f"{case.name}/{file.name}"
if key not in known_files:
new_files.append(file.name)
return new_files
def should_ignore_file(name: str) -> bool:
"""判断是否应忽略该文件"""
for pattern in IGNORE_PATTERNS:
if re.match(pattern, name):
return True
return False
# ==================== 报告生成 ====================
def generate_report(results: ScanResult, case_count: int) -> str:
"""生成Markdown报告"""
lines = [
"# 案件周度扫描报告",
"",
f"**扫描时间**:{results.scan_time.strftime('%Y-%m-%d %H:%M')}",
f"**扫描范围**:{case_count}个案件",
"",
]
# 紧急待办
if results.urgent:
lines.extend([
"## 🚨 紧急待办(需要立即处理)",
"",
])
for item in results.urgent:
lines.extend(format_urgent_item(item))
lines.append("")
# 警告
if results.warning:
lines.extend([
"## ⚠️ 本周待办(建议本周处理)",
"",
])
for item in results.warning:
lines.extend(format_warning_item(item))
lines.append("")
# 新增材料
if results.info:
lines.extend([
"## 📄 新增材料(本周)",
"",
])
for item in results.info:
lines.extend(format_info_item(item))
lines.append("")
# 错误
if results.errors:
lines.extend([
"## ⚠️ 扫描错误",
"",
])
for error in results.errors:
lines.append(f"- {error}")
lines.append("")
# 下次扫描时间
next_scan = results.scan_time + timedelta(days=7)
lines.extend([
"---",
"",
f"**下次扫描时间**:{next_scan.strftime('%Y-%m-%d %H:%M')}",
])
return "\n".join(lines)
def format_urgent_item(item: Dict) -> List[str]:
"""格式化紧急项"""
if item["type"] == "court_reminder":
return [
f"### {item['case']} - 开庭提醒",
f"- 开庭时间:{item['date']}",
f"- 法院:{item['court']}",
f"- 距离:{item['days_until']}天",
]
return []
def format_warning_item(item: Dict) -> List[str]:
"""格式化警告项"""
if item["type"] == "evidence_gap":
lines = [f"### {item['case']} - 证据缺口"]
for gap in item["gaps"]:
lines.append(f"- 缺失证据:{gap}")
return lines
elif item["type"] == "stale":
return [
f"### {item['case']} - 久未更新",
f"- 最后更新:{item['last_update']}({item['days_since']}天前)",
"- 建议:检查是否有新进展",
]
elif item["type"] == "pending_task":
lines = [f"### {item['case']} - 待办事项"]
for task in item["tasks"][:3]: # 最多显示3条
lines.append(f"- {task}")
if len(item["tasks"]) > 3:
lines.append(f"- ...还有{len(item['tasks'])-3}项待办")
return lines
return []
def format_info_item(item: Dict) -> List[str]:
"""格式化信息项"""
if item["type"] == "new_files":
lines = [f"### {item['case']}"]
for file in item["files"][:5]: # 最多显示5个
lines.append(f"- 新增:{file}")
if len(item["files"]) > 5:
lines.append(f"- ...还有{len(item['files'])-5}个文件")
return lines
return []
# ==================== 主流程 ====================
def load_known_files() -> set:
"""加载已知文件列表"""
if PENDING_QUEUE_FILE.exists():
try:
data = json.loads(PENDING_QUEUE_FILE.read_text(encoding="utf-8"))
return {f"{item['case']}/{item['file']}" for item in data.get("pending", [])}
except (json.JSONDecodeError, KeyError):
pass
return set()
def save_scan_result(report: str):
"""保存扫描结果"""
STATE_DIR.mkdir(parents=True, exist_ok=True)
LAST_SCAN_FILE.write_text(report, encoding="utf-8")
def save_report_to_desktop(report: str):
"""保存报告到桌面"""
filename = f"案件周度扫描报告-{datetime.now().strftime('%Y-%m-%d')}.md"
output_path = OUTPUT_DIR / filename
output_path.write_text(report, encoding="utf-8")
return output_path
def main():
"""主扫描流程"""
print("🔍 开始扫描案件...")
results = ScanResult()
known_files = load_known_files()
# 扫描所有案件
cases = find_case_folders()
print(f"发现 {len(cases)} 个案件文件夹")
for case in cases:
print(f" 扫描:{case.name}")
# 检查开庭提醒(紧急)
court_info = check_court_reminder(case)
if court_info:
results.urgent.append({
"type": "court_reminder",
**court_info,
})
# 检查证据缺口(警告)
gaps = check_evidence_gaps(case)
if gaps:
results.warning.append({
"type": "evidence_gap",
"case": case.name,
"gaps": gaps,
})
# 检查待办事项(警告)
pending = check_pending_tasks(case)
if pending:
results.warning.append({
"type": "pending_task",
"case": case.name,
"tasks": pending,
})
# 检查久未更新(警告)
days_since = check_last_update(case)
if days_since and days_since >= 7:
mtime = datetime.fromtimestamp(case.claude_md.stat().st_mtime)
results.warning.append({
"type": "stale",
"case": case.name,
"days_since": days_since,
"last_update": mtime.strftime("%Y-%m-%d"),
})
# 检测新增文件(信息)
new_files = detect_new_files(case, known_files)
if new_files:
results.info.append({
"type": "new_files",
"case": case.name,
"files": new_files,
})
# 生成报告
report = generate_report(results, len(cases))
# 保存报告
save_scan_result(report)
output_path = save_report_to_desktop(report)
# 输出摘要
print("\n" + "="*50)
print(f"✅ 扫描完成")
print(f"📊 紧急待办:{len(results.urgent)}项")
print(f"⚠️ 本周待办:{len(results.warning)}项")
print(f"📄 新增材料:{sum(len(i.get('files', [])) for i in results.info)}个文件")
print(f"💾 报告已保存:{output_path}")
print("="*50)
# 如果有紧急待办,单独提醒
if results.urgent:
print("\n🚨 紧急待办提醒:")
for item in results.urgent:
print(f" - {item['case']}: {item['date']} 开庭")
print("\n⏸️ 扫描完成,等待用户确认...")
return 0
if __name__ == "__main__":
sys.exit(main())
# 民事案件OS 全局配置示例
> 将此文件内容复制到 `~/.claude/AGENTS.md` 中,根据自身情况修改。
> 民事案件OS运行时会在案件项目目录的 `CLAUDE.md` 中写入案件特定配置。
---
## 触发词规则
以下触发词出现时,必须立即调用对应 Skill,不得忽略:
| 触发词 | 调用 Skill | 说明 |
|--------|-----------|------|
| 民事案件OS、案件OS、case-os、case os(不区分大小写) | `case-os` | 民事案件操作系统总控 |
---
## 输出规范
**格式模板**:所有回复套用统一模板,全量中文:
```
[前缀标识]
[正文内容]
```
**文件输出**:生成新文件时必须告知完整路径。
**最终产出文件**:任务完成时,如果生成了最终产出文件(非中间临时文件),用 `code -r <文件路径>` 在 VS Code 中打开。
---
## 输出格式参考
详见 `~/.claude/references/format-guide.md`
**OMC命令参考**
详见 `~/.claude/references/omc-guide.md`
---
## 行为原则
- 禁止敷衍了事
- 禁止编造事实和法律
- 禁止擅自修改核心配置
- 保持专业严谨,主动避免已知错误
{
"案由": "侵权责任纠纷",
"案由编号": "99",
"version": "1.0",
"抗辩分类": {
"权利消灭": [
{
"抗辩名称": "侵权损害已赔偿完毕",
"类型": "权利消灭",
"法律依据": "《民法典》第1187条",
"证明事项": ["已全额支付赔偿金", "赔偿范围覆盖原告全部损失", "原告已出具收据/和解书"],
"证据类型": ["转账记录", "收据", "和解协议", "赔偿计算清单"],
"举证方": "被告",
"难度": "低",
"备注": "需保留完整支付凭证,注明赔偿项目和金额"
},
{
"抗辩名称": "和解协议已履行完毕",
"类型": "权利消灭",
"法律依据": "《民法典》第1187条、第509条",
"证明事项": ["双方已签订和解协议", "协议约定的义务已全部履行", "原告已放弃其他请求"],
"证据类型": ["和解协议", "履行凭证", "原告声明/承诺书"],
"举证方": "被告",
"难度": "低",
"备注": "和解协议中应明确'一次性解决'或'放弃其他请求'条款"
},
{
"抗辩名称": "超过诉讼时效",
"类型": "权利消灭",
"法律依据": "《民法典》第188条",
"证明事项": ["自知道或应当知道权利被侵害超过3年", "自侵权行为发生超过20年", "无时效中止/中断事由"],
"证据类型": ["侵权行为发生时间证明", "受害人知道时间证明", "起诉状"],
"举证方": "被告",
"难度": "中",
"备注": "人身损害赔偿特殊时效:伤害明显的从受伤之日起算,伤害当时未发现的从确诊之日起算"
},
{
"抗辩名称": "诉讼时效中断事由不存在",
"类型": "权利消灭",
"法律依据": "《民法典》第195条",
"证明事项": ["原告主张的时效中断事由不成立", "催告通知未实际送达", "起诉后又撤诉不构成中断"],
"证据类型": ["送达记录", "邮寄凭证", "撤诉裁定"],
"举证方": "被告",
"难度": "中",
"备注": "需逐一核查原告主张的时效中断事由"
}
],
"权利妨碍": [
{
"抗辩名称": "无过错——过错责任原则下",
"类型": "权利妨碍",
"法律依据": "《民法典》第1165条第1款",
"证明事项": ["被告行为无过错", "已尽合理注意义务", "损害系不可预见"],
"证据类型": ["操作规程", "安全措施记录", "行业标准", "专家意见"],
"举证方": "被告",
"难度": "中",
"备注": "过错责任原则下,原告需证明被告有过错;被告可反证无过错"
},
{
"抗辩名称": "因果关系不存在",
"类型": "权利妨碍",
"法律依据": "《民法典》第1165条",
"证明事项": ["被告行为与损害结果之间无因果关系", "损害系其他原因导致", "时间/空间上不具有关联性"],
"证据类型": ["鉴定意见", "专家证人证言", "事故调查报告", "监控录像"],
"举证方": "被告",
"难度": "高",
"备注": "因果关系是侵权责任构成要件之一,由原告承担举证责任"
},
{
"抗辩名称": "损害未发生或夸大损失",
"类型": "权利妨碍",
"法律依据": "《民法典》第1182条、第1184条",
"证明事项": ["原告主张的损害未实际发生", "损失金额明显不合理", "原告存在扩大损失行为"],
"证据类型": ["损失评估报告", "市场价格证明", "原告就医记录", "第三方评估"],
"举证方": "被告",
"难度": "中",
"备注": "原告应就损害事实和损失金额承担举证责任"
},
{
"抗辩名称": "第三人过错导致损害",
"类型": "权利妨碍",
"法律依据": "《民法典》第1175条",
"证明事项": ["损害系第三人行为造成", "被告与第三人无共同故意或过失", "第三人行为是损害的直接原因"],
"证据类型": ["第三人行为证据", "事故调查报告", "监控录像", "证人证言"],
"举证方": "被告",
"难度": "高",
"备注": "第三人过错可免除或减轻被告责任,但法律另有规定的除外"
},
{
"抗辩名称": "受害人故意造成损害",
"类型": "权利妨碍",
"法律依据": "《民法典》第1174条",
"证明事项": ["受害人故意实施危险行为", "受害人故意追求损害结果", "损害系受害人故意所致"],
"证据类型": ["受害人行为记录", "监控录像", "证人证言", "受害人自认"],
"举证方": "被告",
"难度": "高",
"备注": "损害是因受害人故意造成的,行为人不承担责任"
},
{
"抗辩名称": "损害系受害人自身疾病导致",
"类型": "权利妨碍",
"法律依据": "《民法典》第1165条",
"证明事项": ["受害人原有疾病", "损害与侵权行为无因果关系", "损害系疾病自然发展结果"],
"证据类型": ["病历", "医学鉴定", "专家意见", "受害人既往病史"],
"举证方": "被告",
"难度": "高",
"备注": "特殊体质不影响侵权责任成立,但可能影响赔偿范围"
}
],
"权利阻止": [
{
"抗辩名称": "正当防卫",
"类型": "权利阻止",
"法律依据": "《民法典》第1176条第1款、第181条",
"证明事项": ["存在不法侵害", "防卫行为针对不法侵害人", "防卫未超过必要限度"],
"证据类型": ["监控录像", "报警记录", "证人证言", "伤情鉴定"],
"举证方": "被告",
"难度": "中",
"备注": "正当防卫超过必要限度造成不应有的损害的,应承担适当责任"
},
{
"抗辩名称": "紧急避险",
"类型": "权利阻止",
"法律依据": "《民法典》第182条",
"证明事项": ["存在紧急危险", "避险行为不得已", "避险造成的损害小于避免的损害"],
"证据类型": ["危险情况证明", "避险行为记录", "损害对比评估", "证人证言"],
"举证方": "被告",
"难度": "中",
"备注": "紧急避险措施不当或超过必要限度的,应承担适当责任"
},
{
"抗辩名称": "不可抗力",
"类型": "权利阻止",
"法律依据": "《民法典》第180条",
"证明事项": ["事件不能预见", "事件不能避免", "事件不能克服"],
"证据类型": ["气象记录", "地震记录", "政府公告", "行业报告"],
"举证方": "被告",
"难度": "中",
"备注": "不可抗力是法定免责事由,但法律另有规定的除外"
},
{
"抗辩名称": "受害人过错(过失相抵)",
"类型": "权利阻止",
"法律依据": "《民法典》第1173条",
"证明事项": ["受害人对损害发生也有过错", "受害人未尽合理注意义务", "受害人过错与损害有因果关系"],
"证据类型": ["受害人行为记录", "事故责任认定书", "监控录像", "证人证言"],
"举证方": "被告",
"难度": "中",
"备注": "被侵权人对同一损害的发生或者扩大有过错的,可以减轻侵权人的责任"
},
{
"抗辩名称": "自甘风险",
"类型": "权利阻止",
"法律依据": "《民法典》第1176条",
"证明事项": ["受害人自愿参加具有一定风险的文体活动", "损害系其他参加者行为造成", "其他参加者无故意或重大过失"],
"证据类型": ["活动规则", "风险告知书", "参加者行为记录", "证人证言"],
"举证方": "被告",
"难度": "中",
"备注": "仅适用于文体活动,组织者的安全保障义务仍适用"
},
{
"抗辩名称": "自助行为",
"类型": "权利阻止",
"法律依据": "《民法典》第1177条",
"证明事项": ["合法权益受到侵害", "情况紧迫且不能及时获得国家机关保护", "自助行为在合理限度内"],
"证据类型": ["侵害行为证据", "紧迫情况证明", "自助行为记录", "事后及时报告记录"],
"举证方": "被告",
"难度": "高",
"备注": "自助行为超过必要限度造成他人损害的,应承担侵权责任"
},
{
"抗辩名称": "职务行为",
"类型": "权利阻止",
"法律依据": "《民法典》第1191条",
"证明事项": ["行为系执行工作任务", "行为在职务授权范围内", "用人单位应承担替代责任"],
"证据类型": ["劳动合同", "职务授权文件", "工作记录", "单位证明"],
"举证方": "被告",
"难度": "中",
"备注": "用人单位承担侵权责任后可向有故意或重大过失的工作人员追偿"
},
{
"抗辩名称": "正当行使权利——舆论监督",
"类型": "权利阻止",
"法律依据": "《民法典》第1025条、第1026条",
"证明事项": ["为公共利益实施舆论监督", "报道内容基本属实", "评论系基于事实的合理评论"],
"证据类型": ["新闻报道", "调查材料", "公共利益证明", "事实核查记录"],
"举证方": "被告",
"难度": "中",
"备注": "行为人为公共利益实施新闻报道、舆论监督,影响他人名誉的,不承担民事责任"
},
{
"抗辩名称": "受害人同意",
"类型": "权利阻止",
"法律依据": "《民法典》第1176条(类推适用)、第1033条",
"证明事项": ["受害人明确同意实施相关行为", "同意系真实意思表示", "同意范围覆盖实际损害"],
"证据类型": ["书面同意书", "知情同意书", "录音录像", "证人证言"],
"举证方": "被告",
"难度": "中",
"备注": "受害人同意不得违反法律强制性规定和公序良俗"
},
{
"抗辩名称": "损益相抵",
"类型": "权利阻止",
"法律依据": "《民法典》第1182条(目的性解释)",
"证明事项": ["受害人因侵权行为同时获得利益", "利益与损害基于同一原因事实", "赔偿额应扣除获益部分"],
"证据类型": ["保险理赔记录", "第三人补偿记录", "利益证明", "财务凭证"],
"举证方": "被告",
"难度": "高",
"备注": "受害人因侵权行为获得的利益应从赔偿额中扣除"
},
{
"抗辩名称": "安全保障义务已尽到",
"类型": "权利阻止",
"法律依据": "《民法典》第1198条",
"证明事项": ["已采取合理安全措施", "已设置安全警示标识", "损害系第三人行为所致且已尽到安全保障义务"],
"证据类型": ["安全管理制度", "安全检查记录", "警示标识照片", "监控录像"],
"举证方": "被告",
"难度": "中",
"备注": "宾馆、商场、银行、车站等经营场所、公共场所的经营者、管理者负有安全保障义务"
},
{
"抗辩名称": "高度危险责任——受害人故意免责",
"类型": "权利阻止",
"法律依据": "《民法典》第1237-1244条",
"证明事项": ["受害人故意造成损害", "受害人擅自进入危险区域", "受害人违反禁止性规定"],
"证据类型": ["受害人行为记录", "禁止标识照片", "警告通知", "监控录像"],
"举证方": "被告",
"难度": "中",
"备注": "民用核设施、高压电等高度危险作业中,受害人故意可免责"
},
{
"抗辩名称": "环境污染责任——未超标排放",
"类型": "权利阻止",
"法律依据": "《民法典》第1229条、第1230条",
"证明事项": ["排放行为符合国家标准", "损害与排放行为无因果关系", "已取得排污许可并合规排放"],
"证据类型": ["环境监测报告", "排污许可证", "排放检测记录", "专家意见"],
"举证方": "被告",
"难度": "高",
"备注": "环境污染责任为无过错责任,但需证明因果关系;达标排放不能当然免责"
}
]
}
}