From f64678a1089658b968ec86bb88b77d921cca4c4f Mon Sep 17 00:00:00 2001 From: ToussaintKnight <272199827+ToussaintKnight@users.noreply.github.com> Date: Sun, 12 Jul 2026 11:55:31 +0800 Subject: [PATCH] Upgrade repository trust surface --- .gitattributes | 1 + .github/workflows/ci.yml | 6 +- CHANGELOG.md | 6 ++ CHANGELOG.zh-CN.md | 24 +++++++ CONTRIBUTING.md | 8 ++- CONTRIBUTING.zh-CN.md | 39 ++++++++++ README.md | 108 ++++++++++++++-------------- README.zh-CN.md | 126 +++++++++++++++++++++++++++++++++ SECURITY.md | 4 ++ SECURITY.zh-CN.md | 38 ++++++++++ docs/architecture.md | 53 ++++++++++++++ docs/architecture.zh-CN.md | 53 ++++++++++++++ docs/demo.md | 4 +- docs/demo.zh-CN.md | 21 ++++++ docs/roadmap.md | 2 + docs/roadmap.zh-CN.md | 18 +++++ package.json | 4 +- scripts/check-docs.mjs | 71 +++++++++++++++++++ scripts/check-repo-hygiene.mjs | 73 +++++++++++++++++++ 19 files changed, 598 insertions(+), 61 deletions(-) create mode 100644 CHANGELOG.zh-CN.md create mode 100644 CONTRIBUTING.zh-CN.md create mode 100644 README.zh-CN.md create mode 100644 SECURITY.zh-CN.md create mode 100644 docs/architecture.md create mode 100644 docs/architecture.zh-CN.md create mode 100644 docs/demo.zh-CN.md create mode 100644 docs/roadmap.zh-CN.md create mode 100644 scripts/check-docs.mjs create mode 100644 scripts/check-repo-hygiene.mjs diff --git a/.gitattributes b/.gitattributes index a597491..92f4760 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,7 @@ * text=auto *.sh text eol=lf *.mjs text eol=lf +*.json text eol=lf *.md text eol=lf *.yml text eol=lf *.yaml text eol=lf diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ece669e..f118fca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,8 +16,8 @@ jobs: os: [ubuntu-latest, windows-latest, macos-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v7 + - uses: actions/setup-node@v6 with: node-version: 22 - - run: npm test + - run: npm run check diff --git a/CHANGELOG.md b/CHANGELOG.md index bc5177c..a71cc6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,15 @@ # Changelog +**English** | [简体中文](CHANGELOG.zh-CN.md) + All notable changes are documented here. ## Unreleased +- Rewrite the README around user pain, executable proof, and ecosystem positioning. +- Add paired English/Simplified Chinese README, trust, demo, roadmap, and architecture documents. +- Add a repository-native sensitive-information gate to local checks and CI. +- Upgrade GitHub Actions runtimes to their current Node 24-based major versions. - Detect and quarantine abort events that are semantically closed but incompatible with Codex desktop projection. - Repair interrupted turns with Codex-native completion metadata while preserving an explicit non-fabricated tool-output placeholder. - Publish the current synchronization result in the same device report instead of reporting the previous run. diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md new file mode 100644 index 0000000..94e822f --- /dev/null +++ b/CHANGELOG.zh-CN.md @@ -0,0 +1,24 @@ +# 更新日志 + +[English](CHANGELOG.md) | **简体中文** + +所有重要变更都记录在此。 + +## 未发布 + +- 围绕用户痛点、可执行证据与生态定位重写 README。 +- 添加成对的英文/简体中文 README、信任、演示、路线图和架构文档。 +- 将仓库原生敏感信息 gate 加入本地检查与 CI。 +- 将 GitHub Actions runtime 升级到当前基于 Node 24 的主版本。 +- 检测并隔离语义上已关闭、但不兼容 Codex 桌面投影的 abort 事件。 +- 使用 Codex 原生完成元数据修复中断回合,同时保留明确且不虚构原始结果的工具输出占位符。 +- 在同一份设备报告中发布当前同步结果,不再落后一轮。 + +## 0.1.0 - 2026-07-12 + +- 初始私有仓库基线。 +- 使用每设备 head 与 canonical 提升机制同步已选择会话。 +- 用户 Skill 三方同步与冲突保留。 +- 文件夹与私有 Git transport。 +- Windows/macOS bootstrap、调度、维护、健康报告和恢复命令。 +- 跨平台端到端测试套件。 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 76f49ba..f242c5c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,11 +1,13 @@ # Contributing +**English** | [简体中文](CONTRIBUTING.zh-CN.md) + ## Development setup Install Node.js 22 or newer. The test suite has no third-party runtime dependencies. ```sh -npm test +npm run check ``` ## Change workflow @@ -13,7 +15,7 @@ npm test 1. Open an issue describing the user impact, reproduction, and safety boundary. 2. Create a focused branch from the default branch. 3. Add or update a deterministic test before changing synchronization behavior. -4. Run `npm test` on the platforms affected by the change. +4. Run `npm run check` on the platforms affected by the change. 5. Open a pull request and complete the privacy checklist. ## Safety invariants @@ -30,6 +32,8 @@ Changes must preserve these rules: Use synthetic IDs, paths, conversations, and device names in tests and documentation. Do not contribute real vault data, local reports, IP addresses, usernames, or screenshots containing private task content. +The repository-native privacy gate rejects forbidden state files, credential shapes, private home paths, device IDs, and non-example email addresses. Treat a finding as a release blocker, not as a warning to ignore. + ## Pull requests Keep changes scoped and describe root cause, user impact, validation, and rollback. A maintainer may request cross-platform evidence for filesystem, scheduler, or SQLite changes. diff --git a/CONTRIBUTING.zh-CN.md b/CONTRIBUTING.zh-CN.md new file mode 100644 index 0000000..d215001 --- /dev/null +++ b/CONTRIBUTING.zh-CN.md @@ -0,0 +1,39 @@ +# 贡献指南 + +[English](CONTRIBUTING.md) | **简体中文** + +## 开发环境 + +安装 Node.js 22 或更高版本。测试套件没有第三方运行时依赖。 + +```sh +npm run check +``` + +## 变更流程 + +1. 创建 Issue,描述用户影响、复现方式和安全边界。 +2. 从默认分支创建范围明确的分支。 +3. 修改同步行为前,先添加或更新确定性测试。 +4. 在受影响的平台运行 `npm run check`。 +5. 创建 Pull Request,并完成隐私检查清单。 + +## 安全约束 + +所有变更必须保留以下规则: + +- 不得复制身份验证信息或完整 Codex 数据库; +- 不得发布活动回合或语义不完整回合; +- 不得静默合并分叉的会话历史; +- 不得静默覆盖同时发生的 Skill 编辑; +- 不得自动传播删除; +- 不得强制重置或强制推送 Git transport; +- 所有修复操作都必须可从本地备份恢复。 + +测试和文档必须使用合成 ID、路径、会话与设备名。不要贡献真实 vault 数据、本地报告、IP 地址、用户名或包含私有任务内容的截图。 + +仓库原生隐私 gate 会拒绝禁止的状态文件、凭据形态、私人 home 路径、设备 ID 与非示例邮箱。任何命中都应视为 release blocker,而不是可忽略警告。 + +## Pull Request + +保持变更范围明确,并说明根因、用户影响、验证方式和回滚方法。对于文件系统、调度器或 SQLite 变更,维护者可能要求跨平台证据。 diff --git a/README.md b/README.md index b4234a0..082ee31 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,48 @@ # Codex Sync2 -Sync selected local Codex conversations and personal skills across Windows and macOS without copying credentials, entire databases, or unfinished conversation turns. +**English** | [简体中文](README.zh-CN.md) -Sync2 is for people who use Codex on more than one computer and want a controlled, inspectable alternative to copying the whole `~/.codex` directory. +Codex task history lives on one machine. **Codex Sync2 gives multi-device Codex users a recoverable synchronization layer for selected tasks and personal skills—without copying credentials, live databases, or unfinished turns.** + +Built for developers who move between Windows and macOS and need the same trusted Codex context on every device. ```text -Codex device A ─┐ - ├─ trusted private vault ─ stable conversation snapshots -Codex device B ─┤ └ user-defined skills -Codex device C ─┘ -``` +$ sync2 conversation select current +Selected: Example task -> Privacy first: the vault contains plaintext conversation text and skill source. Use trusted encrypted storage or a private Git repository. Sync2 deliberately excludes authentication, full databases, logs, caches, attachments, and system/plugin skills. +$ sync2 sync +conversationsPushed: 1 conversationConflicts: 0 + +$ sync2 doctor +ok: true +``` -## What it does +## Why Sync2 exists -- Synchronizes only explicitly selected Codex tasks. -- Publishes only complete JSONL records and stable, closed turns. -- Keeps independent device heads and promotes a canonical history only when heads have a byte-prefix relationship. -- Quarantines incomplete or divergent history instead of inventing a merge. -- Synchronizes user skill collections with three-way hashes and preserved conflict copies. -- Supports Syncthing/shared-folder and private-Git transports. -- Updates Codex's local thread indexes without copying another device's SQLite database. -- Provides maintenance mode, recovery backups, health reports, and schedulers for Windows and macOS. +Copying `~/.codex` is unsafe: it mixes credentials, device identity, live SQLite state, caches, and partially written turns. Generic file synchronization does not understand which Codex events form a complete conversation. -## Requirements +Sync2 adds that missing semantic layer: -- Node.js 22 or newer -- Codex initialized at least once on each device -- A trusted shared folder, or a checked-out **private** Git repository -- Syncthing only when using project-folder automation or Syncthing transport +- select only the tasks worth carrying between devices; +- publish only complete JSONL and stable, closed turns; +- keep independent device heads and promote a canonical history only when they are byte-prefix compatible; +- quarantine incomplete or divergent history instead of inventing a merge; +- synchronize personal skill collections with three-way hashes and preserved conflict copies; +- update local Codex indexes without copying another device's database. ## Quick start -Install the repository as a Codex skill: +Requirements: Node.js 22+, Codex initialized once on each device, and a trusted shared folder or checked-out private Git vault. + +Install as a Codex skill: ```sh -git clone codex-sync2 +git clone codex-sync2 mkdir -p "$HOME/.codex/skills/sync2" cp -R codex-sync2/. "$HOME/.codex/skills/sync2/" ``` -Initialize one device against an existing trusted vault: +Initialize one device: ```sh node "$HOME/.codex/skills/sync2/scripts/sync2.mjs" init \ @@ -50,41 +51,35 @@ node "$HOME/.codex/skills/sync2/scripts/sync2.mjs" init \ --device mac-main ``` -In a Codex task, invoke: - -```text -$sync2 current -``` - -Only selected tasks are exported. Run `$sync2 doctor` before enabling automatic scheduling. - -Windows and macOS bootstrap commands are documented in [references/deployment.md](references/deployment.md). +Then invoke `$sync2 current` inside the Codex task you want to preserve. Run `$sync2 doctor` before enabling automatic scheduling. -## Safety model +Windows and macOS cold-start commands are in the [deployment guide](references/deployment.md). -Sync2 treats rollout JSONL as append-only. A snapshot is publishable only when: +> **Security note:** the operational vault stores selected conversation text and user skill source in plaintext. Sync2 never copies credentials or complete Codex databases. Use only storage and peers you trust; see [Security and privacy](SECURITY.md). -1. every line is valid JSON; -2. every tool/function call has a matching output; -3. every older turn is completed or natively interrupted; -4. the active turn is excluded; -5. the result is a byte-prefix-compatible device head. +## How it works -If any rule fails, Sync2 preserves the data and reports the problem. It does not silently choose, truncate, or fabricate history. - -See [references/protocol.md](references/protocol.md) for reconciliation and recovery details. +```mermaid +flowchart LR + A["Device A stable head"] --> V["Syntax + semantic + projection validation"] + B["Device B stable head"] --> V + C["Device C stable head"] --> V + V -->|"all heads share a byte-prefix history"| K["Canonical conversation"] + V -->|"divergence or incomplete event"| Q["Preserved quarantine / conflict"] + K --> I["Import JSONL + update local indexes"] +``` -## Verification +Syncthing or private Git transports bytes. Sync2 decides which bytes are a safe Codex conversation. Read the [architecture](docs/architecture.md) and [protocol](references/protocol.md) for the invariants. -Run the deterministic end-to-end suite: +## Evidence that it works ```sh -npm test +npm run check ``` -The suite covers folder and Git transports, conversation import, interrupted-event repair, stable active checkpoints, conflict preservation, maintenance mode, Windows extended paths, desktop catalog updates, device reports, and skill reconciliation. +The deterministic suite runs 65 checks across folder and Git transports, interrupted-event recovery, legacy abort repair, stable active checkpoints, conflict preservation, Windows extended paths, desktop catalog updates, maintenance mode, device reports, and skill reconciliation. -CI runs the suite on Windows, macOS, and Linux with Node.js 22. +GitHub Actions runs the same privacy gate and tests on Windows, macOS, and Linux with Node.js 22. The fixtures use isolated temporary Codex homes and synthetic conversations; they never read the user's real vault. ## Data boundaries @@ -97,7 +92,7 @@ Included: Excluded: -- `auth.json`, tokens, API keys, installation/device credentials; +- `auth.json`, tokens, API keys, installation and device credentials; - `config.toml`, complete SQLite/WAL databases, logs, caches, and models; - attachments and generated images; - system and plugin-managed skill caches; @@ -106,21 +101,26 @@ Excluded: ## Limitations - Conversation attachments are not copied. -- Continuing the same task independently on disconnected devices creates an explicit conflict that requires a human choice. -- Vault contents are not encrypted by Sync2 itself. +- Independently continuing the same task while devices are disconnected creates an explicit conflict requiring a human choice. +- Sync2 does not encrypt the vault itself. - Older Sync2 clients do not understand maintenance mode; disable their schedulers before a fleet upgrade. -- Codex storage formats may evolve, so run `doctor` and the test suite after Codex upgrades. +- Codex storage formats may evolve; run `doctor` and the test suite after Codex upgrades. ## Documentation +- [Demonstration](docs/demo.md) +- [Architecture](docs/architecture.md) - [Usage guide](references/usage.md) - [Deployment and cold start](references/deployment.md) - [Protocol and recovery](references/protocol.md) -- [Demonstration](docs/demo.md) - [Roadmap](docs/roadmap.md) - [Security policy](SECURITY.md) - [Contributing](CONTRIBUTING.md) +## Project status + +Sync2 is an early, tested engineering project. The current release is intended for users comfortable inspecting local files, backups, and synchronization health. Safety and recoverability take priority over automatic conflict resolution. + ## License MIT. See [LICENSE](LICENSE). diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 0000000..442d232 --- /dev/null +++ b/README.zh-CN.md @@ -0,0 +1,126 @@ +# Codex Sync2 + +[English](README.md) | **简体中文** + +Codex 的任务历史通常只保存在当前电脑。**Codex Sync2 为跨 Windows 与 macOS 使用 Codex 的开发者提供可恢复的选定任务与个人 Skill 同步层,同时避免复制凭据、运行中的数据库或尚未完成的回合。** + +它适合需要在多台设备上继续使用同一份可信 Codex 上下文的开发者。 + +```text +$ sync2 conversation select current +Selected: Example task + +$ sync2 sync +conversationsPushed: 1 conversationConflicts: 0 + +$ sync2 doctor +ok: true +``` + +## 为什么需要 Sync2 + +直接复制 `~/.codex` 并不安全:其中混合了凭据、设备身份、正在写入的 SQLite 状态、缓存和未完成回合。普通文件同步工具也不知道哪些 Codex 事件才能构成完整会话。 + +Sync2 补上了这一层语义安全能力: + +- 只选择真正需要跨设备保留的任务; +- 只发布完整 JSONL 和稳定、已关闭的回合; +- 为每台设备保留独立 head,仅在历史满足字节前缀关系时提升 canonical; +- 隔离不完整或分叉历史,而不是虚构合并结果; +- 通过三方哈希同步个人 Skill,并保留冲突副本; +- 更新本地 Codex 索引,而不是复制其他设备的数据库。 + +## 快速开始 + +要求:Node.js 22+、每台设备至少启动过一次 Codex,以及一个可信共享文件夹或已检出的私有 Git vault。 + +安装为 Codex Skill: + +```sh +git clone codex-sync2 +mkdir -p "$HOME/.codex/skills/sync2" +cp -R codex-sync2/. "$HOME/.codex/skills/sync2/" +``` + +初始化一台设备: + +```sh +node "$HOME/.codex/skills/sync2/scripts/sync2.mjs" init \ + --vault "$HOME/Sync2Vault" \ + --transport folder \ + --device mac-main +``` + +随后在需要保留的 Codex 任务中调用 `$sync2 current`。启用自动调度前先运行 `$sync2 doctor`。 + +Windows 与 macOS 的冷启动命令见[部署指南](references/deployment.md)。 + +> **安全提示:**实际运行的 vault 会以明文保存已选择的会话文本与用户 Skill 源码。Sync2 不会复制凭据或完整 Codex 数据库。请只使用你信任的存储与对等设备;详见[安全与隐私](SECURITY.zh-CN.md)。 + +## 工作原理 + +```mermaid +flowchart LR + A["设备 A 的稳定 head"] --> V["语法、语义与桌面投影校验"] + B["设备 B 的稳定 head"] --> V + C["设备 C 的稳定 head"] --> V + V -->|"所有 head 共享字节前缀历史"| K["Canonical 会话"] + V -->|"历史分叉或事件不完整"| Q["保留隔离或冲突"] + K --> I["导入 JSONL 并更新本地索引"] +``` + +Syncthing 或私有 Git 负责传输字节;Sync2 负责判断哪些字节构成安全的 Codex 会话。关键约束见[架构说明](docs/architecture.zh-CN.md)与[协议文档](references/protocol.md)。 + +## 可验证的工程证据 + +```sh +npm run check +``` + +确定性测试套件包含 65 项检查,覆盖文件夹与 Git 传输、中断事件恢复、旧式 abort 修复、活动回合稳定检查点、冲突保留、Windows 扩展路径、桌面目录更新、维护模式、设备报告和 Skill 同步。 + +GitHub Actions 在 Windows、macOS 和 Linux 的 Node.js 22 环境中运行同一套隐私 gate 与测试。测试使用隔离的临时 Codex home 和合成会话,不读取用户的真实 vault。 + +## 数据边界 + +包含: + +- 明确选择的 rollout JSONL; +- 可移植任务元数据; +- 用户自定义 Skill 文件; +- 设备选择事件与健康报告。 + +排除: + +- `auth.json`、token、API key、安装和设备凭据; +- `config.toml`、完整 SQLite/WAL 数据库、日志、缓存和模型; +- 附件与生成图片; +- 系统和插件管理的 Skill 缓存; +- 未单独注册的任意项目内容。 + +## 限制 + +- 不复制会话附件。 +- 设备离线期间若在多端独立继续同一任务,会产生需要人工选择的显式冲突。 +- Sync2 本身不加密 vault。 +- 旧版 Sync2 不理解维护模式;升级整个设备集前必须禁用旧调度器。 +- Codex 存储格式可能变化;Codex 升级后应运行 `doctor` 与测试套件。 + +## 文档 + +- [演示](docs/demo.zh-CN.md) +- [架构](docs/architecture.zh-CN.md) +- [使用指南(英文)](references/usage.md) +- [部署与冷启动(英文)](references/deployment.md) +- [协议与恢复(英文)](references/protocol.md) +- [路线图](docs/roadmap.zh-CN.md) +- [安全策略](SECURITY.zh-CN.md) +- [贡献指南](CONTRIBUTING.zh-CN.md) + +## 项目状态 + +Sync2 是一个处于早期阶段、但已有完整测试的工程项目。目前版本面向愿意检查本地文件、备份与同步健康状态的用户。安全与可恢复性优先于自动冲突合并。 + +## 许可证 + +MIT,详见 [LICENSE](LICENSE)。 diff --git a/SECURITY.md b/SECURITY.md index e08b0fe..ffe43f7 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,11 +1,15 @@ # Security and privacy +**English** | [简体中文](SECURITY.zh-CN.md) + ## Reporting a vulnerability Use GitHub's private security-advisory flow for vulnerabilities or suspected data exposure. Do not open a public issue containing conversation text, local paths, device identifiers, credentials, or vault contents. Include the affected Sync2 version, platform, reproduction steps using synthetic data, and the expected security boundary. Remove secrets before attaching logs. +Before every push or release, run `npm run privacy:scan`. If a credential has already reached Git history or a remote, revoke or rotate it; deleting it in a later commit is not sufficient. + ## Data model Sync2 is a local-first synchronization tool. It reads selected Codex rollout files and user-configured skill roots, then writes sanitized protocol state into a user-controlled vault. diff --git a/SECURITY.zh-CN.md b/SECURITY.zh-CN.md new file mode 100644 index 0000000..211b938 --- /dev/null +++ b/SECURITY.zh-CN.md @@ -0,0 +1,38 @@ +# 安全与隐私 + +[English](SECURITY.md) | **简体中文** + +## 报告漏洞 + +对于漏洞或疑似数据暴露,请使用 GitHub 私有安全公告流程。不要在公开 Issue 中包含会话文本、本地路径、设备标识、凭据或 vault 内容。 + +请提供受影响的 Sync2 版本、平台、使用合成数据的复现步骤,以及预期的安全边界。附加日志前先删除敏感信息。 + +每次 push 或 release 前运行 `npm run privacy:scan`。如果凭据已经进入 Git 历史或远端,必须撤销或轮换;仅在后续提交中删除并不足够。 + +## 数据模型 + +Sync2 是本地优先的同步工具。它读取已选择的 Codex rollout 文件与用户配置的 Skill 根目录,然后将经过协议约束的状态写入用户控制的 vault。 + +Vault 可能包含明文会话正文与源码。Sync2 不加密 vault。请使用加密磁盘、可信 Syncthing 对等设备或具备适当访问控制的私有 Git 仓库。 + +Sync2 不得复制: + +- Codex 身份验证信息或 API 凭据; +- 完整 Codex 数据库或 WAL 文件; +- 包含安装身份的本地配置; +- 日志、缓存、模型、附件或插件/系统 Skill 缓存; +- 私钥、环境文件或无关项目目录。 + +## 操作安全 + +- 修复前备份 rollout 与索引状态。 +- 协议迁移期间,在重新连接设备前禁用旧调度器。 +- 修复和设备集升级必须使用维护模式。 +- 不得强制重置或强制推送 Git vault。 +- 将意外的 `.sync-conflict-*` 文件视为传输事故。 +- `device report` 可能包含本地路径,在可信 vault 外分享前必须检查。 + +## 支持版本 + +安全修复应用于默认分支的最新版本。排查问题前应先升级旧快照。 diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..fcc288b --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,53 @@ +# Architecture + +**English** | [简体中文](architecture.zh-CN.md) + +## Responsibility split + +Sync2 deliberately separates transport from conversation correctness. + +```mermaid +flowchart TB + subgraph Device["Each device"] + R["Selected Codex rollout JSONL"] + S["User skill roots"] + A["Syntax + semantic + projection audit"] + R --> A + end + + subgraph Vault["Trusted private vault"] + H["Per-device immutable-style heads"] + C["Canonical conversation"] + M["Portable metadata + per-device reports"] + K["Skill collections + conflict copies"] + end + + A -->|"stable snapshot only"| H + H -->|"byte-prefix reconciliation"| C + C -->|"complete import only"| R + S <-->|"three-way hashes"| K + Device --> M +``` + +- **Syncthing/shared folders/private Git** move files between machines. +- **Sync2** validates event completeness, reconciles histories, and updates local Codex indexes. +- **Codex** remains the only writer of live task activity; Sync2 never exports the active turn. + +## Conversation invariants + +1. JSONL is treated as append-only. +2. Every tool or function call needs a matching output. +3. Every older turn needs a completion or projection-compatible interruption. +4. The active turn is excluded from publication. +5. Healthy heads must share a byte-prefix relationship. +6. Divergent histories are preserved for explicit human resolution. +7. Complete Codex databases, credentials, logs, and caches never enter the vault. + +## Failure containment + +- An incomplete conversation is quarantined while independent skills and projects continue synchronizing. +- Maintenance mode blocks ordinary writes during repair or fleet migration. +- Repairs append explicit recovery records and create local backups; they do not reconstruct unknown tool output. +- Device reports expose protocol version, script hash, local/vault health, scheduler state, and the result of the run that produced the report. + +For exact reconciliation behavior and recovery commands, read [the protocol guide](../references/protocol.md). diff --git a/docs/architecture.zh-CN.md b/docs/architecture.zh-CN.md new file mode 100644 index 0000000..80f6ea6 --- /dev/null +++ b/docs/architecture.zh-CN.md @@ -0,0 +1,53 @@ +# 架构 + +[English](architecture.md) | **简体中文** + +## 职责拆分 + +Sync2 明确区分“文件传输”和“会话正确性”。 + +```mermaid +flowchart TB + subgraph Device["每台设备"] + R["已选择的 Codex rollout JSONL"] + S["用户 Skill 根目录"] + A["语法、语义与桌面投影审计"] + R --> A + end + + subgraph Vault["可信私有 vault"] + H["每台设备的独立 head"] + C["Canonical 会话"] + M["可移植元数据与设备报告"] + K["Skill 集合与冲突副本"] + end + + A -->|"仅稳定快照"| H + H -->|"字节前缀协调"| C + C -->|"仅完整导入"| R + S <-->|"三方哈希"| K + Device --> M +``` + +- **Syncthing、共享文件夹或私有 Git** 负责在机器间移动文件。 +- **Sync2** 负责校验事件完整性、协调历史并更新本地 Codex 索引。 +- **Codex** 仍是实时任务活动的唯一写入方;Sync2 永远不导出活动回合。 + +## 会话约束 + +1. 将 JSONL 视为只追加历史。 +2. 每个工具或函数调用都必须有匹配输出。 +3. 每个较早回合都必须完成或拥有桌面投影兼容的中断事件。 +4. 发布时排除活动回合。 +5. 健康 head 必须共享字节前缀关系。 +6. 分叉历史必须保留,等待人工显式选择。 +7. 完整 Codex 数据库、凭据、日志和缓存不得进入 vault。 + +## 故障隔离 + +- 不完整会话会被隔离,但独立的 Skill 和项目仍可继续同步。 +- 维护模式会在修复或设备集迁移期间阻止普通写入。 +- 修复操作追加显式恢复记录并创建本地备份,不会重构未知工具输出。 +- 设备报告公开协议版本、脚本哈希、本地与 vault 健康状态、调度器状态,以及生成该报告的同一轮运行结果。 + +精确的协调行为与恢复命令见[协议指南(英文)](../references/protocol.md)。 diff --git a/docs/demo.md b/docs/demo.md index 7a6bb39..ef0a222 100644 --- a/docs/demo.md +++ b/docs/demo.md @@ -1,5 +1,7 @@ # Demonstration +**English** | [简体中文](demo.zh-CN.md) + This demonstration uses synthetic paths and an empty test task. ```text @@ -16,4 +18,4 @@ ok: true On a second device, the first sync imports the stable canonical snapshot and updates that device's local Codex thread indexes. A second sync publishes the new device head. All healthy heads then have the same SHA-256 hash. -For a reproducible local simulation, run `npm test`; it creates isolated temporary Codex homes and vaults and never reads the user's real conversations. +For a reproducible local simulation, run `npm run check`; it first scans the repository for sensitive material, then creates isolated temporary Codex homes and vaults and never reads the user's real conversations. diff --git a/docs/demo.zh-CN.md b/docs/demo.zh-CN.md new file mode 100644 index 0000000..c8cfe78 --- /dev/null +++ b/docs/demo.zh-CN.md @@ -0,0 +1,21 @@ +# 演示 + +[English](demo.md) | **简体中文** + +此演示使用合成路径与空白测试任务。 + +```text +$ sync2 conversation select current +Selected: Example task + +$ sync2 sync +conversationsPushed: 1 +conversationConflicts: 0 + +$ sync2 doctor +ok: true +``` + +在第二台设备上,第一次同步会导入稳定 canonical 快照,并更新该设备的本地 Codex 任务索引。第二次同步发布新的设备 head。随后所有健康 head 都应拥有相同 SHA-256 哈希。 + +运行 `npm run check` 可以进行可复现的本地模拟:它先扫描仓库中的敏感内容,再创建隔离的临时 Codex home 与 vault,且不会读取用户真实会话。 diff --git a/docs/roadmap.md b/docs/roadmap.md index 320eef6..2223596 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,5 +1,7 @@ # Roadmap +**English** | [简体中文](roadmap.zh-CN.md) + ## Near term - Expand format-compatibility fixtures for future Codex rollout schemas. diff --git a/docs/roadmap.zh-CN.md b/docs/roadmap.zh-CN.md new file mode 100644 index 0000000..7bab811 --- /dev/null +++ b/docs/roadmap.zh-CN.md @@ -0,0 +1,18 @@ +# 路线图 + +[English](roadmap.md) | **简体中文** + +## 近期 + +- 为未来 Codex rollout schema 扩展格式兼容性 fixture。 +- 添加脱敏诊断包命令。 +- 为跨设备项目工作目录添加可选路径映射。 +- 改进干净 Windows 与 macOS 主机上的调度器验收测试。 + +## 后续 + +- 独立于 transport 加密的可选会话 payload 加密。 +- 基于设备报告生成只读设备集健康 dashboard。 +- 为其他本地 agent runtime 添加导入/导出 adapter。 + +路线图不构成承诺。安全与可恢复性优先于功能广度。 diff --git a/package.json b/package.json index e818f23..2cdde3d 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,9 @@ }, "scripts": { "test": "node --no-warnings scripts/test-sync2.mjs", - "check": "npm test" + "privacy:scan": "node scripts/check-repo-hygiene.mjs", + "docs:check": "node scripts/check-docs.mjs", + "check": "npm run privacy:scan && npm run docs:check && npm test" }, "license": "MIT" } diff --git a/scripts/check-docs.mjs b/scripts/check-docs.mjs new file mode 100644 index 0000000..65c8fce --- /dev/null +++ b/scripts/check-docs.mjs @@ -0,0 +1,71 @@ +#!/usr/bin/env node + +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const pairedDocs = [ + ["README.md", "README.zh-CN.md"], + ["SECURITY.md", "SECURITY.zh-CN.md"], + ["CONTRIBUTING.md", "CONTRIBUTING.zh-CN.md"], + ["CHANGELOG.md", "CHANGELOG.zh-CN.md"], + ["docs/demo.md", "docs/demo.zh-CN.md"], + ["docs/roadmap.md", "docs/roadmap.zh-CN.md"], + ["docs/architecture.md", "docs/architecture.zh-CN.md"], +]; + +function walk(directory, files = []) { + for (const entry of fs.readdirSync(directory, { withFileTypes: true })) { + if (entry.isDirectory() && [".git", "node_modules"].includes(entry.name)) continue; + const absolute = path.join(directory, entry.name); + if (entry.isDirectory()) walk(absolute, files); + else if (entry.isFile() && entry.name.endsWith(".md")) files.push(absolute); + } + return files; +} + +function headings(markdown) { + return markdown.split(/\r?\n/).filter((line) => /^#{1,6}\s+/.test(line)).map((line) => line.match(/^#+/)[0].length); +} + +function fenceCount(markdown) { + return markdown.split(/\r?\n/).filter((line) => /^```/.test(line)).length; +} + +const findings = []; +for (const file of walk(root)) { + const markdown = fs.readFileSync(file, "utf8"); + const rel = path.relative(root, file).split(path.sep).join("/"); + for (const match of markdown.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)) { + const target = match[1].trim().replace(/^<|>$/g, "").split("#")[0]; + if (!target || /^(?:https?:|mailto:)/i.test(target)) continue; + const resolved = path.resolve(path.dirname(file), decodeURIComponent(target)); + if (!fs.existsSync(resolved)) findings.push(`${rel}: broken local link ${match[1]}`); + } +} + +for (const [english, chinese] of pairedDocs) { + const enPath = path.join(root, english); + const zhPath = path.join(root, chinese); + if (!fs.existsSync(enPath) || !fs.existsSync(zhPath)) { + findings.push(`${english} / ${chinese}: missing language pair`); + continue; + } + const en = fs.readFileSync(enPath, "utf8"); + const zh = fs.readFileSync(zhPath, "utf8"); + const enLink = path.basename(chinese); + const zhLink = path.basename(english); + if (!en.includes(`](${enLink})`)) findings.push(`${english}: missing relative language switcher to ${enLink}`); + if (!zh.includes(`](${zhLink})`)) findings.push(`${chinese}: missing relative language switcher to ${zhLink}`); + if (JSON.stringify(headings(en)) !== JSON.stringify(headings(zh))) findings.push(`${english} / ${chinese}: heading-level structure differs`); + if (fenceCount(en) !== fenceCount(zh)) findings.push(`${english} / ${chinese}: fenced-code structure differs`); +} + +if (findings.length) { + console.error("Documentation check failed:"); + for (const finding of findings.sort()) console.error(`- ${finding}`); + process.exitCode = 1; +} else { + console.log(`Documentation check passed (${walk(root).length} Markdown files, ${pairedDocs.length} language pairs).`); +} diff --git a/scripts/check-repo-hygiene.mjs b/scripts/check-repo-hygiene.mjs new file mode 100644 index 0000000..373c650 --- /dev/null +++ b/scripts/check-repo-hygiene.mjs @@ -0,0 +1,73 @@ +#!/usr/bin/env node + +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const scannerRelativePath = "scripts/check-repo-hygiene.mjs"; +const ignoredDirectories = new Set([".git", "node_modules", "coverage", "dist", "tmp"]); +const textExtensions = new Set(["", ".md", ".mjs", ".js", ".json", ".yaml", ".yml", ".ps1", ".sh", ".txt"]); + +const forbiddenFileRules = [ + { name: "local state directory", test: (value) => value.split("/").some((part) => [".sync2", ".codex", "Sync2Vault", "recovery", "device-reports"].includes(part)) }, + { name: "credential/config file", test: (value) => /(^|\/)(auth\.json|config\.toml|\.env(?:\..*)?)$/i.test(value) && !value.endsWith("/.env.example") }, + { name: "database or conversation artifact", test: (value) => /\.(?:sqlite|sqlite3|db|jsonl)(?:-wal|-shm)?$/i.test(value) }, + { name: "private key material", test: (value) => /(^|\/)(?:id_rsa.*|id_ed25519.*|.*\.(?:pem|key))$/i.test(value) }, + { name: "transport conflict artifact", test: (value) => /\.sync-conflict-/i.test(value) }, +]; + +const contentRules = [ + { name: "private key block", pattern: /-----BEGIN (?:RSA |OPENSSH |EC )?PRIVATE KEY-----/g }, + { name: "GitHub token", pattern: /\b(?:gh[pousr]_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,})\b/g }, + { name: "OpenAI-style secret", pattern: /\bsk-[A-Za-z0-9_-]{20,}\b/g }, + { name: "Slack token", pattern: /\bxox[baprs]-[A-Za-z0-9-]{16,}\b/g }, + { name: "AWS access key", pattern: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g }, + { name: "literal secret assignment", pattern: /\b(?:api[_-]?key|secret|password|token|authorization)\s*[:=]\s*["'][^"'\r\n]{16,}["']/gi }, + { name: "private home path", pattern: /(?:[A-Za-z]:\\Users\\[^\\\s<>:"']+\\|\/Users\/[^/\s<>:"']+\/|\/home\/[^/\s<>:"']+\/)/g }, + { name: "Syncthing device ID", pattern: /\b[A-Z2-7]{7}(?:-[A-Z2-7]{7}){7}\b/g }, + { name: "private-network IP", pattern: /\b(?:10(?:\.\d{1,3}){3}|192\.168(?:\.\d{1,3}){2}|172\.(?:1[6-9]|2\d|3[01])(?:\.\d{1,3}){2})\b/g }, + { name: "non-example email", pattern: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi, allow: (value) => /@(example\.(?:com|org|net)|[^@]+\.invalid|users\.noreply\.github\.com)$/i.test(value) }, +]; + +function walk(directory, files = []) { + for (const entry of fs.readdirSync(directory, { withFileTypes: true })) { + if (entry.isDirectory() && ignoredDirectories.has(entry.name)) continue; + const absolute = path.join(directory, entry.name); + if (entry.isDirectory()) walk(absolute, files); + else if (entry.isFile()) files.push(absolute); + } + return files; +} + +function relative(file) { + return path.relative(root, file).split(path.sep).join("/"); +} + +const findings = []; +for (const file of walk(root)) { + const rel = relative(file); + for (const rule of forbiddenFileRules) { + if (rule.test(rel)) findings.push(`${rel}: forbidden ${rule.name}`); + } + + if (rel === scannerRelativePath || !textExtensions.has(path.extname(file).toLowerCase())) continue; + const content = fs.readFileSync(file, "utf8"); + if (content.includes("\0")) continue; + for (const rule of contentRules) { + rule.pattern.lastIndex = 0; + for (const match of content.matchAll(rule.pattern)) { + if (rule.allow?.(match[0])) continue; + const line = content.slice(0, match.index).split(/\r?\n/).length; + findings.push(`${rel}:${line}: ${rule.name}`); + } + } +} + +if (findings.length) { + console.error("Repository privacy gate failed:"); + for (const finding of [...new Set(findings)].sort()) console.error(`- ${finding}`); + process.exitCode = 1; +} else { + console.log(`Repository privacy gate passed (${walk(root).length} files scanned).`); +}