Skip to main content

acshame

  1. Anthropic 和 OpenAI 在这两天分别发布了报告描述自家平台是如何对抗外国敌对势力和公司对自家平台的滥用的

    其中,Anthropic 的报告指出他们通过追踪元数据,成功识别了 DeepSeek,Moonshot AI 和 Minimax 对他家模型的蒸馏,甚至能准确溯源到特定的实验室人员和公司高级员工

    OpenAI 的报告则更为详细,不仅指出有中国网络战人员使用 OpenAI 的产品,还详细描述了他们输入的内容和进行的操作,例如对高市早苗进行舆论攻击以及在 Twitter 上攻击“李老师不是你老师”等账号

    防止自家的服务被滥用是天经地义的,但是这里有一个问题:

    Anthropic 和 OpenAI 不仅看了用户的聊天内容,用聊天内容和元数据对用户做了大数据分析,还把聊天和分析的内容公之于众

    换句话说,用户在这些平台上并没有什么隐私——平台可以随意查看和审查你的聊天内容,根据他们的道德和价值观主观评判内容的好坏,并且保留权利随时将你的内容公之于众或者提交给司法机关,而且即使是脱敏了的数据也仍然可以关联到个人,且在此之上谁也说不好他们有没有拿这些数据去做别的事情

    我认为这是个大问题,也是一记警钟

    而且我并不觉得这只是美国之外的人应该上心的问题;任何类似的平台都可以这么做,且现在你和他们价值观相同,并不代表你们会永远相同,说不定哪天你私聊里的哪个内容就被 AI flag 了抄送党卫军

    这不仅突出了数据保护法案的落实问题,打上“境外势力”标签的用户的隐私权就可以被区别对待的问题,还凸显出了本地部署模型的重要性,正好呼应了我之前分享的 blog 里的那句话:

    Policy is a promise. Architecture is a guarantee.


    这些服务商的隐私协议和 ZDR 里可以说的天花乱坠,但是只有跑在你本地,拔掉网线还能运行的模型输出的数据才是安全的,完全受控于你的

    https://www.anthropic.com/news/detecting-and-preventing-distillation-attacks

    https://openai.com/index/disrupting-malicious-ai-uses/

    https://www.reddit.com/r/LocalLLaMA/comments/1rd8cfw/anthropics_recent_distillation_blog_should_make/
  2. openclaw-android: 在 Android 上以轻量方式运行 OpenClaw 的 Termux 兼容方案

    感谢 E5 佬的分享

    • 无需安装完整 Linux 发行版,仅通过 glibc 动态链接器即可运行 OpenClaw,显著降低存储占用与部署复杂度

    • 一条命令完成 Node.js、路径转换、临时目录、systemd 替代与兼容补丁配置,适合直接在手机上快速落地 AI Agent 环境

    • 基于平台插件化架构设计,内置更新、卸载、状态检查与可选工具安装能力,也支持 code-server、OpenCode 和多种 AI CLI 扩展

    https://github.com/AidanPark/openclaw-android

    #Android #Termux #OpenClaw #AI Agent #Node.js #glibc #移动开发 #开发者工具 #命令行工具 #GitHub
  3. | 术语 | 定义 |
    |------|------|
    | Area | JD 顶级分类(00-99) |
    | Category | Area 内分类(000-999) |
    | ID | Category 内序号(0001-9999) |
    | 短 ID | Git 风格的简化 ID(如 10.1.15 → `10.001.0015`) |
    | Namespace | 资源逻辑分组,对应目录 |
    | Frontmatter | Markdown 文件头部 YAML 元数据 |
    | GTD | Getting Things Done 方法论 |
    | CORE | Capture, Organize, Review, Execute |
    | Sidecar Index | SQLite 索引文件(`.synapse/index.db`) |
    | Fuzzy Finder | 模糊搜索功能,支持标题匹配 |
    | Command Palette | 命令面板,类似 VS Code Ctrl+P |
    | Refile | 将资源从 Inbox 移动到目标位置(Emacs Org-mode 术语) |
    | Link Refactoring | Refile 时自动更新所有引用该资源的链接 |
    | Transaction Rollback | 事务回滚,利用 Git 暂存区实现原子性操作 |
    | Hybrid Search | 混合搜索,结合全文(Ripgrep)和元数据(SQLite) |
    | Application-Layer Tokenization | 应用层分词,写入/搜索前用 jieba 预处理 |
    | Stopword Filtering | 停用词过滤,去除标点和无意义字符 |
    | Session Persistence | 会话持久化,记录用户上下文状态 |
    | Suspension Context | 暂停上下文,Git 操作期间暂停 watchdog 避免自激 |
    | watchdog | Python 文件系统监听库 |
    | Textual Workers | Textual 框架的异步任务机制(@work 装饰器) |

    ---

    ## 附录 B:参考资源

    - [Johnny.Decimal](https://johnnydecimal.com/)
    - [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)
    - [k9s](https://k9scli.io/)
    - [GTD](https://gettingthingsdone.com/)
    - [GraphRAG](https://github.com/microsoft/graphrag)
    - [LiteLLM](https://github.com/BerriAI/litellm)
    - [Textual](https://textual.textualize.io/) - Python TUI 框架
    - [Ripgrep](https://github.com/BurntSushi/ripgrep) - 高性能搜索工具
    - [watchdog](https://github.com/gorakhargosh/watchdog) - Python 文件系统监听
    - [jieba](https://github.com/fxsjy/jieba) - 中文分词(FTS5 增强) A system to organise your life
  4. | 操作 | 数据源 |
    |------|--------|
    | syn list, syn search (Frontmatter) | SQLite |
    | syn search --content (全文) | Ripgrep → 再查 SQLite 补充元数据 |
    | syn show, syn edit | 实际 Markdown 文件 |

    ### 13.5 混合搜索(Hybrid Search)

    **场景**:用户想搜 "Project X"(全文)且 "Status=Todo"(元数据)。

    **实现逻辑**:
    
    1. Ripgrep 搜索全文 → 返回匹配文件路径列表
    2. 用路径列表查询 SQLite → 过滤 Status=Todo
    3. 返回最终结果(带完整元数据)
    


    **命令示例**:
    
    # 混合搜索:全文包含 "kubernetes" 且状态为 todo
    syn search "kubernetes" --status todo
    
    # 混合搜索:全文包含 "设计" 且标签包含 urgent
    syn search "设计" --tag urgent
    
    # 纯元数据搜索(due-before 基于 due 字段)
    syn search --type task --priority high --due-before 2024-01-15
    


    **时间筛选参数说明**:

    | 参数 | 对应字段 | 说明 |
    |------|----------|------|
    | --due-before | due | 截止日期早于指定日期的任务 |
    | --due-after | due | 截止日期晚于指定日期的任务 |
    | --created-before | created | 创建时间早于指定日期 |
    | --created-after | created | 创建时间晚于指定日期 |
    | --modified-before | modified | 修改时间早于指定日期 |
    | --modified-after | modified | 修改时间晚于指定日期 |

    **示例**:
    
    # 查找本周内到期的任务
    syn search --type task --due-after monday --due-before sunday
    
    # 查找过去 7 天修改过的笔记
    syn search --type note --modified-after "7 days ago"
    


    ### 13.6 批量操作与异步机制

    针对 10,000+ 文件规模的操作优化:

    **SQLite 事务批量写入**:
    
    # ❌ 错误:单条插入 1000 次 ≈ 5秒
    for item in items:
        cursor.execute("INSERT INTO resources ...", item)
        conn.commit()
    
    # ✅ 正确:事务批量插入 1000 次 ≈ 0.05秒
    cursor.execute("BEGIN TRANSACTION")
    for item in items:
        cursor.execute("INSERT INTO resources ...", item)
    cursor.execute("COMMIT")
    


    **Textual 异步 Workers**:
    
    from textual import work
    
    class SynapseApp(App):
        @work(exclusive=True)
        async def action_batch_update_tags(self, ids: list, add_tag: str):
            """耗时操作放入后台线程,不阻塞 UI"""
            self.notify("正在批量更新标签...")
            
            # 执行文件 IO 和 DB 更新
            await processor.batch_add_tag(ids, add_tag)
            
            self.notify(f"✓ 已更新 {len(ids)} 个资源")
            self.refresh_list()
    


    **进度反馈**:
    - 操作耗时 > 500ms 时,TUI 底部状态栏显示 Spinner
    - 批量操作(如 Refile 多个文件)显示进度条

    ---

    ## 14. 成功标准

    1. 快速捕获到 Inbox 并高效处理
    2. Johnny.Decimal 编号清晰组织(支持短 ID 和模糊匹配)
    3. Git 完整审计追踪(Conventional Commits)
    4. k9s 风格 TUI(列表/日历/时间线/图谱视图 + 命令面板)
    5. CORE 工作流:捕获 → 组织 → 回顾 → 执行
    6. 本地 Hook 优先 + GitHub Actions 远程通知
    7. 仅 Markdown,仓库精简(`syn media` 处理附件)
    8. Namespace 支持项目级配置
    9. 10,000 资源列表 < 500ms(SQLite 索引 + 批量事务)
    10. 多端同步无痛(自动 rebase + 智能字段合并)
    11. Refile 时链接自动更新,永不断裂
    12. 中文全文搜索精准(jieba 应用层分词)
    13. 会话状态持久化,随时恢复上下文

    ---

    ## 15. 边缘情况处理

    | 场景 | 处理方案 |
    |------|----------|
    | title 修改 | 文件名同步更新(包含 title),但链接仅使用 ID,不受影响 |
    | Refile 链接断裂 | 自动全局搜索替换旧 ID → 新 ID(见 5.5) |
    | 脏工作区 Refile | 拒绝操作,TUI 提供快捷 Commit/Stash 选项(见 5.5) |
    | Refile 中途失败 | 前置检查确保工作区干净后,失败可安全回滚(见 5.5) |
    | 短 ID 歧义 | CLI 报错列出候选项,TUI 弹窗二选一(见 3.2) |
    | 并发编辑 | watchdog 实时监听 + Hash 检测(见 7.6) |
    | watchdog 自激 | Git 操作期间暂停监听(SuspensionContext),避免误报(见 7.6) |
    | 多端同步 | 启动 `pull --rebase`,退出 `push`,智能字段合并(见 7.4-7.5) |
    | 索引损坏 | syn index rebuild 重建 |
    | 大文件 | .gitignore 排除,`syn media` 上传到外部存储 |
    | 耗时操作 | Textual @work 异步执行,底部显示进度(见 13.6) |
    | 中文搜索噪音 | 停用词过滤 + 标点清理,减小索引体积(见 13.2) |

    ---

    ## 16. 范围外(初始版本)

    **优先级低,预留设计的功能**:
    - **§8 媒体管理**:初始版本仅支持外部链接,`syn media` 命令预留
    - **§10 知识图谱**:初始版本不实现,预留 syn graph 接口
    - **§11 邮件集成**:初始版本仅支持手动创建,API 集成预留

    **明确排除的功能**:
    - 完整 LLM GraphRAG(仅预留接口)
    - 实时协作(多人同时编辑)
    - Web/移动端
    - 邮件 API 自动集成
    - 自定义 FTS5 分词器(使用应用层分词替代)

    ---

    ## 附录 A:术语表
  5. | 协议 | 认证方式 | 说明 |
    |------|----------|------|
    | Gmail API | OAuth2 | 需要 Google Cloud 项目 |
    | IMAP | SSL/TLS | 传统邮箱协议 |
    | Microsoft Graph | OAuth2 | Outlook/Office365 |

    **凭证配置(预留)**:
    
    # config.yaml
    email:
      provider: gmail  # gmail | imap | microsoft
      gmail:
        client_id_env: GMAIL_CLIENT_ID
        client_secret_env: GMAIL_CLIENT_SECRET
      imap:
        server: imap.example.com
        port: 993
        username_env: IMAP_USERNAME
        password_env: IMAP_PASSWORD
    


    ---

    ## 12. 配置

    ### 12.1 配置文件示例

    
    # ~/.config/synapse/config.yaml
    
    workspace: ~/synapse-data
    default_namespace: personal
    
    namespaces:
      - personal      # 1
      - work-project  # 2
      - study         # 3
    
    editor:
      windows: { default: notepad, options: [notepad, typora, code] }
      linux: { default: vim, options: [vim, typora] }
      macos: { default: vim, options: [vim, typora] }
    
    git:
      auto_commit: true
      commit_style: conventional  # conventional | simple | llm
      llm:
        enabled: false
        provider: litellm
        model: gpt-4o-mini
        api_base: https://api.openai.com/v1
        api_key_env: OPENAI_API_KEY
      remotes:
        - name: origin
          url: [email protected]:user/synapse-data.git
          auto_push: true
    
    johnny_decimal:
      areas:
        00: "Inbox"
        10: "个人管理"
        20: "工作项目"
        30: "学习成长"
    
    review:
      daily_time: "08:00"
      weekly_day: friday
      weekly_time: "18:00"
    


    ---

    ## 13. 性能与索引机制

    ### 13.1 性能目标

    | 操作 | 目标 | 实现方式 |
    |------|------|----------|
    | 列表 10,000 资源 | < 500ms | SQLite 索引 |
    | 全文搜索 | < 1s | Ripgrep |
    | 创建/更新 | < 300ms | 增量索引更新 |

    ### 13.2 SQLite Sidecar Index

    **问题**:10,000 个 Markdown 文件逐个解析 Frontmatter,IO 开销巨大。

    **方案**:`.synapse/index.db` 缓存所有元数据。

    
    -- 主索引表
    CREATE TABLE resources (
        id TEXT PRIMARY KEY,        -- "10.001.0015"
        namespace TEXT,
        area TEXT,                  -- "10"
        category TEXT,              -- "001"
        type TEXT,
        title TEXT,
        status TEXT,
        priority TEXT,
        due DATE,
        created DATETIME,
        modified DATETIME,
        tags TEXT,                  -- JSON array
        content_hash TEXT,          -- 用于变更检测
        file_path TEXT
    );
    
    -- 全文搜索 (FTS5) - 存储预分词后的内容
    CREATE VIRTUAL TABLE resources_fts USING fts5(
        id, title_tokens, tags_tokens, content_tokens,
        tokenize = 'simple'  -- 使用默认分词器,输入已分词字符串
    );
    


    **中文搜索:应用层分词方案**(推荐)

    SQLite 原生不支持加载 Python jieba 作为分词器。最佳实践是**应用层分词**:

    
    import jieba
    import re
    
    # 停用词表(标点、空格、无意义字符)
    STOPWORDS = set(['的', '了', '是', '在', '我', '有', '和', '就', 
                      '不', '人', '都', '一', '一个', '上', '也', '很',
                      '到', '说', '要', '去', '你', '会', '着', '没有'])
    
    class SearchEngine:
        def _tokenize(self, text: str) -> str:
            """分词 + 停用词过滤 + 标点清理"""
            # 1. 移除标点符号和特殊字符
            text = re.sub(r'[^\w\s\u4e00-\u9fff]', ' ', text)
            
            # 2. jieba 分词
            tokens = jieba.cut_for_search(text)
            
            # 3. 停用词过滤 + 去除空白
            filtered = [t.strip() for t in tokens 
                        if t.strip() and t not in STOPWORDS]
            
            return " ".join(filtered)
        
        def update_index(self, id: str, title: str, content: str):
            """写入索引前,用 jieba 预处理 + 停用词过滤"""
            title_tokens = self._tokenize(title)
            content_tokens = self._tokenize(content)
            
            cursor.execute("""
                INSERT INTO resources_fts (id, title_tokens, content_tokens) 
                VALUES (?, ?, ?)
            """, (id, title_tokens, content_tokens))
        
        def search(self, query: str):
            """搜索时,同样分词处理"""
            query_tokens = " AND ".join(self._tokenize(query).split())
            
            cursor.execute("""
                SELECT id FROM resources_fts 
                WHERE content_tokens MATCH ?
            """, (query_tokens,))
    


    **停用词过滤的好处**:
    - 减小 index.db 体积(去除 30%+ 无意义词)
    - 提高搜索精度(避免"的"、"了"等高频词干扰)
    - 加快搜索速度

    **工作流程**:
    - **写入**:"完成了需求设计!" → 清理标点 → jieba → 过滤停用词 → "完成 需求 设计" → 存入 FTS
    - **搜索**:用户搜 "需求" → 同样处理 → "需求" → MATCH 成功

    ### 13.3 索引更新策略

    | 触发时机 | 动作 |
    |----------|------|
    | syn 启动 | 检测 Git 变更,增量更新索引 |
    | CRUD 操作 | 实时更新索引 |
    | Git hook | post-merge, post-checkout 触发重建 |
    | 手动 | syn index rebuild 强制重建 |

    ### 13.4 查询路由
  6. # config.yaml
    media:
      provider: s3  # s3 | github | local
      
      # 凭证配置
      credentials:
        # 方式 1:环境变量(推荐)
        aws_access_key_env: AWS_ACCESS_KEY_ID
        aws_secret_key_env: AWS_SECRET_ACCESS_KEY
        # 方式 2:配置文件(不推荐,敏感信息)
        # aws_access_key: "AKIA..."
        # aws_secret_key: "..."
      
      s3:
        bucket: your-bucket
        region: us-east-1
        prefix: synapse/           # 存储前缀
        # 最终路径:s3://your-bucket/synapse/{resource_id}/{filename}
      
      github:
        repo: user/media
        branch: main
        token_env: GITHUB_TOKEN    # GitHub Personal Access Token
      
      local:
        path: ~/synapse-media      # 本地存储路径(.gitignore 隔离)
        base_url: file://~/synapse-media
    


    ### 8.5 TUI 集成(预留)

    在编辑模式下:
    - Ctrl+V`:检测剪贴板图片 → 自动调用 `syn media paste → 插入链接

    ---

    ## 9. 自动化工作流

    ### 9.1 本地自动化(优先)

    对于生成类任务,优先使用**本地 Hook**,无需等待网络:

    
    # 本地执行,立即生成并打开
    syn review daily      # → 生成今日回顾,直接打开编辑
    syn review weekly     # → 生成周报,直接打开编辑
    syn review monthly    # → 生成月报,直接打开编辑
    syn standup           # → 生成站会报告
    
    # LLM 辅助生成(可选)
    syn review daily --llm     # → 使用 LLM 生成摘要
    syn review weekly --llm    # → 使用 LLM 生成周报
    syn review monthly --llm   # → 使用 LLM 生成月报
    


    #### 9.1.1 Review 资源类型与存储

    Review 生成的资源是 note 类型,存储在配置的 review Area/Category 下:

    
    personal/
    └── 90/                    # Area 90: 回顾与复盘
        ├── 001/               # Category 001: 每日回顾
        │   ├── 90.001.0001-note-2024-01-15-daily-review.md
        │   └── 90.001.0002-note-2024-01-16-daily-review.md
        ├── 002/               # Category 002: 每周回顾
        │   └── 90.002.0001-note-2024-W03-weekly-review.md
        ├── 003/               # Category 003: 每月回顾
        │   └── 90.003.0001-note-2024-01-monthly-review.md
        └── 004/               # Category 004: 站会报告
            └── 90.004.0001-note-2024-01-15-standup.md
    


    **Frontmatter 示例**:
    
    ---
    jd_number: "90.001.0015"
    type: note
    namespace: personal
    title: "2024-01-15 每日回顾"
    tags: [review, daily]
    created: 2024-01-15T18:00:00Z
    modified: 2024-01-15T18:30:00Z
    review_type: daily          # daily | weekly | monthly | standup
    review_period: "2024-01-15"
    llm_generated: false        # 是否使用 LLM 生成
    ---
    


    **本地 Hook 配置**:
    
    # config.yaml
    hooks:
      review_daily:
        command: "python ~/.synapse/hooks/daily_review.py"
        auto_open: true   # 生成后自动打开编辑器
      review_weekly:
        command: "python ~/.synapse/hooks/weekly_review.py"
        auto_open: true
      review_monthly:
        command: "python ~/.synapse/hooks/monthly_review.py"
        auto_open: true
      standup:
        command: "python ~/.synapse/hooks/standup.py"
        auto_open: true
    
    review:
      area: "90"              # Review 存储的 Area
      daily_category: "001"   # syn review daily
      weekly_category: "002"  # syn review weekly
      monthly_category: "003" # syn review monthly
      standup_category: "004" # syn standup
      llm:
        enabled: true
        prompt_template: "~/.synapse/prompts/review.md"
    


    ### 9.2 GitHub Actions 模板(远程)

    用于**跨设备通知**和**长期归档**:

    | 模板 | 触发 | 功能 |
    |------|------|------|
    | daily-standup | 工作日 9:00 | 昨日完成、今日计划、阻塞问题 |
    | weekly-review | 周五 18:00 | 周报生成 |
    | due-reminder | 每天 8:00 | 到期/逾期提醒 |
    | inbox-reminder | 工作日 17:00 | Inbox 清理提醒 |
    | monthly-retro | 每月 1 日 | 月度复盘归档 |
    | graph-sync | push 触发 | 图谱同步 |

    **设计理念**:
    - **本地 Hook**:生成类(review, standup)→ 即时响应,无网络依赖
    - **GitHub Actions**:通知类、归档类 → 跨设备、定时触发

    ---

    ## 10. 知识图谱(优先级低,预留设计)

    > ⚠️ 本章节为预留设计,待后续迭代完善。初始版本不实现图谱功能。

    ### 10.1 预留接口

    
    syn graph                    # 启动图谱视图(TUI)
    syn graph export             # 导出图谱数据
    syn graph sync               # 同步到 Nebula Graph(如配置)
    


    ### 10.2 数据模型(预留)

    **节点类型**:Resource, Namespace, Tag, Person, TimePoint, Chunk

    **边类型**:RELATED_TO, BELONGS_TO, CREATED_BY, ASSIGNED_TO, TAGGED_WITH, BLOCKED_BY 等

    ### 10.3 GraphRAG 集成(预留)

    - 使用 Nebula Graph 存储知识图谱
    - 支持 LLM 驱动的图谱查询和推理
    - 自动发现资源间的隐含关系

    ---

    ## 11. 邮件集成(优先级低,预留设计)

    > ⚠️ 本章节为预留设计,待后续迭代完善。初始版本仅支持手动创建邮件资源。

    ### 11.1 手动创建(初始版本支持)

    
    syn new email --subject "项目进度汇报"
    


    - 支持剪贴板粘贴邮件内容
    - 自动解析标题和正文
    - 发件人/收件人需手动填写

    ### 11.2 API 集成(预留)
  7. **字段级合并策略配置**:
    
    # config.yaml
    git:
      merge_strategy:
        # 默认策略:取 modified 最新的一方 (Last Write Wins)
        default: "take_latest_modified"
        
        # 特殊字段策略
        fields:
          # 标签:取并集。A端加了[urgent], B端加了[work] -> [urgent, work]
          tags: "union"
          
          # 关联:取并集
          related: "union"
          
          # 状态:取最新修改方(或自定义优先级:done > in-progress > todo)
          status: "take_latest_modified"
          
          # 正文内容:标准 Git 文本合并
          content: "git_standard"
    


    **冲突安全网**:
    - 当 Merge Driver 无法自动解决(如正文同一行被修改):
    - 生成 .conflict 备份文件
    - TUI 启动时检测到冲突文件 → 进入"冲突解决向导"模式
    - 用户可选择:保留本地 / 保留远程 / 手动合并

    ### 7.6 并发编辑检测

    **场景**:TUI 打开时,用户用 VS Code 编辑同一文件。

    **方案 1:Hash 检测**(基础)
    1. syn edit 前:记录文件 hash
    2. 外部编辑器关闭后:检查 hash
    3. 如果 hash 变化且非当前进程修改:
    - 提示冲突
    - 显示 diff
    - 用户选择:覆盖 / 合并 / 放弃

    **方案 2:watchdog 实时监听**(增强)

    使用 watchdog 库监听文件系统事件,TUI 运行时实时感知外部修改:

    
    # 伪代码示意 - 注意线程安全!
    from watchdog.observers import Observer
    from watchdog.events import FileSystemEventHandler
    
    class SynapseFileWatcher(FileSystemEventHandler):
        def __init__(self, app):
            self.app = app  # Textual App 实例
        
        def on_modified(self, event):
            if event.src_path == self.app.current_open_file:
                # ⚠️ 关键:watchdog 在独立线程,必须用 call_from_thread
                self.app.call_from_thread(
                    self.app.show_file_modified_warning,
                    event.src_path
                )
    


    **线程安全注意**:
    - watchdog 运行在独立线程
    - Textual UI 更新必须在主线程
    - 必须使用 app.call_from_thread() 跨线程调用,否则 TUI 会崩溃

    **自激抑制(Suspension Context)**:

    ⚠️ **问题**:当 Synapse 执行 git pullgit checkout 时,文件系统变化会触发 watchdog,错误地向用户报告"外部修改警告"。

    
    class FileWatcher:
        def __init__(self):
            self._suspended = False
        
        def pause(self):
            """暂停监听(Git 操作前调用)"""
            self._suspended = True
        
        def resume(self):
            """恢复监听(Git 操作后调用)"""
            self._suspended = False
        
        def on_modified(self, event):
            if self._suspended:
                return  # 忽略自身触发的变更
            # ... 正常处理外部修改
    
    class SynapseApp:
        def perform_git_operation(self):
            self.file_watcher.pause()  # 暂停监听
            try:
                git.pull()
                self.index.refresh()   # 主动刷新索引
            finally:
                self.file_watcher.resume()  # 恢复监听
    


    **行为**:
    - TUI 打开时,自动启动 watchdog **仅监听当前 Namespace 目录**(性能优化)
    - 切换 Namespace 时,watchdog 同步切换监听目标
    - 检测到当前查看/编辑的文件被外部修改 → 实时弹出警告
    - 用户可选择:重新加载 / 忽略 / 查看 diff
    - **Git 操作期间自动暂停监听**,避免误报

    
    class SynapseFileWatcher:
        def __init__(self, app):
            self.app = app
            self.observer = Observer()
            self._current_path = None
            self._started = False
        
        def start(self):
            """启动观察者线程(TUI 启动时调用)"""
            if not self._started:
                self.observer.start()
                self._started = True
        
        def stop(self):
            """停止观察者线程(TUI 退出时调用)"""
            if self._started:
                self.observer.stop()
                self.observer.join()  # 等待线程结束
                self._started = False
        
        def watch_namespace(self, namespace_path: str):
            """切换监听目标到指定 Namespace"""
            # 1. 取消之前的监听
            if self._current_path:
                self.observer.unschedule_all()
            
            # 2. 调度新的监听目标
            self._current_path = namespace_path
            self.observer.schedule(
                self, 
                namespace_path, 
                recursive=True  # 监听 Namespace 下所有子目录
            )
            
            # 3. 确保观察者已启动
            self.start()
    


    ---

    ## 8. 媒体管理(优先级低,预留设计)

    > ⚠️ 本章节为预留设计,待后续迭代完善。初始版本仅支持外部链接引用媒体。

    ### 8.1 设计原则

    - **仓库纯净**:禁止二进制文件直接入库
    - **体验友好**:提供便捷的截图/贴图工作流

    ### 8.2 syn media 命令

    
    # 从剪贴板上传图片
    syn media paste
    # → 自动上传到配置的存储 → 返回 Markdown 链接
    
    # 上传指定文件
    syn media upload /path/to/image.png
    
    # 输出示例
    ![image](https://your-bucket.s3.amazonaws.com/synapse/10.001.0015/abc123.png)
    


    ### 8.3 媒体存储目录结构

    **建议使用资源 ID 作为目录名**,便于关联和管理:

    
    媒体存储根目录/
    ├── 10.001.0015/                # 资源 ID 为目录名
    │   ├── screenshot-001.png
    │   └── diagram.svg
    ├── 10.002.0003/
    │   └── meeting-photo.jpg
    └── _orphan/                    # 未关联资源的媒体(待清理)
        └── temp-upload.png
    


    ### 8.4 媒体存储配置
  8. **TUI 交互**:
    
    ┌─────────────────────────────────────────────────────────┐
    │ ⚠️  Refile 需要干净的工作区                              │
    ├─────────────────────────────────────────────────────────┤
    │ 检测到以下未提交的修改:                                 │
    │   M  10/001/10.001.0005-note-学习笔记.md               │
    │   M  20/001/20.001.0003-task-开发任务.md               │
    │                                                         │
    │ 请先提交或暂存这些修改:                                 │
    │   [C] 快速提交 (commit -m 'WIP')                        │
    │   [S] 暂存 (git stash)                                  │
    │   [Q] 取消 Refile                                       │
    └─────────────────────────────────────────────────────────┘
    


    **行为**:
    - Refile 前**必须检查**工作区状态
    - 脏工作区时拒绝操作,提示用户先提交/暂存
    - TUI 提供快捷操作:一键 Commit WIP 或 Stash
    - 绝不丢失用户的其他修改

    **更新的引用类型**:

    | 引用形式 | 示例 | 搜索方式 | 更新方式 |
    |----------|------|----------|----------|
    | Wiki-style 链接 | [[10.001.0001]] | Ripgrep | 字符串替换 |
    | Frontmatter related | related: ["10.001.0001"] | SQLite 索引 | 解析 YAML,更新数组 |

    设计原则:精确引用更新,避免误改

    ⚠️ **不进行盲目的全文替换**:只更新明确的引用语法,不替换正文中的裸 ID 字符串。

    
    ---
    related: ["10.001.0001"]  # ← YAML 数组,精确更新 ✓
    ---
    
    # 设计说明
    
    参考 [[10.001.0001]] 的架构思路...     # ← Wiki-style 链接,更新 ✓
    任务 10.001.0001 定义了核心接口...      # ← 裸 ID,不更新 ✗(可能是历史描述)
    


    为什么不替换裸 ID?
    - 裸 ID 可能出现在非引用上下文:`"历史记录显示 10.001.0001 已于 2023 年完成"`
    - 格式说明文本:`"ID 格式为 10.001.0001,其中 10 表示 Area..."`
    - 盲目替换会导致语义错误和数据损坏
    - 用户若需更新裸 ID,可通过 --dry-run 预览后手动处理

    为什么 related 字段仍需 SQLite?

    related 是 YAML 数组格式,直接全文替换可能破坏 YAML 结构:

    
    # 原始
    related: ["10.001.0001", "10.002.0003"]
    
    # Ripgrep 替换后可能出现的问题(如果替换逻辑不够精确)
    related: ["20.001.0005", "10.002.0003"]  # ✓ 正确
    related: ["20.001.0005", "20.001.0005"]  # ✗ 如果 10.002.0003 也被误改
    


    **SQLite 方案确保**:
    - 查询精确定位到 related 字段
    - 解析 JSON 数组后,只替换匹配的元素
    - 重新序列化,保证 YAML 格式正确

    **Refile 预览模式**:

    为避免意外修改,支持 --dry-run 预览变更:

    
    syn inbox move 00.000.0001 --to work-project --dry-run
    
    # 输出预览:
    Will refile: 00.000.0001 → 20.001.0003
    Source file:
      R  inbox/00.000.0001-unprocessed-xxx.md → work-project/20/001/20.001.0003-task-xxx.md
         - Frontmatter: jd_number: "00.000.0001" → "20.001.0003"
    
    References to update (2 files):
      M  personal/10/001/10.001.0005-note-设计笔记.md
         - Line 5: related: ["00.000.0001"]["20.001.0003"]
         - Line 12: [[00.000.0001]][[20.001.0003]]
      M  personal/10/002/10.002.0001-task-开发任务.md
         - Line 15: [[00.000.0001]][[20.001.0003]]
    
    Skipped (bare IDs not updated):
      -  personal/10/003/10.003.0001-note-历史记录.md
         - Line 8: "任务 00.000.0001 已于 2023 年完成"(裸 ID,需手动处理)
    
    Proceed? [y/N]
    


    > **注意**:预览中的 "Skipped" 部分提示用户哪些裸 ID 不会被自动更新,用户可根据需要手动处理。

    ---

    ## 6. 工作流与回顾

    ### 6.1 回顾类型

    | 类型 | 触发 | 内容 |
    |------|------|------|
    | 每日 | syn review daily | 今日到期、今日会议、昨日未完成 |
    | 每周 | syn review weekly | 本周完成、下周计划、项目进度 |
    | Inbox | syn inbox process | 处理未分类项目 |

    ### 6.2 智能提醒

    - 任务接近截止日期时高亮显示
    - 资源长期未访问时在回顾中提示
    - 阻塞任务链识别和提示

    ---

    ## 7. 存储与版本控制

    ### 7.1 Markdown 格式

    **文件命名**:`{Area}.{Category}-{resource-type}-{title}.md`

    **Frontmatter 示例**:
    
    ---
    jd_number: "10.001.0015"
    type: task
    namespace: work-project
    title: "完成项目设计"
    status: in-progress
    priority: high
    due: 2024-01-15
    created: 2024-01-01T10:00:00Z
    modified: 2024-01-05T14:30:00Z
    tags: [design, urgent]
    related: ["10.001.0012", "10.002.0003"]
    ---
    


    ### 7.2 文件约束

    - 仅允许 .md 文件
    - 禁止 图片、视频、音频等二进制文件
    - 必须 使用外部链接引用媒体资源
    - .gitignore 配置忽略常见二进制格式

    ### 7.3 Git 集成

    - 自动提交:所有 CRUD 操作
    - commit 格式:Conventional Commits
    - 支持多远程仓库
    - 支持 SSH/HTTPS 认证

    ### 7.4 Git 同步策略

    | 时机 | 动作 |
    |------|------|
    | syn 启动 | git pull --rebase |
    | CRUD 操作 | git add + git commit |
    | syn 退出 / syn sync | git push |

    ### 7.5 智能冲突合并

    **问题**:多端同步时,`modified` 时间戳经常冲突。

    **方案**:自定义 Git merge driver + 细粒度字段策略

    
    # .gitattributes
    *.md merge=synapse-frontmatter
    


    
    # .git/config 或全局配置
    [merge "synapse-frontmatter"]
        name = Synapse Frontmatter Merge
        driver = syn merge-driver %O %A %B %P
  9. def refile_with_link_update(old_id, new_id, old_path, new_path, workspace):
        # 0. 前置检查:工作区必须干净
        if not is_working_tree_clean():
            raise RefileError(
                "Refile requires a clean working tree.\n"
                "Please commit or stash your changes first:\n"
                "  syn commit -m 'WIP'  或  git stash"
            )
        
        try:
            # 1. 移动源文件(暂存)
            git_mv(old_path, new_path)
            
            # 1b. 更新源文件的 Frontmatter(jd_number 字段)
            source_content = read_file(new_path)  # 文件已移动,从新路径读取
            source_fm, source_body = parse_frontmatter(source_content)
            source_fm['jd_number'] = new_id  # 更新 ID
            source_fm['modified'] = datetime.now().isoformat()  # 更新修改时间
            write_file(new_path, serialize_frontmatter(source_fm) + source_body)
            git_add(new_path)  # ⚠️ 必须暂存源文件的内容修改(git_mv 只暂存重命名)
            
            # 2. 查找并更新所有引用(其他文件)
            # ⚠️ 顺序重要:先 YAML 精确处理,再全文替换,避免竞态
            modified_files = set()
            files_with_related = set()  # 记录有 related 字段的文件
            
            # 2a. 先处理 Frontmatter 中的 related 字段(YAML 精确解析)
            # 必须先于全文替换,否则 old_id 已被替换,条件判断会失败
            related_refs = db_query(
                "SELECT file_path FROM resources WHERE related LIKE ?",
                (f'%"{old_id}"%',)  # 在 JSON 数组字符串中搜索
            )
            for (file_path,) in related_refs:
                content = read_file(file_path)
                frontmatter, body = parse_frontmatter(content)
                # 安全检查:确保 related 是非空列表
                related = frontmatter.get('related')
                if isinstance(related, list) and old_id in related:
                    # 精确更新 YAML 数组
                    frontmatter['related'] = [
                        new_id if rid == old_id else rid 
                        for rid in related
                    ]
                    # 只替换 wiki-style 链接(避免误改非引用上下文)
                    body = body.replace(f"[[{old_id}]]", f"[[{new_id}]]")
                    content = serialize_frontmatter(frontmatter) + body
                    write_file(file_path, content)
                    modified_files.add(file_path)
                    files_with_related.add(file_path)
            
            # 2b. 搜索 wiki-style 链接:[[old_id]]
            # ⚠️ 只更新明确的引用语法,不盲目替换正文中的 ID 字符串
            wiki_refs = rg_search(f"\\[\\[{old_id}\\]\\]", workspace)
            for file_path in wiki_refs:
                if file_path in files_with_related:
                    continue  # 已被 2a 处理,跳过
                content = read_file(file_path)
                content = content.replace(f"[[{old_id}]]", f"[[{new_id}]]")
                write_file(file_path, content)
                modified_files.add(file_path)
            
            # 3. 暂存所有修改
            for f in modified_files:
                git_add(f)
            
            # 4. 提交事务(+1 是源文件本身)
            git_commit(f"refactor: refile {old_id}{new_id}, update {len(modified_files) + 1} files")
            
        except Exception as e:
            # 4. 安全回滚(因为工作区干净,此时可以安全 reset)
            git_reset_hard()
            logger.error(f"Refile failed, rolled back: {e}")
            raise RefileError("操作失败,已回滚所有修改")
    
    def is_working_tree_clean() -> bool:
        """检查工作区是否干净"""
        result = subprocess.run(
            ["git", "status", "--porcelain"],
            capture_output=True, text=True
        )
        return len(result.stdout.strip()) == 0
    


    策略 B:智能回滚(复杂场景)

    如果必须允许脏工作区 Refile,回滚时需精确还原:

    
    def smart_rollback(modified_files: list, old_path: str, new_path: str):
        """精确回滚,不影响其他未提交修改"""
        # 1. Unstage 暂存的文件
        for f in modified_files:
            subprocess.run(["git", "reset", "HEAD", f])
        
        # 2. 还原被修改的文件内容
        for f in modified_files:
            subprocess.run(["git", "checkout", "--", f])
        
        # 3. 移回源文件
        if os.path.exists(new_path):
            subprocess.run(["git", "mv", new_path, old_path])
  10. ┌─────────────────────────────────────────────┐
    │ Refile: 00.000.0001 - 快速记录              │
    ├─────────────────────────────────────────────┤
    │ [1] personal                                │
    │ [2] work-project                            │
    │ [3] study                                   │
    │ [q] Cancel                                  │
    └─────────────────────────────────────────────┘
             ↓ 按 2
    ┌─────────────────────────────────────────────┐
    │ Refile to: work-project                     │
    ├─────────────────────────────────────────────┤
    │ [20] 项目管理                               │
    │ [21] 开发                                   │
    │ [22] 设计                                   │
    │ [+] 新建 Area                               │
    │ [q] Back                                    │
    └─────────────────────────────────────────────┘
             ↓ 按 20
    ┌─────────────────────────────────────────────┐
    │ Refile to: work-project/20                  │
    ├─────────────────────────────────────────────┤
    │ [001] 任务                                  │
    │ [002] 会议                                  │
    │ [+] 新建 Category                           │
    │ [q] Back                                    │
    └─────────────────────────────────────────────┘
             ↓ 按 001
    ✓ Moved to: work-project/20/001/20.001.0003-task-快速记录.md
    


    **关键设计**:
    - 用户只需选"要去哪个区",**不需要手动输入 ID**
    - 系统自动分配下一个可用 ID
    - 支持快捷键直接跳转(数字键选择)
    - 支持 / 搜索过滤 Namespace/Area/Category

    ### 5.5 引用自动更新(Link Refactoring)

    **问题**:Refile 导致 ID 变化时,其他文件中的引用会断裂。

    
    场景:
    1. Inbox 中有 00.000.0001(想法A)
    2. 笔记 10.002.0005 中写道:"参考 [[00.000.0001]]"
    3. Refile 后,A 变为 20.001.0003
    4. 结果:[[00.000.0001]] 成为 Dead Link
    


    解决方案:自动重构所有引用

    当执行 syn inbox move 且 ID 发生变化时,更新**两种引用形式**:

    
    1. 搜索旧 ID 的引用(使用不同策略):
       - Wiki-style 链接:Ripgrep 搜索 [[00.000.0001]]
       - Frontmatter related:SQLite 索引查询(精确匹配,避免误改)
    2. 自动替换为新 ID
    3. 将所有修改一并加入 Git Commit
    


    **实现逻辑(带安全回滚)**:

    ⚠️ **原子性保证**:Refile 涉及跨多文件修改,必须保证原子性。

    ⚠️ **安全警告**:绝对不能使用 `git reset --hard`!它会清空**所有**未提交的修改,包括与 Refile 无关的用户编辑。

    策略 A:前置检查(推荐)

    在开始 Refile 前,检查工作区是否干净:
  11. ## 5. Inbox 处理与最佳实践

    ### 5.1 Inbox 概念

    - Inbox 是特殊 namespace(`_inbox`),Area 固定为 00
    - 快速捕获的内容默认进入 Inbox,状态为 unprocessed
    - Inbox 中的资源无需完整,支持最小化输入

    ### 5.2 处理操作

    | 操作 | 说明 | 命令 |
    |------|------|------|
    | 归类 (Refile) | 移动到目标 namespace,分配 Area.Category | syn inbox move <id> --to <ns> --type <type> |
    | 转换 | 转换资源类型(如 note → task) | syn inbox convert <id> --type task |
    | 归档 | 原地标记 status=`archived`,不移动文件 | syn inbox archive <id> |
    | 删除 | 永久删除(Git 可恢复) | syn inbox delete <id> |

    **归档说明**:
    - 归档操作**不移动文件**,仅修改 Frontmatter 中的 status 字段为 archived
    - 归档后的资源在列表中默认隐藏,可通过 --include-archived 显示
    - 如需移动到特定归档目录,应使用 syn inbox move --to archive-namespace
    - **`unprocessed` 类型特殊处理**:由于 unprocessed 状态不可变,归档时自动转换为 note 类型并标记 status=archived
    
      syn inbox archive 00.000.0001
      # unprocessed → note (status=archived)
      # 文件重命名:00.000.0001-unprocessed-xxx.md → 00.000.0001-note-xxx.md
      


    ### 5.3 Inbox 处理最佳实践(GTD 风格)

    处理流程决策树:

    
                        ┌─────────────┐
                        │  Inbox 项目  │
                        └──────┬──────┘
                               │
                        ┌──────▼──────┐
                        │ 这是什么?   │
                        │ 需要行动吗? │
                        └──────┬──────┘
                               │
                  ┌────────────┼────────────┐
                  │            │            │
             ┌────▼────┐  ┌────▼────┐  ┌────▼────┐
             │  不需要  │  │ 需要行动 │  │ 参考资料 │
             └────┬────┘  └────┬────┘  └────┬────┘
                  │            │            │
             ┌────▼────┐       │       ┌────▼────┐
             │  删除   │       │       │ 归类为  │
             │  或归档  │       │       │  note   │
             └─────────┘       │       └─────────┘
                               │
                  ┌────────────┼────────────┐
                  │            │            │
             ┌────▼────┐  ┌────▼────┐  ┌────▼────┐
             │ < 2分钟  │  │ 委派他人 │  │ 需要规划 │
             │ 立即做! │  │         │  │         │
             └────┬────┘  └────┬────┘  └────┬────┘
                  │            │            │
             ┌────▼────┐  ┌────▼────┐  ┌────▼────┐
             │ 完成后   │  │ 创建任务 │  │ 创建任务 │
             │ 归档/删除│  │ 分配给   │  │ 设置    │
             └─────────┘  │ 他人     │  │ 截止日期 │
                          └─────────┘  └─────────┘
    


    最佳实践规则:

    1. **2 分钟规则**:如果一件事 2 分钟内能完成,立即做,不要放入任务列表
    2. **每日清空**:每天至少处理一次 Inbox,目标是 Inbox Zero
    3. **快速决策**:每个项目只处理一次,立即决定其去向
    4. **最小化输入**:捕获时只记录关键信息,处理时再补充细节

    处理快捷流程:

    
    # 批量处理 Inbox
    syn inbox process
    
    # 交互式处理(逐项显示,提供操作选项)
    # [m]ove | [c]onvert | [a]rchive | [d]elete | [s]kip
    
    # 快速转换为任务
    syn inbox convert <id> --type task --due tomorrow --priority high
    
    # 快速归类
    syn inbox move <id> --to work-project --type meeting
    


    TUI Inbox 处理快捷键:

    | 快捷键 | 操作 | 说明 |
    |--------|------|------|
    | Enter | 查看详情 | 与全局快捷键一致 |
    | e | 编辑后处理 | 打开编辑器,关闭后弹出处理选项 |
    | m | 移动(Refile) | 层级菜单选择目标位置 |
    | t | 快速转换为 task | 一键转换,保留原位置 |
    | a | 归档 | 标记 status=archived |
    | d | 删除 | 永久删除(Git 可恢复) |

    ### 5.4 Refile 交互优化(类似 Emacs Org-mode)

    m (move) 是最高频操作,TUI 实现层级菜单式 Refile:
  12. ┌─────────────────────────────────────────────┐
    @work-project/20 │ ← 范围搜索
    ├─────────────────────────────────────────────┤
    │ 显示 work-project namespace Area 20 资源 │
    └─────────────────────────────────────────────┘
    
    
    ### 4.5 上下文感知交互
    
    | 视图 | `Enter` | `Space` | `e` |
    |------|---------|---------|-----|
    | Task 列表 | 查看详情 | 切换 status: `todo` → `in-progress` → `done` | 编辑 |
    | Meeting 列表 | 查看详情 | 切换 status: `scheduled` ↔ `completed` | 编辑 |
    | Email 列表 | 查看详情 | 切换 status: `unread` → `read` → `replied` | 编辑 |
    | Note 列表 | 查看详情 | (无活跃状态循环,使用 `a` 键归档) | 编辑 |
    | Calendar (日) | 资源详情 | 同上(根据资源类型) | 新建事项 |
    | Inbox | 查看详情 | (unprocessed 状态不可切换) | 编辑后处理 |
    
    **Space 状态循环**(仅限活跃状态):
    - Task: `todo` → `in-progress` → `done` → `todo`
    - Meeting: `scheduled` ↔ `completed`(二元切换)
    - Email: `unread` → `read` → `replied` → `unread`
    - Note: **无 Space 循环**(仅有 `active`/`archived` 两态,使用 `a` 键切换)
    
    **终态处理**(`archived` / `cancelled`):
    - `archived` 和 `cancelled` 是**终态**,不参与 Space 循环
    - 当资源处于终态时,Space 键**无响应**(或显示提示"资源已归档/取消")
    - 进入/退出终态:使用 `a` 键(归档/取消归档)或编辑 Frontmatter
    
    | 状态 | 类型 | 进入方式 | 退出方式 |
    |------|------|----------|----------|
    | `archived` | task, email | `a` 键归档 | `a` 键取消归档(恢复到 `todo`/`unread`) |
    | `archived` | note | `a` 键归档 | `a` 键取消归档(恢复到 `active`) |
    | `cancelled` | meeting | 编辑 status 字段 | 编辑 status 字段 |
    
    **`a` 键行为**:
    - 活跃状态 → `archived`(归档)
    - `archived` → 恢复到默认活跃状态(task→`todo`, email→`unread`, note→`active`)
    
    ### 4.6 编辑器配置
    
    | 平台 | 默认 | 可选 |
    |------|------|------|
    | Windows | notepad | typora, vscode |
    | Linux | vim | typora |
    | macOS | vim | typora |
    
    ### 4.7 日历视图
    
    **时间来源**:
    - task: `due` / `start_date`
    - meeting: `datetime` / `duration`
    
    **条带说明**:每个条带代表一个资源,高度表示时间跨度,颜色表示类型/优先级。
    
    **交互**:
    - `↑↓` 在同一天的条带间切换选中
    - `←→` 在日期间导航
    - 选中条带时,底部显示资源摘要
    - `Enter` 查看资源详情
    
    

    Calendar - Week (2024-W03) [personal]
    ┌────────┬────────┬────────┬────────┬────────┬────────┬────────┐
    │ Mon 15 │ Tue 16 │ Wed 17 │ Thu 18 │ Fri 19 │ Sat 20 │ Sun 21 │
    ├────────┼────────┼────────┼────────┼────────┼────────┼────────┤
    │ ▓▓▓▓ │ ██ │ ▓▓▓▓▓▓ │ ████ │ ██ │ │ │
    │ ████ │ │ ▓▓▓▓ │ ▓▓ │ │ │ │
    ├────────┴────────┴────────┴────────┴────────┴────────┴────────┤
    [Wed 14:00] 项目评审会议 (meeting) - 14:00~16:00 │
    │ Attendees: @alice, @bob │
    └──────────────────────────────────────────────────────────────┘
    [↑↓] Select [←→] Navigate [Enter] Details [w/m] Week/Month [q] Back

    图例:▓▓ = meeting (紫色) ██ = task (蓝色) ░░ = note (灰色)
    
    
    **跨天显示**:start_date 到 due 的任务、跨天会议,在每天都显示连续条带。
    
    ### 4.8 时间线视图
    
    **时间来源**:Git commit 时间戳(资源的 `modified` 时间)
    
    **条带说明**:每列代表一天,条带高度表示当天活动密度,每个条带对应一次 commit。
    
    **交互**:
    - `←→` 在时间轴上导航
    - `↑↓` 在同一天的 commit 间切换
    - 选中 commit 时,底部显示 commit 信息和涉及的资源
    - `Enter` 跳转到该资源
    
    

    Timeline - Last 7 Days [personal]
    ─────┬──────┬──────┬──────┬──────┬──────┬──────┬─────────
    │ ▓▓ │ ████ │ ██ │ ████ │ ▓▓ │██████│ ██
    │ ████ │ ▓▓ │ ████ │ ██ │ ████ │ ▓▓▓▓ │ ████
    ─────┴──────┴──────┴──────┴──────┴──────┴──────┴─────────
    Mon Tue Wed Thu Fri Sat Sun

    [Sun 14:32] feat(task): add new task for review
    10.001.0015-task-完成项目设计.md
    ──────────────────────────────────────────────────────────
    [↑↓] Select [←→] Navigate [Enter] Resource [d/w/m/y] Scale [q] Back

    图例:▓▓ = meeting ██ = task ░░ = note (颜色同日历视图)
    
    
    ### 4.9 会话持久化(Session Persistence)
    
    系统在 `.synapse/session.json`(不入 Git)中记录当前上下文状态:
    
    ```json
    {
      "last_active_namespace": "work-project",
      "namespaces": {
        "personal": {
          "last_view": "calendar",
          "selected_date": "2024-01-20",
          "recent_files": ["10.001.0001", "10.002.0005"]
        },
        "work-project": {
          "last_view": "list",
          "filter_state": {"status": "todo", "tag": "urgent"},
          "recent_files": ["20.001.0003"]
        }
      },
      "global_history": [
        "syn show 10.001.0001",
        "syn search kubernetes"
      ]
    }
    


    **行为**:
    - 启动 syn 时,自动恢复到上次退出的 Namespace 和 View
    - Ctrl+Tab 可在最近打开的两个文件/视图间快速切换
    - 每个 Namespace 独立记录筛选状态和最近文件

    ---