diff --git a/.gitignore b/.gitignore
index 536a5b3..5ef5f56 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,27 +2,28 @@
# mancode .gitignore
# ===================================
-# 内部开发文档(包含借鉴对象名,不公开)
-DEVELOPMENT.md
+# 内部法律文档(包含借鉴对象名,不公开)
LEGAL.md
*.backup
-# 设计文档(内部参考,不公开)
+# 历史设计文档默认不公开;当前文档白名单见下方
docs/
# Claude Code 开发配置(本地测试用)
.claude/
-# 技术实现文档(包含借鉴对象名和完整实现细节,不公开)
-docs/12-lifecycle.md
-docs/13-scanning.md
-docs/14-orchestration.md
-docs/15-adapters.md
-
-# 公开优化决策文档:PostToolUse P0 设计决策需要随代码提交
+# 当前维护文档
!docs/
docs/*
+!docs/README.md
+!docs/architecture.md
+!docs/workflows.md
+!docs/project-intelligence.md
+!docs/platform-adapters.md
+!docs/engineering.md
!docs/12-lifecycle.md
+!docs/release-acceptance.md
+!docs/planning-governance-evolution.md
# Node.js
node_modules/
@@ -94,6 +95,5 @@ output/
# Demo projects (local only, not published)
examples/
-# Local-only publishing and planning documents
+# Local-only publishing configuration
.npmrc
-CONTEXT_TEAM_REFACTOR_PLAN.md
diff --git a/README.en.md b/README.en.md
index c86082b..1f5390d 100644
--- a/README.en.md
+++ b/README.en.md
@@ -17,7 +17,7 @@
-
+
@@ -34,8 +34,9 @@ different gears for different stakes: light solo mode for daily practice, `/man`
for playoff-level engineering discipline, and coaching-staff subagents for
research, planning, implementation, and review.
-**mancode Continuity** is the continuous-context runtime that carries tasks,
-decisions, and verification evidence safely into later conversations.
+**mancode Continuity** is the cross-session and team-collaboration runtime that
+carries tasks, decisions, and verification evidence safely into later
+conversations.
[Installation](#installation) · [Usage](#usage)
@@ -107,10 +108,15 @@ one window's temporary state as another window's identity.
```bash
mancode status --brief --json
mancode context session new --client claude-code
+mancode context session show --session --client claude-code --json
mancode context resume --session --client claude-code
mancode context show --purpose orient --session --client claude-code
```
+When the project has a local mancode install, pin one CLI binary for the whole
+task: prefer `./node_modules/.bin/mancode`, otherwise use `mancode`. Check the
+selected binary with `--version` once and do not mix versions afterward.
+
The original `/man`, `/manba`, and `/manteam` entries handle these steps. The
CLI form above is useful for diagnostics, automation, or manual recovery.
@@ -234,7 +240,9 @@ design tokens:
```
-The default workflow asks six questions before writing code:
+The default workflow evaluates a conditional clarity gate before writing code: it asks the user
+only when an unresolved decision could change the goal, scope, behavior, acceptance, or key
+constraints, and it proceeds without ceremonial questions when the request is already clear.
1. What problem does this change solve?
2. Can an existing implementation be reused?
@@ -243,6 +251,10 @@ The default workflow asks six questions before writing code:
5. What is the smallest meaningful runtime check?
6. What remains uncertain after checking the code and docs?
+These are internal readiness checks, not a mandatory questionnaire. Any unresolved
+decision-changing uncertainty becomes a focused question and blocks requirements finalization
+until the user answers; clear requests continue directly.
+
## Usage
@@ -353,7 +365,7 @@ it should behave, and why previous decisions were made.
## Installation
-**Status**: mancode Continuity v0.3.18. Claude Code, Cursor, Codex in the ChatGPT
+**Status**: mancode Continuity v0.4.0. Claude Code, Cursor, Codex in the ChatGPT
desktop app and CLI, GitHub Copilot, and ZCode adapters are included.
Requires Node.js 20 or newer. macOS, Linux, Windows CMD, PowerShell, and Git Bash
@@ -394,8 +406,9 @@ mancode init --platform PLATFORMS # One or more: claude-code,cursor,codex,copilo
mancode init --empty # Allow a safe empty directory in non-interactive scripts
mancode init --lang zh-CN # Explicit initialization language (zh-CN or en)
mancode refresh-project # Refresh facts after Git or project files are added
-mancode install --force # Repair or reinstall the selected adapter
-mancode install --minimal # The bootstrap is already minimal; retained for compatibility
+mancode adapter status --json # Inspect actual managed-content digests
+mancode adapter upgrade --platform codex --dry-run # Stage a preview only
+mancode adapter upgrade --platform codex --confirm --operation-id --session --client
```
## Agent Modes
@@ -424,10 +437,16 @@ mancode init --legacy
mancode status
mancode status --json
mancode status --brief --json
-mancode install
+mancode install --confirm --operation-id --session --client
+mancode adapter status [--platform ] --json
+mancode adapter upgrade <--all|--platform > --dry-run
+mancode adapter upgrade <--all|--platform > --confirm --operation-id --session --client
+mancode project upgrade --policy 2 --dry-run
+mancode project upgrade --policy 2 --operation-id --session --client
mancode list-platforms
mancode team identity create --name ""
mancode context session new --client
+mancode context session show --session --client --json
mancode workflow create "" --session
mancode workflow list --json
mancode workflow show --json
@@ -438,6 +457,7 @@ mancode workflow plan confirm --plan-decision --status --expected-revision --session
mancode workflow review apply --file --expected-revision --session
mancode workflow verify apply --file --expected-revision --session
+mancode workflow reframe --expected-revision --checkpoint-id --session
mancode workflow complete --expected-revision --session
mancode manps [area]
mancode refresh-project
@@ -452,7 +472,7 @@ mancode version
Simplified output:
```text
-mancode v0.3.18
+mancode v0.4.0
Project: my-app
Runtime: ready
@@ -594,14 +614,19 @@ mancode/
- Irreversible operations such as force pushes, schema migrations, and bulk
deletes require explicit human confirmation.
-## Roadmap
+## Remaining Work
+
+- Complete real-host session acceptance for Claude Code, Codex, Cursor,
+ GitHub Copilot, and ZCode on one release candidate; verified host sessions
+ and isolated explicit sessions are both valid evidence paths.
+- Run `npm run release:check -- --candidate ` for the clean
+ checkout, automated two-clone/legacy checks, tarball SHA-256, and install
+ smoke; complete cross-host recovery separately.
+- Confirm ZCode project-skill discovery and workspace-command paths; keep the
+ adapter provisional until then.
+- Evaluate Windsurf, Cline, and Roo Code adapters based on real demand.
-| Phase | Focus |
-|---|---|
-| MVP-1 | solo mode, aesthetics, and Claude Code hooks |
-| MVP-2 | `/manba`, `/man`, `/manteam`, `/manps`, and coaching-staff subagents |
-| MVP-3 | Cursor, Codex (ChatGPT desktop/CLI), and GitHub Copilot adapters |
-| Public Release | stable npm release, marketplace distribution, docs, and demos |
+See [0.4.0 Continuity Release Acceptance](./docs/release-acceptance.md) for the complete gate.
## Troubleshooting
@@ -616,21 +641,26 @@ a deliberately empty directory.
### Claude Code hooks not triggering
After `mancode init`, restart Claude Code so it reloads `.claude/settings.json`.
-Run `mancode status` to verify hooks are registered. If hooks are still missing,
-run `mancode install claude-code --force` to rewrite the settings.
+Run `mancode status` to verify hooks are registered. For Continuity adapter drift, preview
+`mancode adapter upgrade --platform claude-code --dry-run`, then repair that
+preview with its `--operation-id`, an active session, and `--confirm`. The legacy
+hook architecture still uses `mancode init --legacy --force`.
### `mancode status` shows a platform as "not ready"
-This means the platform's target files are missing. Run
-`mancode install --force` to regenerate them. For managed-block
+This means the platform's target files are missing or their digest is stale.
+Preview `mancode adapter upgrade --platform --dry-run`, inspect the
+staged result, then run it with the returned `--operation-id`, an active session,
+and `--confirm`. For managed-block
platforms (Codex, ZCode, Copilot), the managed block in `AGENTS.md` or
`.github/copilot-instructions.md` may have been manually edited or deleted.
### AGENTS.md or copilot-instructions.md managed block was accidentally deleted
-Run `mancode install codex --force` (or `zcode`, or `copilot`) to reinsert the
-managed block. User-authored content outside the relevant mancode managed
-markers is preserved.
+Preview `mancode adapter upgrade --platform codex --dry-run` (or `zcode`, or
+`copilot`), then run it with the returned `--operation-id`, an active session,
+and `--confirm` to reinsert the managed block. User-authored content outside the
+relevant markers is preserved.
### ZCode skills not appearing
@@ -648,16 +678,8 @@ description field — invoke them by asking for `/manba` or similar.
### How to reinstall Continuity adapters
```bash
-mancode uninstall claude-code --force
-mancode uninstall cursor --force
-mancode uninstall codex --force
-mancode uninstall copilot --force
-mancode uninstall zcode --force
-mancode install claude-code
-mancode install cursor
-mancode install codex
-mancode install copilot
-mancode install zcode
+mancode adapter upgrade --all --dry-run
+mancode adapter upgrade --all --confirm --operation-id --session --client
```
Continuity authority is protected, so `mancode uninstall --all` does not delete workflow
@@ -684,8 +706,11 @@ use.
### How is mancode different from a CLAUDE.md file?
-A `CLAUDE.md` file is static guidance. mancode adds hooks, persisted workflow
-state, slash-command skills, and separate review subagents with clean context.
+A `CLAUDE.md` file is static guidance. Continuity maintains one always-loaded
+Claude Code bootstrap block there, while sessions, TaskRefs, requirements,
+plans, and review evidence remain structured authority under `.mancode/`.
+mancode also adds mode skills, persisted workflows, and separate review
+subagents with clean context.
### How is mancode different from Cursor rules, custom prompts, or agent instructions?
diff --git a/README.md b/README.md
index d818221..dc61761 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@
-
+
@@ -36,7 +36,7 @@
日常任务用轻量 `solo`,关键任务用季后赛级别的 `/man`,复杂任务让教练组 subagents
负责调研、计划、实现和审查。
-**mancode Continuity(连续上下文运行时)**负责把任务、决策和验证证据安全地带到后续对话。
+**mancode Continuity(跨会话与团队协作运行时)**负责把任务、决策和验证证据安全地带到后续对话,并协调多人或多 Agent 的任务权威。
mancode 当前支持 Claude Code、Cursor、ChatGPT 桌面端中的 Codex、Codex CLI、
GitHub Copilot 和 ZCode。每个平台继续使用原来的 `man*` 入口,并通过静态
@@ -118,6 +118,16 @@ mancode context session new --client claude-code
mancode list-platforms
```
+要只读检查一个 session(例如确认 Codex 续接时仍使用同一身份),使用:
+
+```bash
+mancode context session show --session --client --json
+```
+
+在项目同时安装 mancode 时,整项任务应固定使用项目本地 CLI:优先
+`./node_modules/.bin/mancode`,否则再使用 PATH 中的 `mancode`;先运行一次
+`--version`,后续不要混用不同版本。
+
普通 `mancode init` 会生成原来的 `man`、`manba`、`manteam`、`manps`、
`mansolo` 平台命令,不需要改用另一套命令名。确实需要旧 `state.json` 架构时才使用
`mancode init --legacy`。
@@ -228,7 +238,8 @@ mancode 不把“当前模式”写进持久状态。需要某种工作方式时
`/man` 既是正式计划入口,也是面向关键任务的季后赛模式。即使当前处于默认
`solo`,当用户要求先调研、给方案或出计划时,也会进入 `/man`。它会先了解项目,
-只追问会改变范围、架构、成本或验收的问题;适合由系统推荐的决策会给出 2–3 个
+只追问会改变范围、架构、成本或验收的问题;需求足够清晰时不制造形式问题,需求
+不清晰时会停下等待用户回答;适合由系统推荐的决策会给出 2–3 个
方案、优缺点和明确建议。需求足够清楚后,计划才会写入
`.mancode/local/workflows//plan.md`。
@@ -248,6 +259,10 @@ mancode 不把“当前模式”写进持久状态。需要某种工作方式时
跳过的步骤会被记录。所有产物保留在本地,之后可以回看当时为什么做某个决策。
+默认 `solo` 也执行同一个轻量清晰度判断:清晰、窄范围的需求直接做最小改动;会改变
+行为、范围、验收或关键约束的歧义必须先提问。涉及架构、owner/source of truth、迁移、
+跨模块或团队决策时,`solo` 推荐 `/man`,但不会自行切换模式。
+
## 工作原理
### Bootstrap 和 Adapters
@@ -307,7 +322,7 @@ src/components/
## 安装
-**状态**:mancode Continuity v0.3.18。Claude Code、Cursor、ChatGPT 桌面端中的
+**状态**:mancode Continuity v0.4.0。Claude Code、Cursor、ChatGPT 桌面端中的
Codex、Codex CLI、GitHub Copilot 和 ZCode adapter 均已接入。
需要 Node.js 20 或更高版本。原生支持 macOS、Linux、Windows CMD、
@@ -346,8 +361,9 @@ mancode init --platform PLATFORMS # 一个或多个:claude-code,cursor,codex,c
mancode init --empty # 非交互脚本中允许安全的空目录
mancode init --lang zh-CN # 显式指定初始化语言(zh-CN 或 en)
mancode refresh-project # 后续加入 Git 或项目文件后刷新项目事实
-mancode install --force # 修复或重装所选 adapter
-mancode install --minimal # bootstrap 已是最小形态;保留兼容参数
+mancode adapter status --json # 检查实际 managed content digest
+mancode adapter upgrade --platform codex --dry-run # 只生成 staging 预览
+mancode adapter upgrade --platform codex --confirm --operation-id --session --client
```
## Agent Modes
@@ -376,10 +392,16 @@ mancode init --legacy
mancode status
mancode status --json
mancode status --brief --json
-mancode install
+mancode install --confirm --operation-id --session --client
+mancode adapter status [--platform ] --json
+mancode adapter upgrade <--all|--platform > --dry-run
+mancode adapter upgrade <--all|--platform > --confirm --operation-id --session --client
+mancode project upgrade --policy 2 --dry-run
+mancode project upgrade --policy 2 --operation-id --session --client
mancode list-platforms
mancode team identity create --name ""
mancode context session new --client
+mancode context session show --session --client --json
mancode workflow create "" --session
mancode workflow list --json
mancode workflow show --json
@@ -390,6 +412,7 @@ mancode workflow plan confirm --plan-decision --status --expected-revision --session
mancode workflow review apply --file --expected-revision --session
mancode workflow verify apply --file --expected-revision --session
+mancode workflow reframe --expected-revision --checkpoint-id --session
mancode workflow complete --expected-revision --session
mancode manps [area]
mancode refresh-project
@@ -404,7 +427,7 @@ mancode version
以下是简化输出示例:
```text
-mancode v0.3.18
+mancode v0.4.0
Project: my-app
Runtime: ready
@@ -537,14 +560,14 @@ mancode/
- `/manps` 默认只扫描;进入整改前应明确确认代码改动。
- force push、schema migration、批量删除等不可逆操作需要明确人工确认。
-## 路线图
+## 仍在推进
+
+- 在同一发布候选上完成 Claude Code、Codex、Cursor、GitHub Copilot 和 ZCode 的真实宿主 session 验收;宿主自动 session 和显式双 session 都是合法证据路径。
+- 使用 `npm run release:check -- --candidate <完整提交 SHA>` 完成干净 checkout、自动双 clone/legacy、tarball SHA-256 和安装 smoke;另完成跨真实宿主恢复。
+- 确认 ZCode 项目级 skill 发现和 workspace command 路径;完成前继续标记为 provisional。
+- 根据真实需求评估 Windsurf、Cline 和 Roo Code adapter。
-| 阶段 | 重点 |
-|---|---|
-| MVP-1 | solo 模式、审美扫描、Claude Code hooks |
-| MVP-2 | `/manba`、`/man`、`/manteam`、`/manps` 和教练组 subagents |
-| MVP-3 | Cursor、Codex(ChatGPT 桌面端/CLI)、GitHub Copilot 适配 |
-| 公开发布 | npm 稳定版、marketplace 分发、文档和演示 |
+完整发布条件见 [0.4.0 Continuity 发布验收](./docs/release-acceptance.md)。
## 故障排查
@@ -557,18 +580,24 @@ mancode/
### Claude Code hooks 不生效
`mancode init` 后需要重启 Claude Code 以重新加载 `.claude/settings.json`。
-运行 `mancode status` 确认 hooks 已注册。如果仍不生效,运行
-`mancode install claude-code --force` 重写配置。
+运行 `mancode status` 确认 hooks 已注册。Continuity adapter 内容异常时,先运行
+`mancode adapter upgrade --platform claude-code --dry-run`,再用该预览返回的
+`--operation-id`、active session 和 `--confirm` 完成修复。legacy hook 架构仍使用
+`mancode init --legacy --force`。
### `mancode status` 显示某平台 "not ready"
-该平台的目标文件缺失。运行 `mancode install --force` 重新生成。
+该平台的目标文件缺失或 digest 不匹配。先运行
+`mancode adapter upgrade --platform --dry-run`,确认 staging 结果后再用
+该预览返回的 `--operation-id` 和 active session 执行 `--confirm`。
对于 Codex、ZCode 和 Copilot,`AGENTS.md` 或 `.github/copilot-instructions.md`
中的受控区可能被手动编辑或删除了。
### AGENTS.md 或 copilot-instructions.md 受控区被误删
-运行 `mancode install codex --force`(或 `zcode`、`copilot`)重新插入受控区。
+运行 `mancode adapter upgrade --platform codex --dry-run`(或 `zcode`、`copilot`)
+检查差异,再用该预览返回的 `--operation-id` 和 active session 执行 `--confirm`
+重新插入受控区。
对应 mancode 受控标记外的用户内容会被保留。
### ZCode skills 未出现
@@ -586,16 +615,8 @@ manps)按 description 触发——输入 `/manba` 等关键词即可激活。
### 如何重装 Continuity 适配器
```bash
-mancode uninstall claude-code --force
-mancode uninstall cursor --force
-mancode uninstall codex --force
-mancode uninstall copilot --force
-mancode uninstall zcode --force
-mancode install claude-code
-mancode install cursor
-mancode install codex
-mancode install copilot
-mancode install zcode
+mancode adapter upgrade --all --dry-run
+mancode adapter upgrade --all --confirm --operation-id --session --client
```
Continuity authority 受保护,`mancode uninstall --all` 不会删除工作流权威数据。需要
@@ -620,8 +641,9 @@ npm uninstall -g mancode
### mancode 和 CLAUDE.md 有什么区别?
-`CLAUDE.md` 是静态指导。mancode 增加 hooks、持久化工作流状态、slash-command skills
-和独立审查 subagents。
+`CLAUDE.md` 是静态指导。Continuity 只在其中维护一段始终加载的 Claude Code bootstrap,
+真正的 session、TaskRef、requirements、计划和审查证据仍由 `.mancode/` 的结构化权威管理;
+mancode 还提供 mode skills、持久化工作流和独立审查 subagents。
### mancode 和 Cursor rules、自定义 prompt、agent instructions 有什么区别?
diff --git a/docs/12-lifecycle.md b/docs/12-lifecycle.md
index 9dfde5c..0f3f5c1 100644
--- a/docs/12-lifecycle.md
+++ b/docs/12-lifecycle.md
@@ -1,237 +1,61 @@
-# 12 - Harness 生命周期
+# Continuity 生命周期
-mancode 的运行时生命周期:跨平台 hook、上下文注入、状态管理与团队检测。
+本文描述默认 Continuity 路径。旧 `state.json` 生命周期仅用于显式 `--legacy` 兼容。
----
-
-## 1. 生命周期概览
+## 初始化
```text
-用户执行 mancode init
- ↓
-Node CLI 检测项目 profile
- ↓
-Git 可用 → 读取团队活动
-Git 不可用 → 使用 solo 默认值,不阻止初始化
- ↓
-创建 .mancode/state.json 与平台适配文件
- ↓
-Claude Code 启动
- ↓
-node .mancode/hooks/session-start.mjs
- ↓ 注入模式与项目上下文
-用户提交 prompt
- ↓
-node .mancode/hooks/user-prompt-submit.mjs
- ↓ 注入 solo 六问、自动路由与条件审美 token
-Claude 处理请求
+mancode init
+ → 检查项目与 legacy/Continuity 物理布局
+ → 检测 Project Profile
+ → 创建 schema.json,状态 initializing
+ → 写 shared/local/runtime 基础权威
+ → 安装所选平台 bootstrap
+ → 校验 adapter 与 runtime binding
+ → 最后切换为 v3_active
```
-当前两个 runtime hook 都是 Node `.mjs` 脚本。它们不依赖 Bash、jq、grep、
-sed 或 Git,因此可以从 macOS、Linux、Windows CMD、PowerShell 与 Git Bash
-运行。
-
----
-
-## 2. Hook 详细说明
-
-### 2.1 SessionStart Hook
-
-**位置**:`.mancode/hooks/session-start.mjs`
-
-**触发时机**:Claude Code 启动会话并加载项目。
-
-**职责**:
-
-1. 从 hook 文件位置计算项目根目录,不依赖当前 shell 或 Git。
-2. 读取 `.mancode/state.json` 与 `.mancode/project-profile.json`。
-3. 注入当前模式、技术栈、UI 能力和最小改动原则。
-4. 已检测到团队且仍处于 solo 时,提示按需使用 `/manteam`。
-5. 状态不存在或 JSON 无法读取时安全退出,不抛出未处理异常。
-
-动态值会移除换行并限制为 200 个字符,避免项目状态污染 prompt 结构。
-
-### 2.2 UserPromptSubmit Hook
-
-**位置**:`.mancode/hooks/user-prompt-submit.mjs`
-
-**触发时机**:用户提交 prompt 后、Claude 开始处理前。
-
-**职责**:
-
-1. 在 solo 模式下注入六个动手前问题。
-2. 从 stdin 读取 Claude Code 提供的 JSON,并提取原始 prompt。
-3. 识别规划或调研请求,提示路由到 `man` skill。
-4. 只有项目 profile 确认存在 UI 资产且 prompt 涉及 UI 时,才注入审美摘要。
-5. 对颜色、字体、组件和 CSS variable 设置数量与字符上限。
-
-Node 直接解析 JSON。不存在 jq fallback,也不会调用任何外部进程。
-
-### 2.3 PostToolUse Hook(计划中,尚未实现)
-
-未来如果实现 PostToolUse,必须继续满足同一跨平台约束:
+已有 legacy authority 时,普通初始化会拒绝覆盖。使用 `mancode migrate context --dry-run` 检查,再通过 stage、resolve 和显式 activation 迁移。
-- 使用 Node 脚本,不调用 Bash 或 POSIX 管道。
-- 默认 opt-in,只处理本次修改的项目内文本文件。
-- 不格式化整仓,不修改未触达文件。
-- 子进程使用 `execFile` / `spawn` 参数数组,不拼接 shell 字符串。
-- 单次执行设置超时;超时提示但不无限阻塞编辑流程。
-- lint/typecheck 的失败必须保留真实退出状态。
+## 会话与任务
----
-
-## 3. 团队检测
-
-团队检测发生在 CLI 初始化和状态查询阶段,不在 hook 内执行。
-
-判定为团队需要同时满足:
-
-1. Git 历史贡献者超过 1 人。
-2. 存在 GitHub、GitLab 或 Bitbucket remote。
-3. 最近 30 天活跃贡献者超过 1 人。
-
-实现通过 Node `execFile('git', args)` 直接调用 Git,并在 Node 中拆分、去重邮箱;
-不使用 `/bin/bash`、`sort`、`grep`、重定向或管道。以下情况统一安全降级:
-
-- Git 未安装或不在 PATH。
-- 项目来自 ZIP,没有 `.git`。
-- Git 历史为空或命令执行失败。
-- 当前目录不是独立仓库根目录。
-
-降级结果为 `isTeam: false`、`contributors: 1`、`recentActive: 1`,不会阻止
-`mancode init`。用户仍可通过 `--team` 或 `--no-team` 明确覆盖。
-
----
-
-## 4. `.mancode/state.json`
-
-```json
-{
- "version": "0.3.14",
- "currentMode": "solo",
- "lastMode": "solo",
- "platform": "claude-code",
- "initializedAt": "2026-07-11T10:30:00.000Z",
- "techStack": "JavaScript/TypeScript + React",
- "uiLibrary": "Tailwind CSS",
- "currentTask": null,
- "currentWorkflowMode": null,
- "skippedSteps": [],
- "activeSoloPlan": null,
- "teamModeAutoDetected": false,
- "contributors": 1
-}
-```
-
-| 字段 | 说明 |
-|---|---|
-| `currentMode` / `lastMode` | 当前和上一次工作流模式 |
-| `platform` | 初始化时选择的默认适配器 |
-| `techStack` / `uiLibrary` | 从 project profile 得到的摘要 |
-| `currentTask` / `currentWorkflowMode` | 当前持久化工作流信息 |
-| `skippedSteps` | 用户明确跳过的工作流步骤 |
-| `activeSoloPlan` | `/man` 计划交给 solo 后保留的 taskId 与 planVersion;solo 验证完成后由 `workflow handoff --complete` 原子清理 |
-| `teamModeAutoDetected` | 自动检测或显式配置后的团队状态 |
-| `contributors` | 检测到的贡献者数量;降级时为 1 |
-
-CLI 负责写入 state;hooks 只读,不在会话启动或 prompt 提交时修改磁盘状态。
-
----
-
-## 5. Claude Code 集成
-
-### 5.1 settings.json
-
-```json
-{
- "hooks": {
- "SessionStart": [
- {
- "hooks": [
- {
- "type": "command",
- "command": "node \".mancode/hooks/session-start.mjs\""
- }
- ]
- }
- ],
- "UserPromptSubmit": [
- {
- "hooks": [
- {
- "type": "command",
- "command": "node \".mancode/hooks/user-prompt-submit.mjs\""
- }
- ]
- }
- ]
- }
-}
+```text
+宿主或用户创建 session
+ → workflow create 生成 TaskRef 与四个治理实体
+ → context resume 绑定当前 session
+ → context show 解析一致的 Context Pack
+ → mutation 使用 expected revision 执行
```
-命令只使用 `node` 和带引号的相对路径,在 CMD、PowerShell、Git Bash 与 POSIX
-shell 中语义一致。
-
-### 5.2 旧版本迁移
-
-`mancode init --force` 或 `mancode install claude-code --force` 会:
-
-1. 识别并移除 mancode 生成的旧命令:
- `bash .mancode/hooks/session-start.sh` 与
- `bash .mancode/hooks/user-prompt-submit.sh`。
-2. 注册新的 Node hook 命令。
-3. 删除两个旧 `.sh` 文件并生成 `.mjs` 文件。
-4. 保留无法精确识别为 mancode 生成内容的用户 hook。
-
-卸载同样同时识别新旧 mancode hook,不会删除无关用户命令。
+session 是本地便利状态。关闭一个 session 不会关闭任务,也不会影响其他 session。宿主 identity key 只保存不可逆 lookup hash;显式 session 不保存原始宿主 key。
----
+## 写入与恢复
-## 6. `/man` 受管验证门禁
+简单单实体 cache 写入可以原子替换。影响任务语义、claim、handoff、迁移或 transport 的写入必须经过 operation journal。
-新创建的 `/man` 与 `/manteam` workflow 使用 policy v2:
+发现 `operation_pending`、reservation、task-head fence 漂移或未完成 git-ref receipt 时:
-1. Step 2 把目标、非空范围、排除项、技术决策、默认值、阻塞未知项、七个 coverage 维度和带稳定 ID 的验收标准写入结构化输入。
-2. `workflow requirements finalize --file ` 校验输入,并确定性生成 `requirements.json` 和 `requirements.md`;readiness 由 `blockingUnknowns` 是否为空推导。
-3. Step 6 初始化 `verification-ledger.json`。每个 required 验收项按 `automated`、`manual` 或 `hybrid` 记录证据;自动 passed/failed 包含命令和退出码。
-4. `manual_required` 会把主 workflow 标为 blocked;普通 `workflow update --status in_progress` 不能绕过,必须在取得用户明确确认后通过 `verify ... confirm-manual` 恢复。
-5. 台账绑定 requirements digest、planVersion 与 remediation round。任一发生变化,旧验证自动失效;remediation 后在 Step 9 重新登记全部 required 验收。
-6. 验证未全部通过时,CLI 同时拒绝 Step 7、review 初始化和 completed。完成还要求 `summary.md`。review 只能在 Step 6 通过专用 skip 命令和理由跳过。
+- read-only 命令返回可证明的一致子集和 repair 信息。
+- 普通 mutation 被拒绝。
+- `context doctor`、`operation repair` 或 transport recovery 继续原 operation。
+- 只有尚未产生可见业务写时才允许 abort。
-review policy v2 只允许 `clarification` 和用户明确要求跳过的整个 `review` 出现在 `skippedSteps`。targeted review 的第二领域是不适用,不写入 skippedSteps。旧 policy v1 workflow 保持兼容读取。
+## 完成与保留
----
+完成门禁读取整个 Task Aggregate。requirements、plan、review、verification、子任务、claim 与 repair 状态必须同时满足策略。
-## 7. 验证矩阵
+`mancode context compact` 先列出候选,再删除符合 retention policy 的本地 cache、终态 workflow 产物或 repair journal。active task、milestone、被引用 checkpoint 和未完成 operation 不会被静默删除;shared 删除需要显式确认。
-本地测试覆盖:
+## 团队与跨 clone
-- Git 存在与 PATH 中没有 Git。
-- 非 Git manifest 项目正常初始化。
-- 团队检测的单人、多人、remote 与近期活跃组合。
-- Node hooks 的上下文输出、自动路由、token cap 与不可信 token 清洗。
-- 旧 Bash hook 设置迁移、新 hook 状态检查与卸载。
+本地 transport 在同一 Git common directory 内共享协调权威。git-ref transport 只在显式 pull/push 后更新,并通过 remote revision 与 ownership fence 做 CAS。
-Windows CI 会在同一个 `windows-latest` runner 上分别从 CMD、PowerShell 和 Git
-Bash 执行 smoke test。测试清空子进程 PATH 来模拟 Git/Bash 不可用,验证 Codex
-初始化、solo 降级、Claude Code 初始化和两个 Node hooks 均能完成。它还会让
-PowerShell 以不允许删除的共享模式实际打开 session evidence 文件,再运行一次
-`context session spike`;原子替换必须在句柄释放后自行重试并成功。该 job 名为
-`Windows compatibility gate`;其 `windows-shells` check 已配置为 `main` 的发布必需检查。
+业务 Git 内容、分支和 worktree 不由 mancode 自动同步。跨 clone handoff 的接收者必须同时取得对应代码基线。
-发布候选还必须在目标项目执行内部发布证据命令。该门禁会拒绝
-未激活或不兼容的 mancode 工作流、未安装的平台 bootstrap、未完成的 repair journal、
-缺失 checkout binding,以及没有绑定当前发布候选的五个平台真实双窗口、子命令传播和子 agent
-继承证据的项目。session evidence 文件不保存宿主 session key;`context session spike` 不会从临时
-环境变量自动推断传播结果,只有在每个平台宿主中完成真实 spike 后才能通过。
+## 发布证据
----
+`context session spike` 记录真实宿主的窗口隔离、子命令传播、子 agent 继承和 hook approval,但不保存原始宿主 session key 或显式 session ID。证据模式为 `host` 或 `explicit`:前者通过后可授权受信宿主身份,后者只证明两个已存在、active、client 匹配的显式 session 隔离,不改变运行时的 `explicit_required` 策略。内部 `context beta` 接受与平台能力匹配的任一路径,并要求所有证据绑定同一个 immutable release candidate。
-## 8. 实施状态
+`npm run release:check -- --candidate <完整提交 SHA>` 从 `origin/develop` 创建干净 checkout,运行完整自动化、真实双 clone/legacy fixture、audit、pack 与 tarball 安装 smoke,并把报告和候选 tarball 保存在 `.mancode/local/release-evidence/`。该命令不会执行 `npm publish` 或修改 dist-tag。
-| 阶段 | 内容 |
-|---|---|
-| MVP-1 | SessionStart、UserPromptSubmit、state.json |
-| MVP-2 | 多模式切换;PostToolUse 仍为计划项 |
-| MVP-3 | Claude Code、Cursor、Codex、Copilot、ZCode 适配 |
-| Windows native | Node hooks、无 shell 团队检测、Git 可选、三 shell smoke |
+尚未完成的验收见 [release-acceptance.md](./release-acceptance.md)。
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..c394bae
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,35 @@
+# mancode 开发文档
+
+这里记录当前实现的稳定契约。历史 MVP 计划、一次性审核报告和已完成的网站计划不再保留在仓库中。
+
+## 信息来源
+
+发生冲突时,按以下优先级判断:
+
+1. `src/` 中的 schema、CLI 注册和运行时代码。
+2. 对应的自动化测试。
+3. `README.md` 与 `README.en.md` 的公开使用说明。
+4. 本目录中的开发说明。
+
+文档不能把计划中的功能写成已支持,也不能用旧测试数量或版本号证明当前状态。
+
+## 文档索引
+
+| 文档 | 内容 |
+|---|---|
+| [architecture.md](./architecture.md) | Continuity 权威模型、目录、Task Aggregate 和一致性 |
+| [workflows.md](./workflows.md) | 模式、工作流状态、治理门禁与团队协作 |
+| [project-intelligence.md](./project-intelligence.md) | 项目检测、设计资产扫描和 preseason |
+| [platform-adapters.md](./platform-adapters.md) | 五个平台的 bootstrap、能力差异与边界 |
+| [12-lifecycle.md](./12-lifecycle.md) | 初始化、会话、任务、恢复和迁移生命周期 |
+| [engineering.md](./engineering.md) | 开发原则、验证要求和代码地图 |
+| [release-acceptance.md](./release-acceptance.md) | 尚未完成的真实宿主与发布验收 |
+
+法律和许可证边界单独保存在仓库根目录的 `LEGAL.md`。
+
+## 维护规则
+
+- 新文档优先更新现有专题,不创建新的阶段计划副本。
+- 已完成计划应删除;仍有长期价值的决策应写成当前约束。
+- 仅在真实宿主验证完成后更新平台能力声明。
+- 命令示例必须能在当前 `src/cli.ts` 中找到对应入口。
diff --git a/docs/architecture.md b/docs/architecture.md
new file mode 100644
index 0000000..0a9e8ff
--- /dev/null
+++ b/docs/architecture.md
@@ -0,0 +1,74 @@
+# Continuity 架构
+
+mancode Continuity(跨会话与团队协作运行时)把跨会话任务状态、治理证据和团队协调放在显式、可校验的本地权威中。平台适配器只负责入口与 bootstrap,不保存任务副本。内部 layout/schema version 仍为 3;它是存储兼容版本,不是产品名称。
+
+## 核心模型
+
+一个任务由 `TaskRef` 标识:
+
+```text
+local:
+shared:
+```
+
+`local` 任务只属于当前 checkout;`shared` 任务可以参与团队协调。可见性与协作方式是两个维度:`visibility=local|shared`,`coordination=single|team`。
+
+任务的稳定视图是 Task Aggregate,由以下实体共同组成:
+
+- `metadata.json`:生命周期、owner、revision、scope 和治理摘要。
+- `requirements.json`:目标、范围、未知项和验收标准。
+- `review-ledger.json`:审查领域、报告与 blocker。
+- `verification-ledger.json`:自动或人工验证证据。
+- checkpoint、claim、handoff 和 task-head fence:团队协调与恢复状态。
+
+Markdown 计划和报告是人类可读产物。完成门禁以结构化实体及其 digest 为准。
+
+## 目录与权威
+
+```text
+.mancode/
+├── schema.json # Continuity 激活状态和兼容门禁
+├── shared/
+│ ├── config.json # 项目策略与 transport 配置
+│ ├── context/project.json # 可共享项目事实
+│ ├── workflows/ # shared Task Aggregate
+│ ├── team/ # actor、claim、handoff、checkpoint
+│ └── memory/decisions/ # 明确确认的共享决策
+├── local/
+│ ├── sessions/ # checkout-local 会话
+│ ├── workflows/ # local Task Aggregate
+│ ├── cache/ # 可重建扫描与 transport 缓存
+│ └── preseason-* # 本地健康扫描产物
+└── runtime/ # operation journal、reservation、repair
+```
+
+旧架构的 `state.json`、`config.json`、`project-profile.json`、`workflows/` 和 `memory/` 与 Continuity 目录物理隔离。普通 `mancode init` 创建 Continuity 布局;只有显式 `--legacy` 才创建旧布局。
+
+## 一致性与恢复
+
+所有跨实体业务写入都使用 durable operation:
+
+1. 写入带预期 revision 的 operation journal。
+2. 获取本地锁并校验 session、Task Aggregate、checkout binding 和 coordination freshness。
+3. 为受影响实体写 reservation 或 `operation_pending` 状态。
+4. 按 operation definition 幂等应用步骤。
+5. 最后发布稳定 metadata,并清理 reservation。
+
+进程中断后,普通 writer 不会把新旧实体拼成稳定结果。`mancode context doctor` 和 `mancode operation` 根据 journal 继续 repair;只有能证明没有可见业务写时才允许 abort。
+
+## 版本与兼容
+
+`schema.json` 支持 manifest version 1 和 2,layout version 固定为 3。0.4.0 新初始化项目直接写入 V2;历史 V1 项目只有完成显式 Policy 2 upgrade 后才写入 V2。激活状态包括 `initializing`、`dual_read`、`activating`、`v3_active` 和 `repair_required`。
+
+Reader 和 writer 必须先通过兼容门禁。legacy 迁移采用隔离 stage、显式确认和 journaled activation;不能把当前 Git HEAD 或当前用户伪装成历史事实。
+
+## Transport
+
+默认 `local` transport 在同一 Git common directory 内协调。可选 `git-ref` transport 使用 `refs/mancode/team` 在不同 clone 间显式同步:
+
+```bash
+mancode team sync pull
+mancode team sync push shared: --expected-task-revision N --session
+```
+
+远端不会自动同步业务代码。bundle、ownership fence 和 remote revision 只协调 mancode 权威;调用者仍需自行同步 Git 分支。
diff --git a/docs/engineering.md b/docs/engineering.md
new file mode 100644
index 0000000..e3d6cbd
--- /dev/null
+++ b/docs/engineering.md
@@ -0,0 +1,49 @@
+# 工程约定
+
+mancode 的实现原则是:外科手术式修改、可验证、可恢复。
+
+## 开发规则
+
+- 只修改需求涉及的路径,不顺带重构相邻模块。
+- 优先复用现有实现、标准库、平台能力和已安装依赖。
+- schema parser 拒绝未知字段;跨实体写入必须有 revision 和 operation journal。
+- shared 内容先做隐私筛查,再 canonicalize 和计算 digest。
+- adapter 只管理自己的文件或明确标记的托管区。
+- 探测失败应安全降级;业务一致性失败应停止写入并进入 repair。
+
+## 代码地图
+
+| 目录 | 职责 |
+|---|---|
+| `src/context/` | schema、Task Aggregate、任务 mutation 和 Context Pack |
+| `src/runtime/` | session、锁、operation、reservation、recovery 和 retention |
+| `src/team/` | actor、claim、handoff、checkpoint 和 transport |
+| `src/installers/` | 平台 bootstrap、managed block 和 capability 检查 |
+| `src/commands/` | CLI 解析后的应用服务边界 |
+| `src/system/` | 项目检测、扫描和 legacy 辅助功能 |
+| `tests/` | contract、crash matrix、E2E 和 adapter 回归 |
+
+## 验证
+
+从最窄验证开始,再按风险扩大:
+
+```bash
+npx vitest run tests/.test.ts
+npm run typecheck
+npm run lint
+npm test
+npm run build
+npm run test:dist
+```
+
+发布候选先运行 `npm run prepublishOnly`。提交并推送最终 `develop` 后,运行 `npm run release:check -- --candidate <完整提交 SHA>`,由远端候选的干净 checkout 重跑完整门禁、跨 clone、legacy、audit、pack 和 tarball 安装 smoke,并生成本地 SHA-256 证据。涉及 Windows 原子文件行为时追加 `npm run test:windows-smoke`;涉及网站时运行对应网站测试和浏览器检查。
+
+不要把历史测试数量写入长期文档。报告当前命令、退出码和失败原因即可。
+
+## 文档与发布
+
+- `README.md` 和 `README.en.md` 面向用户并保持功能声明一致。
+- `docs/` 只描述当前契约和未完成验收,不保存已完成实施计划。
+- 新 CLI 入口必须同步 help、测试和公开参考。
+- 平台能力声明必须区分自动化 contract 与真实宿主证据。
+- 发布版本由 `package.json` 与 `src/version.ts` 共同约束。
diff --git a/docs/planning-governance-evolution.md b/docs/planning-governance-evolution.md
new file mode 100644
index 0000000..0eced37
--- /dev/null
+++ b/docs/planning-governance-evolution.md
@@ -0,0 +1,449 @@
+# 规划治理演进与安全发布契约
+
+本文是 mancode 0.4.0 的规划治理实施与发布契约。它描述目标行为、兼容边界、验证证据和停止条件。`4dc2e7e` 是当前已验证的开发集成候选;后续契约修复必须进入新的唯一候选。在最终发布门禁和 npm 发布完成前,本文的“已完成”只表示代码与自动化验证完成,不表示已经发布。
+
+## 1. 决策摘要
+
+| 阶段 | 发布内容 | 明确不做的事 | 放行前提 |
+| --- | --- | --- | --- |
+| 0.4.0 | additive advisory、adapter digest/upgrade、policy parser/capability gate、显式 project upgrade、已升级项目的 Policy 2 默认值,以及 local transport 的原子 `reframe` | 不强制 `solo -> /man`;不批量重写旧 workflow;不把项目升级伪装成普通 `refresh`;不自动降级 policy;git-ref transport 不执行 reframe | A–F 工作包、完整测试、真实宿主验收和 npm 发布前检查全部通过;只有完成 project upgrade 的项目启用 Policy 2 |
+
+这是唯一对外版本。工作包可以分 PR、独立合并和独立评审,但不产生中间发布。任何能力都必须一起通过 0.4.0 release gate;在此之前不得通过 feature flag、环境变量、手工 metadata 或 npm 预发布提前启用。
+
+### 当前状态(2026-07-22)
+
+| 类别 | 状态 | 证据 |
+| --- | --- | --- |
+| A–F 实现工作包 | 工作树生产加固完成 | Policy 2、adapter recovery、reframe、旧 CLI 黑盒、decision-readiness、requirements draft 和 Continuity 命名已经完成;本轮继续修正平台发布证据与最终 release-check |
+| 本地发布前检查 | 新候选待重跑 | 最终候选必须通过 `npm run prepublishOnly`,并由 `npm run release:check -- --candidate ` 从远端干净 checkout 重跑跨 clone、legacy、audit、pack 和 tarball 安装 smoke |
+| GitHub Quality gate | 历史候选通过 | `2c9d697` 的 [Quality gate run 29930349142](https://github.com/whitelonng/mancode/actions/runs/29930349142) 成功;本轮形成新候选后必须重跑 |
+| GitHub Windows gate | 历史候选通过 | `2c9d697` 的 [Windows gate run 29930353690](https://github.com/whitelonng/mancode/actions/runs/29930353690) 在 CMD、PowerShell、Git Bash 全部成功;本轮形成新候选后必须重跑 |
+| `develop` 远程一致性 | 新候选待形成 | `2c9d697` 当前与 `origin/develop` 对齐;本轮工作树形成新候选后需重新推送和对齐,`main` 不得修改 |
+| Claude Code 条件式澄清实测 | 工作树通过 | Claude Code 2.1.142 使用本地 0.4.0 tarball:清晰请求直接执行,需求不清晰时提问等待,明确但与证据冲突的请求被拦截;真实测试同时发现并修复了隐藏 bootstrap skill 未稳定加载的问题,最终改为 `CLAUDE.md` 受控区,候选形成后仍需重跑 |
+| 最终发布验收 | 未完成 | 五平台真实宿主与跨宿主恢复、自动 release-check、最终 Beta gate 和候选 tarball 验收仍待完成 |
+| npm 发布 | 未执行 | 在上项全部完成前保持禁止 |
+
+## 2. 目标、不变量与非目标
+
+### 2.1 目标
+
+1. 让 `/man` 在入口、语义 owner、source of truth、状态和 contract 发生不确定时停下来,而不是用默认值继续推进。
+2. 让旧 CLI 在 V2 manifest 的 reader/writer 边界显式拒绝,并让 0.4.x parser 拒绝未知 policy,避免“看起来成功但执行了旧规则”。
+3. 让新 policy 只影响新建 workflow;历史 workflow 按创建时记录的 policy 继续运行。
+4. 让 0.4.0 的 local `reframe` 具备单一 journal、单一 revision 线和可证明的中断恢复结果。
+
+### 2.2 必须保持的不变量
+
+- `.mancode/shared|local/workflows//metadata.json`、`requirements.json`、`plan.md`、review ledger、verification ledger、claim、handoff 和 task-head fence 的权威关系不变。
+- Markdown 是人类可读产物;完成门禁和兼容门禁读取结构化实体及 digest,不以文本存在代替权威状态。
+- 所有跨实体写入都经过 durable operation、expected revision、锁和 recovery action。
+- 任何重新对齐触发都先保留当前权威文件;不能通过手工编辑 `currentStep`、`planning`、requirements 或 plan 制造“回退”。
+- policy 版本是 workflow 创建时的事实。后续 CLI 升级不会静默把 `planning: 1` 改成 `2`。
+
+### 2.3 非目标
+
+- advisory 不改变 Solo 的执行授权,也不把提示性升级变成自动升级。
+- advisory 本身不写 `planning: 2`、`blocked`、`reframe` 事件或其他状态来模拟回 Step 2。
+- 不创建独立的 Domain Matrix 权威文件;它只能是高风险 `plan.md` 的可选章节。
+- 不用 scope-change 表达需求重构。scope-change 只处理 implementation scope 和 claim successor。
+- git-ref reframe 不属于本计划;在其具备跨 clone 原子语义前始终稳定拒绝,不发布本地成功、远端稍后同步的变体。
+
+## 3. 基线证据与代码 owner
+
+以下证据以当前工作树的 Continuity 实现为准。候选形成前后都应重新运行对应 contract;如果在途改动改变了行号或行为,更新证据而不是沿用旧结论。
+
+| 事实 | 当前证据 | 对本计划的影响 |
+| --- | --- | --- |
+| workflow create 保留 Policy 1 基线,只在 V2 已升级项目的新 `/man` 上使用 Policy 2 默认值 | 以 release candidate 的 public `workflow create` contract 和 [`src/context/workflow-create.ts`](../src/context/workflow-create.ts#L277) 共同复核 | 不以单个 builder 行号宣称基线;项目升级状态和 workflow mode 共同决定默认值 |
+| policy parser 只接受各组件显式支持的版本白名单 | [`src/context/workflow-metadata.ts`](../src/context/workflow-metadata.ts#L621) | 未知版本产生稳定的 `WorkflowPolicyVersionUnsupportedError`,不能被当成未来版本静默接受 |
+| context compatibility 使用 adapter 的实际磁盘 inventory | [`src/commands/context.ts`](../src/commands/context.ts#L881) | manifest echo 不作为磁盘内容证据;inventory、renderer version 和内容状态必须共同匹配 |
+| adapter inspection 重建 expected bytes/digest,并分类 `ready`、`missing`、`stale`、`unreadable` | [`src/installers/v3-adapter.ts`](../src/installers/v3-adapter.ts#L626) | status 与 mutation gate 都以物理 target 为准,非 ready 状态不能与 manifest version 混淆 |
+| requirements finalize 与 plan revise 显式把旧 review/verification ledger 标为 stale | [`src/context/requirements-finalize.ts`](../src/context/requirements-finalize.ts#L188)、[`src/context/plan-revision.ts`](../src/context/plan-revision.ts#L347) | 复用 ledger stale 的验证模式,但 reframe 仍使用独立原子 operation |
+| scope-change 会处理旧 claim 与 successor claim | [`src/context/scope-change.ts`](../src/context/scope-change.ts#L637) | 它不是需求重构;reframe 使用独立 operation type 和 eligibility |
+| parent contract 变化会使 child snapshot stale | [`src/context/child-result-merge.ts`](../src/context/child-result-merge.ts#L331) | reframe 在入口拒绝 active child,而不是事后合并 stale 结果 |
+| reframe eligibility 显式拒绝 active child、open handoff 和 active solo assignment | [`src/context/reframe.ts`](../src/context/reframe.ts#L497) | advisory 只返回诊断;不得通过局部清理绕过跨实体约束 |
+
+**实现 owner 约定:** workflow metadata/parser 负责 policy 结构和状态转换; adapter installer/status 负责渲染、digest 和物理文件; compatibility/resolver 负责 reader/writer 门;workflow operation 模块负责跨实体写入;templates 只负责 advisory 文本,不得直接写权威状态。
+
+## 4. 0.4.0:Additive Advisory
+
+### 4.1 Scout 报告字段
+
+在 `scout-report.md` 增加以下可选小节。它们是调研证据,不进入 workflow metadata,也不替代 requirements 的确认。
+
+```markdown
+## Current Behavior Evidence
+- Observation: <当前真实行为>
+- Evidence: ``
+- Reproduction/validation: <如何复现或验证>
+
+## Candidate Semantic Owner
+- Candidate: <模块、实体或命令>
+- Confidence: high | medium | low
+- Unresolved conflict:
+
+## Source of Truth
+- Authority: <结构化文件、ledger、manifest 或外部系统>
+- Readers: <读取方>
+- Writers: <唯一写入路径或 operation>
+- Derived copies:
+
+## Historical / Compatibility Impact
+- Existing workflows: <影响或 no change>
+- Legacy/transport: <影响或 no change>
+- Migration/rollback: <需要的证据>
+```
+
+字段规则:
+
+- “可选”表示只有相关或能从项目事实验证时才写;不得为了填满模板编造 owner 或 source of truth。
+- `Current Behavior Evidence` 至少包含一个可复现观察和一个仓库路径、测试或命令证据;只有推测时必须标为未知。
+- `Candidate Semantic Owner` 可以不是最终 owner,但必须写置信度和冲突;存在两个同等候选时,Plan Coach 必须停在 `NEEDS_CLARIFICATION`。
+- `Source of Truth` 必须区分 authority 与 derived copy。adapter、cache、Markdown 默认不是任务状态的 owner。
+- `Historical / Compatibility Impact` 至少回答旧 workflow、legacy fixture、transport 和 rollback 是否受影响;不受影响也要明确写 `no change`。
+
+### 4.2 Plan Coach 规则
+
+Plan Coach 继续是只读组件。它在产生计划前执行以下检查:
+
+1. 所有候选方案是否针对同一个用户目标、同一验收边界和同一 scope。若某个选项偷偷改变目标或把问题改成另一个问题,返回 `NEEDS_CLARIFICATION`。
+2. 每个选项的复杂度由谁承担:实现代码、迁移、运行维护、用户操作、兼容层或测试。不能只写“更简单”,要写承担者和可观察成本。
+3. 输出唯一推荐,并给出拒绝其他选项的主要理由。推荐不是“由用户决定”;用户仍可否决,但计划不能同时保留多个未决方向。
+4. 简单任务可以只列一个明显可行方向,并标注为什么没有真实替代方案;不能为了满足“多选”制造伪选项。
+5. Continuity mode entry 在 requirements finalize 前执行条件式 decision-readiness gate:同时检查清晰度、与项目证据的一致性和风险;清晰且可靠的需求不制造形式问题,仍有会改变目标、范围、行为、验收、owner/source of truth 或关键约束的歧义时,必须停下提问并等待用户回答,不得把未验证假设写成 confirmed requirements。
+
+建议的输出契约:
+
+```text
+READY_FOR_PLAN
+goal:
+options:
+ - id:
+ solves:
+ complexity_bearer:
+ tradeoffs:
+recommendation:
+stop_conditions:
+```
+
+违反同目标、复杂度承担者或唯一推荐规则时,不写 `plan.md`,只返回 `NEEDS_CLARIFICATION` 及缺失决策。简单任务的单一方向仍必须有 `recommendation` 和停止条件。
+
+### 4.3 Domain Matrix(仅高风险任务)
+
+Domain Matrix 是 `plan.md` 内的可选章节,不另建权威文件。以下任一条件成立时建议加入:入口或流程跨平台不一致、owner/source of truth 不清、状态或 contract 语义会变化、跨 workflow/child/team/transport、迁移或兼容影响超过一个版本。
+
+推荐表格:
+
+| Domain | Current behavior/evidence | Candidate owner | Source of truth | Contract/state impact | Compatibility/history | Validation | Rollback/stop |
+| --- | --- | --- | --- | --- | --- | --- | --- |
+| `` | `` | `` | `` | `` | `` | `` | `` |
+
+表格只帮助计划审查,不产生运行时决策。最终实现仍必须由对应 authority writer 和 operation contract 证明。
+
+### 4.4 Solo 到 `/man` 的升级条件
+
+advisory 只增加建议,不改变 Solo 权限或当前 workflow:
+
+- 入口或流程不一致;
+- semantic owner 不清;
+- source of truth 不清;
+- 状态或 contract 语义会变化;
+- 任务范围、架构、成本或验收发生跨文件/跨模块变化;
+- 需要历史兼容、迁移、跨平台或团队协调证据。
+
+命中条件时,Solo/Head Coach 应建议改用 `/man` 并说明原因。除非用户明确发起新 `/man` workflow,否则不得自动改变 mode、current step、policy 或 authority。
+
+### 4.5 停止与重新对齐诊断
+
+以下任一条件出现,实施立即停止:
+
+- 新证据推翻已确认的目标、owner、source of truth 或验收;
+- 发现入口/流程在平台间不一致,且会导致不同语义;
+- 需要改变 status、contract、policy 或 workflow transition 的含义;
+- 发现 adapter stale、writer 不兼容、未完成 operation、active child、open handoff 或 active solo assignment;
+- 用户提出的变化已经超出当前 requirements/plan 的 scope。
+
+advisory 的唯一结果是停止当前代理执行,并返回只读诊断 `NEEDS_REALIGNMENT` 与原因 `MANCODE_REFRAME_REQUIRED`。该诊断不写入 workflow authority,也不调用通用 `workflow update`。
+
+- 保留当前 `requirements.json`、`plan.md`、review/verification ledger、claims 和 handoff,且不写入任何 metadata;
+- 不手改 `currentStep`,不写 `planning: 2`,不归档旧文件;
+- 不释放 claim,不取消 handoff,不宣称任务已回到 Step 2;
+- 用户可以查看当前 authority,并明确选择新建 `/man` workflow 或显式执行第 7 节的 local `reframe`。旧 workflow 不因该诊断而自动获得新的状态转换。
+
+这是“停止并保留证据”,不是 reframe。advisory 不得把诊断伪装成持久 blocker;真正返回 Step 2 只由第 7 节的独立 operation 完成。
+
+## 5. 0.4.0 的 Policy 2 基础能力
+
+以下四个能力是 0.4.0 的独立工作包和共同 release gate,不是中间发布版本。它们分别有失败测试;不能用 `VERSION` 常量、managed marker 存在或 manifest 回显互相替代。
+
+### 5.1 Adapter 内容 digest 与 stale 检测
+
+目标契约:
+
+- 对每个平台的每一个 managed target,以 `UTF-8("mancode-adapter-digest-v1") || 0x00 || UTF-8(target identity) || 0x00 || managed bytes` 作为 hash 输入,计算 `sha256:<64 hex>`。renderer 的 expected bytes 必须是规范化 UTF-8/LF;磁盘的 actual bytes 不先把 CRLF 转成 LF,否则换行漂移和截断会被掩盖。整文件 target 的 managed bytes 是整个文件,嵌入式 target 的 managed bytes 仅为 marker 边界内的 block,用户托管区域不参与 digest。
+- status 同时读取磁盘内容、manifest 的 renderer version 和 renderer 重建的期望内容,返回 `ready`、`missing`、`stale` 或 `unreadable`,并提供 target、actual digest、expected digest、renderer version 和修复建议。
+- 只有所有 required adapter 均 `ready`,compatibility gate 才允许需要 adapter 的 mutation。stale 读可以返回诊断,但不得继续执行新 policy。
+- digest 必须覆盖实际 managed 内容;仅检查 marker 或版本常量不能证明用户没有改动、文件没有截断、换行没有变化。
+- 首次落地时先对五个平台、共享 target(如 AGENTS)和 legacy marker 做 fixture,确认同一内容在支持的平台上得到同一 digest。
+
+**本计划的选择:** 不把 content digest 写入 V1 或 V2 manifest。manifest 继续保存 renderer/schema version;status 每次从 renderer 重建 expected digest,并与磁盘 actual digest 比较。可重建的性能 cache 只能放在 local cache,不能成为 authority。性能问题不能在 0.4.0 临时扩展 schema;它必须作为后续独立设计重新评审。
+
+`managedAdapters` 的 key 是 required adapter inventory,不是五个平台的固定全集。greenfield 只登记初始化时选择的平台;后续新增平台由 journaled adapter upgrade 同时发布 managed targets 和 inventory。compatibility 必须双向比较 manifest inventory 与实际主 target;required target 全部丢失时,空的实际集合不能通过门禁。
+
+### 5.2 显式 adapter 升级命令
+
+0.4.0 已固定并由 CLI contract 覆盖的命令:
+
+```bash
+mancode adapter upgrade --all --dry-run
+mancode adapter upgrade --all --confirm --operation-id --session --client
+mancode adapter upgrade --platform --dry-run
+mancode adapter status --json
+```
+
+升级命令必须:
+
+1. 先读取 project manifest、实际 adapter inventory、profile 和当前 writer capability;stale、路径冲突、未完成 operation 或版本门禁失败时只报告,不写文件。
+2. 在 staging 目录渲染所有目标,展示变更 target、digest 和托管区域差异;用户确认后才进入 journal。
+3. 在 adapter locks 下按目标写入,使用 before/target digest recovery action;中断后由 `operation repair` 继续或在无业务写的前提下 abort。
+4. 写完重新读取并验证每个 target;仅在 renderer version 变化时更新 manifest 的 adapter version/inventory,不写 content digest。任何一个 target 失败都不能报告整体成功。
+5. 不修改 task、requirements、plan、policy 或 step;adapter 升级与 workflow 语义升级是两个 operation。
+
+`refresh-project` 可以发现并报告 stale,但不能悄悄完成 adapter upgrade。`install --force` 是否复用底层 writer 由实现决定,但必须保留一个语义明确、可审计的显式 upgrade 入口。
+
+### 5.3 Policy parser 版本白名单
+
+版本解析必须从“任意正整数”改为按组件白名单:
+
+| CLI/release | 可读取/写入的 planning policy |
+| --- | --- |
+| 既有 0.3.x CLI | 只支持 V1/Policy 1;已发布的 0.3.18 在 V2 manifest schema parser 边界拒绝未知的 `workflowPolicyDefaults`,并在 policy 执行和 authority mutation 前终止;能解析 V2 的 reader/writer 仍由 `minReaderVersion`/`minWriterVersion` 拒绝 |
+| 0.4.0(未升级项目) | 读取和写入已有 `1`;新建仍按项目记录的默认值 |
+| 0.4.0(已升级项目) | `1` 和 `2`;新建 `/man` 默认 `2` |
+
+解析失败必须发生在 workflow mutation 之前,并携带 component、observed version、supported versions 和 required writer。不得把未知版本降为 1,也不得把缺省值自动解释成 2。
+
+### 5.4 Writer capability 与 `minWriterVersion`
+
+兼容门分两层:
+
+- CLI 启动 mutation 前声明 capability 集合,例如 `planning-policy:1|2`、`adapter-digest:1`、`reframe-local:1`;resolver 根据 task/project 和 transport 需要的 capability 逐项检查。git-ref transport 不声明 `reframe` capability。
+- project manifest 使用已有的 `minWriterVersion` 作为粗粒度下限。启用 Policy 2 的升级操作必须把 `minReaderVersion` 和 `minWriterVersion` 都提高到能完整理解 Policy 2、adapter digest 和 recovery contract 的版本,确保旧 CLI 在解析 workflow policy 前被 manifest gate 阻断。
+
+门禁顺序固定为:读取 manifest → 检查 reader/writer semver → 检查 capability → 检查 adapter 内容 digest → 解析 workflow policy → 获取 locks。任一失败都没有业务写入。
+
+建议的稳定错误:`MANCODE_WRITER_VERSION_TOO_OLD`、`MANCODE_WRITER_CAPABILITY_MISSING`、`MANCODE_ADAPTER_CONTENT_STALE`、`MANCODE_POLICY_VERSION_UNSUPPORTED`。错误输出应指向显式升级命令,而不是建议手工编辑 manifest。
+
+## 6. 0.4.0:项目升级后的 Policy 2 默认值
+
+### 6.1 项目升级边界
+
+0.4.0 已固定并由 CLI contract 覆盖的命令:
+
+```bash
+mancode project upgrade --policy 2 --dry-run
+mancode project upgrade --policy 2 --operation-id --session --client
+```
+
+升级是 project-level journaled operation,至少包含:
+
+1. 预检:所有 adapter digest fresh;没有 `operation_pending`/`repair_required`;没有正在进行的迁移;当前 writer 满足新 policy 和 adapter capability。
+2. 生成 staging manifest 和新默认策略,记录 before/after digest、minReader/minWriter、操作 ID 和 release version。
+3. 用户确认后原子提交 project policy marker/default 和兼容门;不改任何已有 workflow 的 `governance.policyVersions`。
+4. 重新读取 project 与 workflow fixture,证明新建路径选择 2、旧路径仍按记录选择 1。
+
+升级失败或进程中断时,必须由 journal repair 恢复到升级前的 project authority;不能留下“默认值已变但门禁未变”的半状态。
+
+**推荐的 source of truth:** 0.4.0 引入支持 V1/V2 白名单的 schema manifest parser;V2 保持 `layoutVersion=3`,只新增 `workflowPolicyDefaults: { planning }`,并提高 `minReaderVersion`/`minWriterVersion`。review 与 verification 没有本次升级的 policy 语义,不能提前加入 defaults。现有 V1 项目必须显式升级到 V2;0.4.0 新初始化项目只有在 adapter digest 校验完成后才创建 V2,因此视为已升级。不要把 planning policy 放进现有 `TeamPolicyV1`:该实体的 owner 是团队推荐、可见性和 retention,不是 workflow 治理版本。
+
+### 6.2 新旧 workflow 规则
+
+- 只有已完成项目升级的项目,之后新建的 `/man` workflow 才默认 `policyVersions.planning=2`。
+- 现有 workflow 的 planning/review/verification policy 按创建时记录保持不变;读取、plan revise、review 和 verification 不得因为 CLI 升级而批量重写。
+- 0.4.0 在未升级项目中不得隐式写 2。若用户需要 Policy 2,必须先完成显式 project upgrade 并通过所有门禁。
+- 删除、复制或导出 workflow 时保留 policy provenance;不能以“当前 CLI 版本”重算历史 policy。
+
+### 6.3 回滚边界
+
+- 在 project upgrade journal commit 前可 repair/abort,前提是没有可见业务写。
+- 一旦已有 Policy 2 workflow 被创建,不允许把 project `minWriterVersion` 或默认 policy 静默降回旧值;发布回滚只能停止创建新 workflow,保留已有 policy 事实。
+- 发现 adapter stale、旧 writer 或解析不支持时,阻塞 mutation 并要求 adapter/project upgrade;不能自动降级到 Policy 1。
+
+## 7. 0.4.0:local transport 的独立原子 `reframe` operation
+
+`reframe` 是需求语义重构,不是 scope-change 的别名,也不是设置一个 step 数字。它随 0.4.0 发布,但只有 local transport 支持;完整 operation 和 recovery 是 0.4.0 的 release gate。
+
+### 7.1 Eligibility 与拒绝条件
+
+在获取 task、task-head、claims、handoffs、review、verification 的锁后,以 expected task revision 做一次完整检查:
+
+- task 必须是可继续的 active workflow,不能是终态、`operation_pending` 或 `repair_required`;
+- 有 active child 时拒绝:`MANCODE_REFRAME_ACTIVE_CHILD`;
+- 有 draft/offered/open handoff 时拒绝:`MANCODE_REFRAME_OPEN_HANDOFF`;
+- 有 active solo assignment 时拒绝:`MANCODE_REFRAME_ACTIVE_SOLO`;
+- git-ref transport 一律拒绝:`MANCODE_REFRAME_GIT_REF_UNSUPPORTED`;其跨 clone receipt/CAS 设计不属于本计划;
+- 任一 claim、task head、review/verification digest 或 session freshness 过期时拒绝并要求重新读取 context。
+
+拒绝发生在任何 authority 写入之前。特别是不能先释放 claim 再发现 child 或 handoff,不能先改 currentStep 再等待用户确认。
+
+### 7.2 单一 journal 的原子步骤
+
+建议 operation type 为 `reframe`,并为每个目标写 recovery action:
+
+1. 记录 before revision、requirements digest、plan digest、review/verification digest、active claim IDs、handoff IDs 和 code head。
+2. 写 `transitionState=operation_pending`,固定 operation ID 和 expected revisions。
+3. 将旧 `requirements.json` 和 `plan.md` 复制为不可变 archive artifact,保存内容 digest、来源 revision、operation ID 和时间;旧文件不被覆盖或删除。
+4. 在同一 operation 中释放所有 active claims;不能复用 claim identity,也不能把旧 claim 标记成新范围的 claim。
+5. 重置旧 plan decision,并将 review 与 verification ledger 标为 `stale`,保留原证据和 stale reason。`soloExecution` 必须已为 `null`;active assignment 在 eligibility 阶段拒绝,不能在 operation 中清除它来绕过拒绝。
+6. 写入新的 requirements draft/clarification-needed 状态、`currentStep=2`、`status=in_progress` 和 reframe checkpoint;这些字段只能作为同一 journal 的最终提交出现。
+7. 更新 aggregate 和 task-head fence,提交 journal,清理 reservation;最后才允许新的 Step 2 writer 继续。
+
+中断恢复必须能重跑每一步而不重复归档、重复释放或生成两个合法 task head。若无法证明前后状态,保持 `operation_pending`/`repair_required` 并拒绝普通 mutation。
+
+### 7.3 与现有操作的边界
+
+| 操作 | 能改变什么 | 不能代替什么 |
+| --- | --- | --- |
+| requirements finalize | 确认当前需求并使旧 ledger stale | 不能归档旧需求并回 Step 2 |
+| plan revise | 在允许的步骤更新计划并使 ledger stale | 不能清理 solo/claim/handoff |
+| scope-change | 更换 implementation scope,释放/重建 successor claim | 不能改变需求语义或 workflow step |
+| `reframe` | 归档旧 requirements/plan、释放 claim、使 ledger stale、回 Step 2 | 不能清除 active assignment 来绕过 eligibility,也不能在 active child/open handoff/git-ref transport 中强行执行 |
+
+## 8. 兼容矩阵
+
+| Writer / project | 旧 Policy 1 workflow | Policy 2 workflow | 新建 `/man` | 结果 |
+| --- | --- | --- | --- | --- |
+| 0.3.x + V1 未升级项目 | 读写 Policy 1 | 不提供 Policy 2 创建 | 写 1 | 保持旧行为;Policy 2 需要显式升级 |
+| 0.4.x + 未升级项目 | 读写 | 不创建、不接受未满足门禁的 2 | 写 1 | CLI 新,但 project policy 未升级 |
+| 0.4.x + 已升级项目 | 读写且保留 provenance | 读写,要求 digest/capability | 写 2 | 目标 Policy 2 路径 |
+| 0.3.x + V2 已升级项目 | 不允许写入 | 不允许读取后执行 | 不允许创建 | 0.3.18 在 manifest parser 边界拒绝;能解析 V2 的旧 reader/writer 返回 version-too-old failure |
+| 任意 CLI + stale adapter | 读诊断 | 拒绝 mutation | 拒绝创建 | 先执行显式 adapter upgrade |
+| 任意 CLI + git-ref transport | 正常既有 transport contract | Policy 2 仍可按 transport contract 运行 | 不能 reframe | `MANCODE_REFRAME_GIT_REF_UNSUPPORTED` |
+
+“读”不等于“执行”:旧 CLI 只能在 V1/Policy 1 的兼容范围内展示安全诊断;遇到 V2 manifest 必须先由 manifest parser 或 reader gate 阻断,不能生成执行指令或写入任何 authority。
+
+## 9. 验证矩阵
+
+### 9.1 单元和 contract
+
+- policy parser:1 可接受;0、负数、小数、未知正整数、缺失值和错误 component 均拒绝。
+- workflow create:未升级项目只写 1;升级项目新 `/man` 写 2;已有 workflow 的 policy provenance 不变。
+- Plan Coach:不同目标、无复杂度承担者、多个 recommendation、无 stop condition 均返回 `NEEDS_CLARIFICATION`;简单任务单方向通过。
+- Continuity `/man`、`/manteam` 和 Solo mode entry 的条件式 decision-readiness gate:清晰且可靠的请求直接继续,决策性歧义在 requirements finalize 或代码修改前停下并等待用户回答;不强制无意义提问。
+- stop/re-align:触发时返回 `NEEDS_REALIGNMENT + MANCODE_REFRAME_REQUIRED`,且 authority 的前后 digest 完全相同。
+- Scout/Domain Matrix:可选字段不进入 metadata;高风险 plan 可包含章节;普通任务不被强制增加文件。
+- digest:内容、marker、路径、换行、截断、用户托管区域变化分别得到预期结果;manifest echo 与磁盘不一致必为 stale。
+- manifest:V1/V2 都拒绝 content digest 字段;expected digest 只能由 renderer 和 managed target 重建。
+- compatibility:reader、writer、capability、adapter digest、policy parser 按规定顺序短路,且失败前没有 journal 或业务写。
+- reframe:每个拒绝条件都在第一步失败;每个 recovery action 幂等;中断后不出现部分 Step 2。
+
+### 9.2 集成和恢复
+
+- 五个平台生成、status、stale 编辑、dry-run、确认升级、升级后重新读取。
+- adapter upgrade 在每个 target 写入前、写入后和中断点恢复;共享 `AGENTS` target 不能被两个平台重复覆盖。
+- local transport 下的 Policy 2 create/plan/review/verification 与旧 workflow 并存。
+- 两个 clone 的 git-ref 兼容门、旧 writer 拒绝、receipt recovery;所有 git-ref reframe 请求必须确认硬拒绝。
+- active child、open handoff、active solo assignment 的并发 race:先取得锁的一方决定,另一方得到 expected-revision 或明确 reframe error。
+
+### 9.3 真实宿主和跨平台
+
+发布候选必须在 Claude Code、Codex、Cursor、GitHub Copilot、ZCode 以及 Windows CMD/PowerShell/Git Bash 路径上验证:入口传播、session identity、adapter 文件、status/stale 诊断和升级命令。缺少真实宿主证据的平台只能标记 `not_applicable` 并阻止对应能力发布,不能用另一个宿主代替。
+
+## 10. 单版本 release gate
+
+### 10.1 实施工作包
+
+| 工作包 | 主要模块 | 必须新增的证据 | 完成定义 |
+| --- | --- | --- | --- |
+| A. advisory | `src/templates/agents/scout.ts`、Plan Coach template、`src/templates/skills/man.ts`、共享 adapter renderer | template snapshot、八类路由样例、简单任务单方向样例 | 所有 renderer 文案一致;advisory 不直接修改 metadata/schema |
+| B. stop/re-align diagnostic | Scout/Plan Coach template、context 输出 | 触发器 contract、authority before/after digest、重复调用测试 | 只返回 `NEEDS_REALIGNMENT + MANCODE_REFRAME_REQUIRED`;authority、step、requirements、plan、claim 不变 |
+| C. adapter digest/upgrade | `src/installers/v3-adapter.ts`、status/context callsite、operation recovery、CLI registration | 五平台 target fixture、stale 分类、dry-run、每个 crash point | compatibility 使用实际磁盘 digest;显式 upgrade 可 repair |
+| D. policy/version gate | workflow metadata parser、manifest parser、compatibility/resolver、workflow create | V1/V2 白名单、0.3.x + V2 reader/writer fixture、capability/minWriter matrix | 未知 policy/manifest 在写前拒绝;没有 fallback |
+| E. project Policy 2 upgrade | 新 project upgrade command/operation、manifest V2 writer、workflow create | V1→V2 dry-run/commit/repair、旧 workflow provenance、新 workflow default | 只有 V2 项目新建 `/man` 写 2;历史 metadata 无 diff |
+| F. atomic local reframe | 新 `reframe` context module、operation definition/recovery、archive store、claim/ledger writers | eligibility、并发、每步 crash、archive digest、task-head fence | 单 journal 全前/全后;0.4.0 local transport 发布,git-ref 一律硬拒绝 |
+
+每个工作包单独合并和评审。C、D 可以并行开发,E 依赖 C 和 D;F 与 E 可以分别完成,但二者都属于同一个 0.4.0 release gate,不能提前开启默认路径。
+
+### 10.2 0.4.0 gate
+
+当前 gate 结果如下。`[x]` 仅表示实现和自动化证据完成;真实宿主或发布验收未完成时,仍不得发布 npm。
+
+- [x] 模板和文档 contract 通过;advisory 不直接执行 schema migration、policy 写入或自动 step 变更。
+- [x] 旧 layout-version-3 fixture、legacy fixture、已有 adapter 和历史 workflow 通过原有测试。
+- [x] 触发 stop/re-align 时只返回 `NEEDS_REALIGNMENT + MANCODE_REFRAME_REQUIRED`,authority 文件内容和 claims 不变。
+- [x] Scout/Plan Coach 新字段和规则覆盖正常、缺失证据、冲突 owner、单方向简单任务。
+- [x] Continuity mode entry 恢复条件式需求澄清:清晰需求不机械追问,决策性歧义必须在 finalize/实施前向用户提问并等待回答;五平台生成契约覆盖。
+- [x] adapter digest algorithm、status 分类、stale error、explicit upgrade command 和 recovery contract 完成。
+- [x] policy parser 白名单、writer capability、`minWriterVersion`/reader gate 和 0.3.x + V2 manifest 拒绝 contract 完成;本轮已增加真实发布版 0.3.18 CLI 的黑盒写入拒绝与 authority 全树字节不变证据,后续候选仍需重跑。
+- [x] project upgrade dry-run、确认、commit、repair、abort 和 provenance 完成;现有 workflow 未被批量重写,新 `/man` Policy 2 默认值有明确创建证据。
+- [x] local transport 的完整原子 reframe、recovery、archive retention,以及与 child create、handoff create/start、solo handoff start 的真实并发竞争通过;没有复用 scope-change,也没有部分 authority 或 eligibility 绕过路径。
+- [x] 已升级/未升级项目、旧/新 workflow、local/git-ref transport 的自动化兼容矩阵通过;git-ref reframe 稳定返回 `MANCODE_REFRAME_GIT_REF_UNSUPPORTED`。
+- [x] 5 个 adapter renderer、Windows path、line ending、用户托管区和每个 target 写前/写后中断恢复在 `4dc2e7e` 通过;后续候选仍需重跑 dist/Windows/recovery gate。
+- [x] 平台证据区分 `host_session_verified` 与 `explicit_session_verified`;显式路径验证两个 active、不同且 client 匹配的 session,只满足发布证据,不提升运行时宿主信任。
+- [x] release-check 从远端候选创建干净 checkout,自动运行完整门禁、双 clone、legacy、audit、pack、tarball CLI/module smoke,并输出提交绑定的 SHA-256 证据;脚本不包含 npm publish 或 dist-tag 操作。
+- [ ] Claude Code、Codex、Cursor、GitHub Copilot、ZCode 的最终候选真实宿主验收,以及 [`docs/release-acceptance.md`](./release-acceptance.md) 要求的跨 clone、legacy、Beta gate 和干净 checkout tarball 验收。
+- [x] 按开发集成要求将候选 `4dc2e7e` 推送到远程 `develop`,且 Quality 与 Windows gate 均通过;这不等同于 release gate 通过。
+- [ ] 完成剩余发布验收后才允许进入 npm 发布检查;任何条件失败都不得发布 npm。
+
+## 11. 发布回滚与停止策略
+
+| 发现的问题 | 立即动作 | 禁止动作 | 恢复方式 |
+| --- | --- | --- | --- |
+| advisory 模板误导但无 authority 写 | 停止发布、修模板、重跑 snapshot | 不要求用户迁移 workflow | 发布前替换 adapter/模板;已有 authority 不变 |
+| adapter stale 或 renderer mismatch | 阻塞 mutation,提示显式 upgrade | 自动 `--force`、手工改 digest | upgrade dry-run → 确认 → journal repair |
+| Policy 2 parser/门禁错误 | 关闭 Policy 2 创建入口 | 把 2 降为 1 或修改旧 workflow | 修复 CLI 后重新读取;project marker 保持可审计 |
+| project upgrade 中断 | 标记 pending/repair,禁止普通写 | 直接删除半成品或手改 minWriter | `operation repair` 恢复 before/target 状态 |
+| reframe 中断或前置条件冲突 | 保留 operation journal,维持 blocked/repair_required | 手动写 Step 2、释放部分 claim | 修复原 operation;无法证明时人工处理后再重试 |
+| git-ref reframe 请求 | 稳定返回 `MANCODE_REFRAME_GIT_REF_UNSUPPORTED` | 只在本 clone 先执行、以后同步 | 本计划不提供恢复路径;后续若要支持,必须另立协议设计与发布评审 |
+
+版本回滚只回滚代码入口和默认创建策略,不回写历史 policy、requirements、plan 或 task step。任何需要修改现有权威内容的“回滚”都必须成为另一个明确、journaled 的 migration,并经过独立评审。
+
+## 12. 实施顺序与停止/重新对齐条件
+
+截至 `4dc2e7e`,步骤 1–4 是已验证的开发集成候选;本轮生产加固修复形成新候选后,受影响的自动化证据和步骤 5、7 仍需重新完成。
+
+1. [x] 冻结本文件中的错误码、字段语义、digest 规范和兼容矩阵,并复核 public workflow create 的 Policy 1 基线。
+2. [x] 完成 A–F 工作包;工作包可以独立合并,但不发布中间 npm 版本。
+3. [x] 完成 adapter digest/upgrade 和 parser/capability gate;当前工作树已通过 0.3.x + V2 manifest reader/writer fixture,新候选仍需重跑。
+4. [x] 完成 project upgrade 和 local reframe 的独立 operation/recovery;只在 upgraded marker 下打开 Policy 2 create default。
+5. [ ] 在同一个 release candidate 上通过第 10.2 节和 [`docs/release-acceptance.md`](./release-acceptance.md) 的全部真实宿主、release-check、Beta gate 和 tarball 检查。
+6. [ ] 将本轮加固后的唯一候选提交推送到远程 `develop`;`4dc2e7e` 的历史推送不替代新候选,且禁止推送、合并或创建以 `main` 为目标的发布操作。
+7. [ ] 确认远程 `develop` 与完整验收证据完全一致后,才允许发布唯一的 npm 版本 0.4.0。git-ref reframe 始终拒绝,不进入本计划范围。
+
+任何阶段出现下列情况,都回到“证据和契约评审”,而不是继续编码:
+
+- 发现两个入口给同一任务不同的 owner、scope 或状态语义;
+- authority writer 与 derived copy 的责任无法唯一确定;
+- status、contract、policy 或完成门禁的意义需要改变;
+- digest 只反映版本常量、无法重建 expected bytes,或跨平台结果不稳定;
+- 旧 CLI 能读到新 policy 却没有可靠的 reader/writer/capability 拒绝;
+- operation 无法在任意中断点证明“全前或全后”;
+- 需求变化需要同时改 requirements、plan、claims、handoff 或 step,但当前 operation 没有覆盖它们。
+
+重新对齐完成的最低证据是:新的目标、候选 owner、source of truth、兼容影响、停止条件和唯一 recommendation 已被记录,并由相应 authority writer 重新确认。仅仅修改 Markdown 或手动调整 step 不算重新对齐。
+
+## 13. 0.4.0 已定稿的四个实现选择
+
+以下选择已由 compatibility contract 固定;发布前不得在缺少重新评审和对应证据时改变:
+
+1. **Public create 基线:** 用未升级项目的端到端 create contract 证明只写 Policy 1;不得把 CLI 选项、模板默认值或 builder 默认值中的任一项当成唯一证据。
+2. **Digest 的持久化位置:** 固定采用“renderer 重建 expected digest + local 非权威 cache”,不在 V1 或 V2 持久化 content digest。性能不足必须另立设计,不能阻塞后临时扩 schema。
+3. **升级命令的公开名称:** 以现有 `adapter status/upgrade` 命令注册、JSON 输出和 shell 退出码测试确定名称;文档和实现必须只保留一个正式入口。
+4. **Capability 的编码:** 默认由 CLI 内置 capability set 加 project `minWriterVersion` 双门控制,不新增可被旧 CLI 忽略的 manifest capability 字段;local reframe 与 git-ref hard reject 必须使用不同 capability 结果。
+
+这四项仍须与第 10.2 节的共同 release gate 一起通过,才允许发布 0.4.0。git-ref reframe 不在支持范围内。
+
+## 14. 远程分支与 npm 发布顺序
+
+0.4.0 只允许按以下顺序发布:
+
+`2c9d697` 是上一已验证开发候选,其 [Quality gate](https://github.com/whitelonng/mancode/actions/runs/29930349142) 和 [Windows gate](https://github.com/whitelonng/mancode/actions/runs/29930353690) 均成功。候选后的任何实现、测试或发布文档修改都会使这些证据不再适用于新候选;新的唯一候选必须重新完成第 1–3 步并推送到 `origin/develop`,第 4–6 步必须在任何正式 npm 发布前补齐。
+
+1. 所有实现、修复和文档进入本地 `develop`,形成唯一 release candidate commit。
+2. 在该提交上完成第 10.2 节的全部自动化、恢复、跨平台和真实宿主验收。任何失败都必须修复并从头重跑受影响的 release gate。
+3. [ ] 对本轮形成的新候选只执行 `git push origin develop`。禁止直接或间接更新远程 `main`,包括 push、merge、rebase、以 `main` 为目标的 PR 或自动化发布工作流。
+4. 从远程 `develop` 的同一提交运行 `npm run release:check -- --candidate <完整 SHA>`;使用其保留的 tarball 完成真实宿主证据,再运行最终 Beta gate。
+5. 确认 release candidate commit、远程 `develop` commit、测试证据和待发布 package version 完全一致。
+6. 前五步全部成功后才允许执行 `npm publish`。不发布 `0.4.0-beta`、`0.4.0-rc` 或其他中间 npm 版本。
+
+若 `npm publish` 前发生任何代码、依赖、构建配置或发布文档变化,候选提交立即失效,必须重新执行步骤 2–5。npm 发布成功不授权更新远程 `main`。
diff --git a/docs/platform-adapters.md b/docs/platform-adapters.md
new file mode 100644
index 0000000..6bacc47
--- /dev/null
+++ b/docs/platform-adapters.md
@@ -0,0 +1,61 @@
+# 平台适配器
+
+平台适配器把相同的 Continuity workflow 暴露给不同宿主。它们安装 bootstrap 和 mode 入口,但任务、session、review 与 verification 只存于 `.mancode/`。
+
+## 能力矩阵
+
+| 平台 | 入口与文件 | 动态能力 | 当前边界 |
+|---|---|---|---|
+| Claude Code | `CLAUDE.md` 托管区、`.claude/skills/` | 项目 memory 与 mode skills;legacy 模式可用 hooks | 默认 Continuity 不依赖旧 `state.json` hooks |
+| Cursor | `.cursor/rules/`、`.cursor/commands/` | rules 与 commands | 无 mancode 原生 session API |
+| Codex | `AGENTS.md` 托管区、`.agents/skills/` | `$man*` skills | session 传播需真实宿主证据 |
+| GitHub Copilot | instruction 托管区、`.github/prompts/` | instructions 与 prompts | 能力依具体 Copilot 宿主而异 |
+| ZCode | `AGENTS.md` 托管区、`.agents/skills/` | provisional `$man*` skills | 项目级发现与命令路径仍需真实 UI 验证 |
+
+安装示例:
+
+```bash
+mancode init --platform codex,cursor
+mancode adapter upgrade --platform copilot --dry-run
+mancode adapter upgrade --platform copilot --confirm --operation-id --session --client
+mancode status
+```
+
+manifest 的 `managedAdapters` key 是项目登记的 required 平台集合。greenfield init 只登记所选平台;后续新增或修复平台必须通过带 active session 和显式确认的 adapter upgrade journal。`AGENTS.md` 和 Copilot instruction 文件中托管区外的用户内容必须原样保留。
+
+## Bootstrap 合约
+
+每个平台都必须:
+
+- 能发现原来的 `man`、`manba`、`manteam`、`manps` 和 `mansolo` 入口。
+- 在开始任务前解析 Continuity manifest、session、TaskRef 和 Context Pack。
+- 不保存易过期的 task/session 状态副本。
+- 未证明宿主 session 传播时要求显式 session。
+- 保留用户自写配置,并支持重复安装和安全卸载。
+
+## Legacy hooks
+
+只有 `mancode init --legacy` 安装读取 `.mancode/state.json` 的旧 Claude Code hooks。Continuity adapter 不应创建、读取或刷新 legacy authority。
+
+Continuity 的 Claude Code bootstrap 位于根目录 `CLAUDE.md` 的 `mancode:continuity:claude` 托管区,确保普通 Solo 请求也会加载;原有 mode skills 仍位于 `.claude/skills/`。Cursor bootstrap 位于 `.cursor/rules/mancode-continuity.mdc`,其他嵌入式托管区同样使用 `mancode:continuity:*` 标记。升级时只自动移除带 mancode 旧管理标记的 `mancode-v3`/旧 Continuity bootstrap 或托管区;用户在 `CLAUDE.md` 和同名旧文件中的自写内容会保留。
+
+Windows 上的脚本与文件替换不能依赖 Bash、jq 或 Unix rename 行为。发布流程包含 CMD、PowerShell 和 Git Bash smoke test。
+
+## Session 发布证据
+
+真实宿主证据必须显式选择 `--session-mode host` 或 `--session-mode explicit`。`host` 模式使用一次性宿主 key 比较双窗口,并且只有完整证明传播、继承和必要 hook approval 后才允许 `host_verified`。`explicit` 模式读取 `MANCODE_SPIKE_SESSION_ID` 与 `MANCODE_SPIKE_SECOND_SESSION_ID`,验证两个 session 已存在、active、互不相同且 client 与平台一致;写入证据前丢弃原始 ID。它可以得到 `explicit_session_verified` 发布证据,但运行时继续要求显式 `--session`。
+
+```bash
+MANCODE_SPIKE_SESSION_ID= \
+MANCODE_SPIKE_SECOND_SESSION_ID= \
+./node_modules/.bin/mancode context session spike \
+ --platform cursor --session-mode explicit --host-session-source none \
+ --command-propagation proven \
+ --subagent-inheritance not_applicable \
+ --subagent-inheritance-reason "Host has no child-agent API" \
+ --host-version --release-candidate --json
+```
+
+## 发布声明
+
+“文件能生成”不等于“宿主已验证”。每个平台的双窗口 session、子命令传播和子 agent 继承必须在同一发布候选上记录,才能通过内部 Beta gate。ZCode 在完成该验证前保持 provisional 描述。
diff --git a/docs/project-intelligence.md b/docs/project-intelligence.md
new file mode 100644
index 0000000..50b9056
--- /dev/null
+++ b/docs/project-intelligence.md
@@ -0,0 +1,50 @@
+# 项目检测与健康扫描
+
+mancode 先检测项目事实,再决定可用工作流和验证方式。扫描结果是证据,不是对技术栈的猜测。
+
+## Project Profile
+
+Project Profile 记录:
+
+- 项目类型:backend、web、mobile、desktop、CLI、library、data、mixed 或 unknown。
+- 语言、framework、manifest 和源码根目录。
+- 可用的 build、lint、test 与平台验证命令。
+- 是否检测到 UI 资产和浏览器自动化能力。
+- high、medium 或 low 置信度。
+
+普通 Continuity 初始化把可共享项目事实写入 `.mancode/shared/context/project.json`。项目结构变化后运行:
+
+```bash
+mancode refresh-project
+```
+
+检测不到的 framework 不会写入 profile。Git、manifest 或源码目录缺失时,初始化可以安全降级,但不会把 unknown 项目伪装成 Web 项目。
+
+## 设计资产扫描
+
+只有 profile 确认存在 UI 资产时,mancode 才扫描设计信号。当前实现识别:
+
+- Tailwind 配置中的顶层颜色、字体和 dark mode。
+- CSS custom properties。
+- 常见组件文件名。
+- 已检测到的 UI library。
+
+结果写入 checkout-local cache。`matchLevel=high` 表示存在可复用配置、CSS token 或组件;`low` 只表示依赖提示;`none` 表示没有可靠资产。
+
+```bash
+mancode refresh-style
+```
+
+扫描器不解析任意 `theme.json`、Design Tokens Community Group 文件、Figma 或运行时动态主题。Agent 可以人工读取这些资料,但必须标明它们不是自动检测结果。
+
+## Preseason
+
+`mancode manps` 是确定性健康扫描,支持 `all`、`deps`、`security`、`dead-code` 和 `config`。它检查脚本、依赖重叠、TODO、测试、配置、审美、架构和基础安全信号。
+
+```bash
+mancode manps deps
+mancode manps all --json
+mancode manps config --remediate
+```
+
+报告和问题库保存在 `.mancode/local/`。`--remediate` 仍逐项要求决定,只自动执行白名单内的低风险修复。扫描结果不授权批量改代码,也不能代替项目测试或人工安全审查。
diff --git a/docs/release-acceptance.md b/docs/release-acceptance.md
new file mode 100644
index 0000000..ce1fb02
--- /dev/null
+++ b/docs/release-acceptance.md
@@ -0,0 +1,49 @@
+# 0.4.0 Continuity 发布验收
+
+自动化 contract 必须覆盖 Continuity runtime/schema、条件式需求澄清、Policy 2、project upgrade、local reframe、operation recovery、团队协调、git-ref hard reject、五个平台生成结果和 Windows smoke path。条件式需求澄清必须证明清晰请求不被机械提问阻塞,决策性歧义会在 requirements finalize 或实施前停下、提问并等待用户回答。以下证据完成前,不得发布 mancode 0.4.0 或执行 `npm publish`。
+
+## 发布条件
+
+- 在最终发布候选运行 `npm run release:check -- --candidate <完整提交 SHA>`;该命令必须从 `origin/develop` 创建干净 checkout,运行 `npm ci`、`npm run prepublishOnly`、跨 clone、legacy、audit、pack 和安装 smoke,并输出绑定提交与 tarball SHA-256 的本地 JSON 证据。
+- release-check 先运行 `npm pack --dry-run`,再执行实际 `npm pack`,保留生成的 tarball 并完成 CLI/module smoke;npm beta/rc 不能替代该字节级验证。
+- Windows required check 在 CMD、PowerShell 和 Git Bash 上通过。
+- Claude Code、Codex、Cursor、GitHub Copilot 和 ZCode 都完成真实双窗口 session 验证。每个平台可证明受信宿主 session,或证明两个真实显式 session 隔离;显式证据只满足发布验收,不授权运行时信任宿主 key。
+- 每个平台证明子命令传播;支持子 agent 的平台还需证明继承,或记录合法的 `not_applicable` 原因。缺失、伪造、错误 client、关闭或碰撞的显式 session 证据必须拒绝。
+- 完成跨真实宿主 resume、claim、handoff 和恢复路径。
+- release-check 中两个真实独立 clone 完成 git-ref pull、并发 CAS、handoff、receipt recovery 和代码基线交接;不再重复要求同一流程的人工双 clone。
+- release-check 中的真实 0.3.18 legacy fixture 完成 stage、activation、每个中断点恢复和严格条件下的 rollback;人工 smoke 不能替代 crash matrix。
+- 最终执行 `mancode context beta --release-candidate --json` 返回 `ready: true` 和空 blockers。
+- 候选提交已经推送到 `origin/develop`,且本地 `HEAD`、`origin/develop`、全部验收证据和待发布 package version 指向同一提交。
+- 记录验收前后的 `origin/main` commit,并证明它没有变化。禁止 push、merge、rebase 或创建以 `main` 为目标的发布操作。
+- 上述条件全部成功后才允许执行 `npm publish`;npm 发布成功后也不得更新 `main`。
+
+## 发布顺序
+
+1. 在本地 `develop` 完成唯一 release candidate。
+2. 完成全部自动化、跨平台、恢复和真实宿主验收。
+3. 只执行 `git push origin develop`。
+4. 从远程 `develop` 的同一提交运行 release-check,使用它保留的 tarball 完成真实宿主 spike,再运行最终 Beta gate。
+5. 所有证据一致且 `origin/main` 未变化后,执行唯一一次正式 `npm publish`。
+
+任何测试失败或候选提交变化都会使已有验收失效。修复后必须重新验证;不得先发布 npm 再补证据。
+
+## 证据规则
+
+所有证据必须绑定同一个 `origin/develop` commit、mancode version、操作系统和宿主版本。历史祖先提交、未推送提交或替代宿主结果只能用于调试,不能用于最终 Beta gate。
+
+session evidence 不保存原始 key、token、绝对业务路径或任务正文。屏幕截图和日志应脱敏,并保存在发布流程约定的本地证据目录,而不是长期开发文档中。
+
+## 当前未完成项
+
+- 本轮发布门禁修复形成的新候选上的 Quality gate,以及 Windows CMD、PowerShell、Git Bash required gate。
+- 五个平台在最终候选上的完整 session spike。
+- 跨真实宿主协作与恢复验收。
+- 最终候选上的 release-check,包括自动双 clone git-ref 和 legacy 验收。
+- ZCode 项目级 skill 发现和 workspace command 路径确认。
+- release-check 输出的候选 tarball、SHA-256、安装与 CLI/module smoke 证据。
+- 验收前后 `origin/main` 未变化的记录。
+- 汇总全部证据后的最终 Beta gate。
+
+上一开发候选 `2c9d697` 的 [Quality gate](https://github.com/whitelonng/mancode/actions/runs/29930349142) 和 [Windows compatibility gate](https://github.com/whitelonng/mancode/actions/runs/29930353690) 均已通过,后者覆盖 Windows CMD、PowerShell 和 Git Bash。该证据是历史基线;本轮门禁修复形成新候选后必须重新执行。当前候选 SHA 和运行链接保存在外部发布证据中,避免把自引用 SHA 写进候选提交。
+
+完成一项时更新本文件的未完成列表;不要创建新的平行验收计划。
diff --git a/docs/workflows.md b/docs/workflows.md
new file mode 100644
index 0000000..34751b0
--- /dev/null
+++ b/docs/workflows.md
@@ -0,0 +1,130 @@
+# 工作流与团队协作
+
+mancode 不把“当前模式”保存成全局开关。平台入口创建或恢复显式 session 与 TaskRef,再读取 Context Pack。
+
+## 模式
+
+| 模式 | 用途 | 持久任务 |
+|---|---|---|
+| `solo` | 小改、最窄验证和一次受限 diff 自检;先判断需求是否清晰 | 否;可执行已确认的 solo handoff |
+| `manba` | 复现、根因诊断、最小修复或真实验证 | 是,5 步 |
+| `man` | 需求对齐、计划及可选完整治理 | 是,9 步 |
+| `manteam` | 带 owner、participant、scope 和 claim 的团队治理 | 是,9 步 |
+| `manps` | 确定性项目健康扫描 | 否 |
+
+平台入口分别表现为 `/man`、`$man` 或 prompt,具体映射见 [platform-adapters.md](./platform-adapters.md)。
+
+### 条件式需求澄清
+
+`solo` 和 `/man` 都先判断需求是否足够清晰,不机械追问。目标、范围、验收边界和关键约束可以从用户请求、项目事实或明确的安全默认值确定时,直接继续;不需要为了形式制造问题。
+
+如果仍有会改变目标、范围、用户可见行为、验收、架构、数据、安全、兼容性、owner 或 source of truth 的歧义,必须先向用户提出聚焦问题并等待回答。回答前不得把假设写成 confirmed requirements;复杂度、owner、迁移、跨模块或团队决策超出 Solo 边界时,应推荐 `/man` 并等待用户选择。该判断属于 Continuity mode entry 的固定行为契约。
+
+| 输入状态 | 处理方式 |
+| --- | --- |
+| 目标与需求清晰、与项目证据一致、风险低 | 默认 Solo 直接执行最窄改动,不创建 session 或 TaskRef,不做形式化追问 |
+| 目标清晰、需求有缺口 | 先把未知项分成 blocking、recommendable、defaultable;只为会改变决策的 blocking 项停下提问 |
+| 表述明确但与项目证据冲突,或涉及认证、支付、敏感数据、删除、迁移、公开 API、并发、基础设施等高风险边界 | 展示证据和影响,推荐更安全路径,取得聚焦确认后再继续;“明确”不等于“正确或安全” |
+| 用户明确请求计划、架构、迁移设计或正式验收 | 可直接进入 `/man` 规划路径;普通实现中途遇到这类决策时只推荐 `/man`,不得静默切换权威 |
+
+受治理任务在等待 blocking 回答前,必须把已知事实、部分决定和开放问题写入 requirements draft:
+
+```bash
+mancode workflow requirements local: draft \
+ --file requirements.json --expected-revision N --session
+```
+
+draft 的 `blockingUnknowns` 必须列出开放决定;scope、coverage、technical decisions 或 acceptance 可以暂不完整。后续会话通过 TaskRef 恢复同一澄清状态,每次回答后更新 draft;只有 blocking 项清零且 requirements 完整时才能 `finalize`。`manba` 在修复前还必须先从复现、测试、文档、历史或语义 owner 建立预期行为,无法确定时先问一个聚焦问题。
+
+## `man` 流程
+
+1. 调研已有实现、复用点、风险和未知项。
+2. 澄清会改变范围、架构、成本或验收的问题。
+3. 建立带版本的计划。
+4. 用户选择只保留计划、solo handoff、完整治理或修订计划。
+5. 按确认范围实施。
+6. 运行验证并确定 targeted/full 审查范围。
+7. 质量审查。
+8. 仅在 full 深度执行安全与边界审查。
+9. 最多一轮 blocker 修复、复验、summary 和完成。
+
+需求未 ready、计划未确认、验证失败、审查 blocker 未清零、存在活动子任务或未完成 repair 时,任务不能完成。
+
+## 状态与 revision
+
+工作流状态为 `in_progress`、`planned`、`blocked`、`completed` 或 `abandoned`。终态不可恢复;`blocked` 只能在阻塞条件被显式处理后回到 `in_progress`。
+
+每个写命令都需要当前 `expected-revision`。这是一条 compare-and-swap 约束,不是可选提示。revision、requirements digest 或 plan version 变化后,旧 review 和 verification 可能变为 `stale`。
+
+典型路径:
+
+```bash
+mancode context session new --client codex --json
+mancode context session show --session --client codex --json
+mancode workflow create man "添加导出功能" --session --json
+mancode workflow requirements local: finalize \
+ --file requirements.json --expected-revision 1 --session
+mancode workflow plan local: revise \
+ --file plan.md --expected-revision 2 --session
+mancode workflow plan local: confirm \
+ --plan-decision governed_execution --expected-revision 3 --session
+```
+
+`plan revise` 必须通过 `--file ` 读取 Markdown 计划。修订与确认是两个独立写操作;每次写入后都应从命令结果或 `mancode workflow show --json` 获取最新 revision,再用于下一次 `--expected-revision`。只保留计划时把确认参数改为 `--plan-decision plan_only`。不要手工编辑 metadata 或 ledger。
+
+## Policy 2 与需求重新对齐
+
+项目治理升级是显式操作,不会批量重写已有 workflow:
+
+```bash
+mancode project upgrade --policy 2 --dry-run
+mancode project upgrade --policy 2 --operation-id --session --client
+```
+
+升级后的项目只对新建 `/man` workflow 默认使用 planning Policy 2;历史 workflow 继续使用创建时记录的 policy。
+
+当新证据推翻已确认需求时,local workflow 可以从现有 checkpoint 执行原子 reframe:
+
+```bash
+mancode context resume local: --session --client --json
+mancode workflow reframe local: \
+ --expected-revision N \
+ --checkpoint-id \
+ --summary "新证据为何使当前需求失效" \
+ --next-action "回到 Step 2 后要澄清的事项" \
+ --session --json
+```
+
+仅在 session 尚未指向目标 TaskRef 时执行 `context resume`。`--checkpoint-id` 必须是本次操作新生成的规范 ULID,不能复用旧 checkpoint。reframe 会原子归档当前 requirements、plan 和 ledger,释放有效 claim,清除 plan decision,并把任务带回 Step 2 的 draft requirements;完成命令后应停止实施,先重新澄清、finalize requirements、revise plan,再由用户确认计划。它只接受 requirements 已确认、且不存在 active child、open handoff 或 active solo assignment 的 local workflow;git-ref transport 明确拒绝该操作。
+
+reframe 的 JSON 结果会返回 `archive.archiveId` 与 `checkpoint.checkpointId`。可通过只读 CLI 检查证据,无需读取 `.mancode` 私有 authority 文件:
+
+```bash
+mancode workflow archive local: show --json
+mancode workflow checkpoint local: show --json
+```
+
+archive 输出会校验归档摘要,并返回 reframe 前的 requirements 与 plan;checkpoint 输出返回该次 reframe 的完整 checkpoint。这两个命令不修改 workflow,也不需要 `--session`。
+
+## Session 与 Context Pack
+
+session 是 checkout-local 的调用身份,不决定任务是否完成。没有真实宿主传播证据时,mutating command 必须显式传 `--session`。
+
+`mancode context show` 按 `bootstrap`、`task` 或 `full` 级别生成 Context Pack。它只返回与当前 revision 和 digest 一致的内容;发现未完成 operation 时返回 repair envelope。
+
+## 团队协作
+
+团队任务使用显式 actor、participant 和 implementation scope:
+
+```bash
+mancode team identity create --name "Alice"
+mancode team join --name "Alice" --session
+mancode team claim shared: \
+ --path 'src/api/**' --expected-task-revision N --session
+```
+
+claim 声明 path、module、API 或 schema 边界。任务或代码基线漂移后需要 revalidate;lease 过期不自动授权另一个 writer 接管。
+
+handoff 必须经过 `draft → offered → accepted|rejected|cancelled`。accept 会在同一个 journaled operation 中更新 owner、claim、checkpoint 和 task head,避免出现两个合法 owner。
+
+只有经过明确确认且通过隐私筛查的决策才能进入 shared memory。任务文本、绝对路径、凭据和宿主 session key 不应写入共享 transport。
diff --git a/package-lock.json b/package-lock.json
index 61744e8..3ad16a1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "mancode",
- "version": "0.3.18",
+ "version": "0.4.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mancode",
- "version": "0.3.18",
+ "version": "0.4.0",
"license": "AGPL-3.0-only",
"dependencies": {
"commander": "^12.1.0",
diff --git a/package.json b/package.json
index b2e9989..021e2a6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "mancode",
- "version": "0.3.18",
+ "version": "0.4.0",
"description": "AI coding agent workflow harness with mancode Continuity for cross-conversation tasks, decisions, verification, and team coordination.",
"type": "module",
"license": "AGPL-3.0-only",
@@ -63,6 +63,7 @@
"test:windows-smoke": "node scripts/windows-smoke.mjs",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
+ "release:check": "node scripts/release-check.mjs",
"lint": "biome check src tests",
"typecheck": "tsc --noEmit",
"format": "biome format --write src tests",
diff --git a/scripts/release-check.mjs b/scripts/release-check.mjs
new file mode 100644
index 0000000..f7b8283
--- /dev/null
+++ b/scripts/release-check.mjs
@@ -0,0 +1,317 @@
+import { spawnSync } from 'node:child_process';
+import { createHash } from 'node:crypto';
+import {
+ copyFile,
+ mkdir,
+ mkdtemp,
+ readFile,
+ rm,
+ writeFile,
+} from 'node:fs/promises';
+import { tmpdir } from 'node:os';
+import path from 'node:path';
+import process from 'node:process';
+import { fileURLToPath } from 'node:url';
+
+const sourceRoot = path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '..',
+);
+const options = parseOptions(process.argv.slice(2));
+
+if (options.help) {
+ console.log(
+ 'Usage: npm run release:check -- --candidate [--output ]',
+ );
+ process.exit(0);
+}
+
+const sourceStatus = runCaptured('git', ['status', '--porcelain'], sourceRoot);
+assert(sourceStatus === '', 'release check requires a clean source checkout');
+
+const candidate = normalizeCandidate(
+ options.candidate ?? runCaptured('git', ['rev-parse', 'HEAD'], sourceRoot),
+);
+const sourceHead = runCaptured('git', ['rev-parse', 'HEAD'], sourceRoot);
+assert(sourceHead === candidate, 'release candidate must equal source HEAD');
+
+const remote = runCaptured('git', ['remote', 'get-url', 'origin'], sourceRoot);
+const originDevelop = remoteRef(remote, 'refs/heads/develop');
+const originMainBefore = remoteRef(remote, 'refs/heads/main');
+assert(
+ originDevelop === candidate,
+ 'release candidate must equal origin/develop',
+);
+
+const outputPath = path.resolve(
+ sourceRoot,
+ options.output ??
+ path.join(
+ '.mancode',
+ 'local',
+ 'release-evidence',
+ `${candidate}.json`,
+ ),
+);
+const outputDirectory = path.dirname(outputPath);
+const temporaryRoot = await mkdtemp(
+ path.join(tmpdir(), 'mancode-release-check-'),
+);
+const checkout = path.join(temporaryRoot, 'checkout');
+const installFixture = path.join(temporaryRoot, 'install-smoke');
+const checks = [];
+
+try {
+ runCheck(checks, 'clean_clone', 'git', [
+ 'clone',
+ '--branch',
+ 'develop',
+ '--single-branch',
+ remote,
+ checkout,
+ ], temporaryRoot);
+ assert(
+ runCaptured('git', ['rev-parse', 'HEAD'], checkout) === candidate,
+ 'clean checkout does not match the release candidate',
+ );
+
+ runCheck(checks, 'npm_ci', 'npm', ['ci'], checkout);
+ runCheck(
+ checks,
+ 'prepublish',
+ 'npm',
+ ['run', 'prepublishOnly'],
+ checkout,
+ );
+ runCheck(
+ checks,
+ 'cross_clone',
+ 'npx',
+ [
+ 'vitest',
+ 'run',
+ 'tests/git-ref-cross-clone-e2e.test.ts',
+ 'tests/git-ref-clock-skew-contracts.test.ts',
+ 'tests/git-ref-handoff-repair-contracts.test.ts',
+ ],
+ checkout,
+ );
+ runCheck(
+ checks,
+ 'legacy_migration',
+ 'npx',
+ [
+ 'vitest',
+ 'run',
+ 'tests/migrate-contracts.test.ts',
+ 'tests/migrate-command-contracts.test.ts',
+ 'tests/migration-parity-contracts.test.ts',
+ 'tests/legacy-cli-compatibility-contracts.test.ts',
+ ],
+ checkout,
+ );
+
+ const audit = JSON.parse(
+ runCaptured('npm', ['audit', '--omit=dev', '--json'], checkout),
+ );
+ const vulnerabilities = audit.metadata?.vulnerabilities?.total;
+ assert(vulnerabilities === 0, 'production dependency audit is not clean');
+ checks.push({ name: 'production_audit', status: 'passed' });
+
+ const dryRun = parseSinglePackResult(
+ runCaptured('npm', ['pack', '--dry-run', '--json'], checkout),
+ );
+ checks.push({ name: 'pack_dry_run', status: 'passed' });
+ const packed = parseSinglePackResult(
+ runCaptured('npm', ['pack', '--json'], checkout),
+ );
+ const tarballPath = path.join(checkout, packed.filename);
+ const tarballBytes = await readFile(tarballPath);
+ const tarballSha256 = createHash('sha256')
+ .update(tarballBytes)
+ .digest('hex');
+
+ await mkdir(installFixture, { recursive: true });
+ await writeFile(
+ path.join(installFixture, 'package.json'),
+ `${JSON.stringify({ name: 'mancode-release-install-smoke', private: true })}\n`,
+ );
+ runCheck(
+ checks,
+ 'tarball_install',
+ 'npm',
+ ['install', '--no-audit', '--no-fund', tarballPath],
+ installFixture,
+ );
+ const installedCli = path.join(
+ installFixture,
+ 'node_modules',
+ 'mancode',
+ 'dist',
+ 'cli.js',
+ );
+ const installedVersion = runCaptured(
+ process.execPath,
+ [installedCli, '--version'],
+ installFixture,
+ );
+ const packageMetadata = JSON.parse(
+ await readFile(path.join(checkout, 'package.json'), 'utf8'),
+ );
+ assert(
+ installedVersion === packageMetadata.version,
+ 'installed CLI version does not match package.json',
+ );
+ runCheck(
+ checks,
+ 'tarball_cli',
+ process.execPath,
+ [installedCli, 'init', '--empty', '--platform', 'all', '--lang', 'en'],
+ installFixture,
+ );
+ const status = JSON.parse(
+ runCaptured(
+ process.execPath,
+ [installedCli, 'status', '--brief', '--json'],
+ installFixture,
+ ),
+ );
+ assert(
+ status.runtime === 'mancode-continuity' && status.ready === true,
+ 'installed tarball did not produce a ready Continuity runtime',
+ );
+ runCheck(
+ checks,
+ 'tarball_module',
+ process.execPath,
+ [
+ '--input-type=module',
+ '--eval',
+ "const mod = await import('mancode'); if (Object.keys(mod).length === 0) process.exit(1);",
+ ],
+ installFixture,
+ );
+
+ const originMainAfter = remoteRef(remote, 'refs/heads/main');
+ assert(originMainAfter === originMainBefore, 'origin/main changed during release check');
+ checks.push({ name: 'origin_main_unchanged', status: 'passed' });
+
+ await mkdir(outputDirectory, { recursive: true });
+ const artifactFile = `mancode-${packageMetadata.version}-${candidate.slice(0, 12)}.tgz`;
+ await copyFile(tarballPath, path.join(outputDirectory, artifactFile));
+ const report = {
+ schemaVersion: 1,
+ localReady: true,
+ releaseCandidate: candidate,
+ packageVersion: packageMetadata.version,
+ generatedAt: new Date().toISOString(),
+ source: {
+ branch: 'develop',
+ originDevelop,
+ originMainBefore,
+ originMainAfter,
+ },
+ environment: {
+ nodeVersion: process.version,
+ npmVersion: runCaptured('npm', ['--version'], checkout),
+ platform: process.platform,
+ arch: process.arch,
+ },
+ tarball: {
+ artifactFile,
+ sha256: tarballSha256,
+ npmShasum: packed.shasum,
+ npmIntegrity: packed.integrity,
+ size: packed.size,
+ unpackedSize: packed.unpackedSize,
+ fileCount: packed.entryCount ?? packed.files?.length ?? null,
+ dryRunFileCount: dryRun.entryCount ?? dryRun.files?.length ?? null,
+ },
+ checks,
+ externalGates: {
+ platformSessionEvidence: 'pending',
+ betaGate: 'pending',
+ githubQuality: 'pending',
+ githubWindows: 'pending',
+ },
+ };
+ await writeFile(outputPath, `${JSON.stringify(report, null, 2)}\n`);
+ console.log(`Local release check passed: ${outputPath}`);
+ console.log(`Candidate tarball: ${path.join(outputDirectory, artifactFile)}`);
+ console.log(`SHA-256: ${tarballSha256}`);
+} finally {
+ await rm(temporaryRoot, { recursive: true, force: true });
+}
+
+function runCheck(checks, name, command, args, cwd) {
+ run(command, args, cwd, 'inherit');
+ checks.push({ name, status: 'passed' });
+}
+
+function runCaptured(command, args, cwd) {
+ return run(command, args, cwd, 'pipe').trim();
+}
+
+function run(command, args, cwd, stdio) {
+ const result = spawnSync(command, args, {
+ cwd,
+ encoding: 'utf8',
+ stdio,
+ });
+ if (result.error) throw result.error;
+ if (result.status !== 0) {
+ const detail = [result.stderr, result.stdout].filter(Boolean).join('\n');
+ throw new Error(
+ `${command} ${args.join(' ')} failed (${result.status})${detail ? `:\n${detail}` : ''}`,
+ );
+ }
+ return typeof result.stdout === 'string' ? result.stdout : '';
+}
+
+function remoteRef(remote, ref) {
+ const output = runCaptured('git', ['ls-remote', remote, ref], sourceRoot);
+ const [commit] = output.split(/\s+/u);
+ return normalizeCandidate(commit ?? '');
+}
+
+function parseSinglePackResult(output) {
+ const parsed = JSON.parse(output);
+ assert(
+ Array.isArray(parsed) && parsed.length === 1,
+ 'npm pack returned an unexpected result',
+ );
+ return parsed[0];
+}
+
+function normalizeCandidate(value) {
+ const candidate = String(value).trim();
+ assert(
+ /^[a-f0-9]{40}$/u.test(candidate),
+ 'release candidate must be a full Git commit SHA',
+ );
+ return candidate;
+}
+
+function parseOptions(args) {
+ const parsed = { candidate: null, output: null, help: false };
+ for (let index = 0; index < args.length; index += 1) {
+ const argument = args[index];
+ if (argument === '--help') {
+ parsed.help = true;
+ continue;
+ }
+ if (argument !== '--candidate' && argument !== '--output') {
+ throw new Error(`unknown release-check argument: ${argument}`);
+ }
+ const value = args[index + 1];
+ assert(value !== undefined, `${argument} requires a value`);
+ if (argument === '--candidate') parsed.candidate = value;
+ else parsed.output = value;
+ index += 1;
+ }
+ return parsed;
+}
+
+function assert(condition, message) {
+ if (!condition) throw new Error(message);
+}
diff --git a/scripts/verify-dist-adapters.mjs b/scripts/verify-dist-adapters.mjs
index 7f2caac..157a49f 100644
--- a/scripts/verify-dist-adapters.mjs
+++ b/scripts/verify-dist-adapters.mjs
@@ -19,9 +19,9 @@ try {
runCli(['init', '--empty', '--platform', 'all', '--lang', 'en']);
const generatedFiles = [
- '.claude/skills/mancode-v3/SKILL.md',
+ 'CLAUDE.md',
'.claude/skills/man/SKILL.md',
- '.cursor/rules/mancode-v3.mdc',
+ '.cursor/rules/mancode-continuity.mdc',
'.cursor/commands/man.md',
'AGENTS.md',
'.agents/skills/man/SKILL.md',
@@ -30,7 +30,10 @@ try {
];
for (const relativePath of generatedFiles) {
- const content = await readFile(path.join(projectRoot, relativePath), 'utf8');
+ const content = await readFile(
+ path.join(projectRoot, relativePath),
+ 'utf8',
+ );
assertGeneratedContract(relativePath, content);
}
@@ -79,6 +82,8 @@ function assertGeneratedContract(relativePath, content) {
'mancode status --json',
'authority: "v3"',
'v3_active',
+ 'mancode-v3',
+ 'mancode:v3',
]) {
assert(
!content.includes(forbidden),
diff --git a/scripts/windows-smoke.mjs b/scripts/windows-smoke.mjs
index 259f48e..3f206b6 100644
--- a/scripts/windows-smoke.mjs
+++ b/scripts/windows-smoke.mjs
@@ -36,7 +36,7 @@ try {
);
await readFile(path.join(emptyProject, 'AGENTS.md'), 'utf8');
await readFile(
- path.join(emptyProject, '.cursor', 'rules', 'mancode-v3.mdc'),
+ path.join(emptyProject, '.cursor', 'rules', 'mancode-continuity.mdc'),
'utf8',
);
await readFile(
@@ -215,6 +215,8 @@ async function assertV3SessionEvidenceRenameUnderOpenWindowsHandle() {
'spike',
'--platform',
'codex',
+ '--session-mode',
+ 'host',
'--host-session-source',
'api',
'--command-propagation',
diff --git a/src/cli.ts b/src/cli.ts
index 2f1e0e2..320c2b7 100644
--- a/src/cli.ts
+++ b/src/cli.ts
@@ -1,5 +1,6 @@
#!/usr/bin/env node
import { Option, program } from 'commander';
+import { adapterStatus, adapterUpgrade } from './commands/adapter.js';
import {
contextBeta,
contextClose,
@@ -10,6 +11,7 @@ import {
contextReconcileTaskHead,
contextResume,
contextSessionNew,
+ contextSessionShow,
contextSessionSpike,
contextShow,
contextWorktreeRegister,
@@ -24,6 +26,7 @@ import {
operationRepair,
operationShow,
} from './commands/operation.js';
+import { projectUpgrade } from './commands/project.js';
import { refreshProject } from './commands/refresh-project.js';
import { refreshStyle } from './commands/refresh-style.js';
import { status } from './commands/status.js';
@@ -99,6 +102,10 @@ program
'--shadow',
'Stage a mancode bootstrap candidate without changing live files',
)
+ .option('--confirm', 'Confirm a journaled adapter install or repair')
+ .option('--operation-id ', 'Operation ID returned by adapter dry-run')
+ .option('--session ', 'mancode session ID (otherwise MANCODE_SESSION_ID)')
+ .option('--client ', 'Client identity (default: mancode-cli)')
.action(async (platform, options) => {
const code = await install(
process.cwd(),
@@ -108,6 +115,34 @@ program
process.exitCode = code;
});
+const adapterProgram = program
+ .command('adapter')
+ .description('Inspect and explicitly upgrade managed platform adapters');
+
+adapterProgram
+ .command('status')
+ .description('Inspect managed adapter content on disk')
+ .option('--platform ', 'Inspect one platform adapter')
+ .option('--json', 'Output as JSON (for scripts)')
+ .action(async (options) => {
+ process.exitCode = await adapterStatus(process.cwd(), options);
+ });
+
+adapterProgram
+ .command('upgrade')
+ .description('Preview or repair managed adapter content')
+ .option('--all', 'Upgrade all platform adapters')
+ .option('--platform ', 'Upgrade one platform adapter')
+ .option('--dry-run', 'Stage and report changes without writing live targets')
+ .option('--confirm', 'Confirm the journaled adapter upgrade')
+ .option('--operation-id ', 'Operation ID returned by adapter dry-run')
+ .option('--session ', 'mancode session ID (otherwise MANCODE_SESSION_ID)')
+ .option('--client ', 'Client identity (default: mancode-cli)')
+ .option('--json', 'Output as JSON (for scripts)')
+ .action(async (options) => {
+ process.exitCode = await adapterUpgrade(process.cwd(), options);
+ });
+
program
.command('status')
.description('Show current mancode project status')
@@ -118,6 +153,23 @@ program
process.exitCode = code;
});
+const projectProgram = program
+ .command('project')
+ .description('Manage project-level mancode policy and compatibility');
+
+projectProgram
+ .command('upgrade')
+ .description('Upgrade project governance policy explicitly')
+ .requiredOption('--policy ', 'Target planning policy version (2)')
+ .option('--dry-run', 'Preview the upgrade without writing')
+ .option('--operation-id ', 'Operation ID returned by project dry-run')
+ .option('--session ', 'Session ID (otherwise MANCODE_SESSION_ID)')
+ .option('--client ', 'Client identity (default: mancode-cli)')
+ .option('--json', 'Output as JSON (for scripts)')
+ .action(async (options) => {
+ process.exitCode = await projectUpgrade(process.cwd(), options);
+ });
+
program
.command('list-platforms')
.description('List available and installed mancode platform adapters')
@@ -159,6 +211,7 @@ program
.option('--session ', 'mancode session ID (otherwise MANCODE_SESSION_ID)')
.option('--client ', 'Client identity (default: mancode-cli)')
.option('--expected-revision ', 'Expected task revision for mutations')
+ .option('--checkpoint-id ', 'Checkpoint ID for a requirements reframe')
.option('--child-revision ', 'Expected child task revision for merge')
.option('--summary ', 'Privacy-screened child result summary')
.option('--next-action ', 'Next parent action after a child merge')
@@ -197,7 +250,7 @@ program
)
.option(
'--file ',
- 'Semantic or canonical structured requirements JSON input file',
+ 'Semantic requirements JSON, plan Markdown, or ledger input file',
)
.option('--acceptance ', 'Acceptance criterion id (for example AC-1)')
.option('--method ', 'Verification method: automated or manual')
@@ -250,6 +303,16 @@ contextSessionProgram
process.exitCode = await contextSessionNew(process.cwd(), options);
});
+contextSessionProgram
+ .command('show')
+ .description('Show one explicit session without changing it')
+ .requiredOption('--session ', 'Session ID')
+ .option('--client ', 'Expected client identity')
+ .option('--json', 'Output as JSON (for scripts)')
+ .action(async (options) => {
+ process.exitCode = await contextSessionShow(process.cwd(), options);
+ });
+
contextSessionProgram
.command('spike')
.description('Record real-host session evidence without persisting host keys')
@@ -257,9 +320,10 @@ contextSessionProgram
'--platform ',
'claude-code, codex, cursor, copilot, or zcode',
)
+ .requiredOption('--session-mode ', 'Evidence path: host or explicit')
.requiredOption(
'--host-session-source ',
- 'hook_stdin, environment, or api',
+ 'hook_stdin, environment, api, or none for explicit sessions',
)
.requiredOption(
'--command-propagation ',
diff --git a/src/commands/adapter.ts b/src/commands/adapter.ts
new file mode 100644
index 0000000..c8ebdda
--- /dev/null
+++ b/src/commands/adapter.ts
@@ -0,0 +1,150 @@
+import { assertUlid } from '../context/ids.js';
+import {
+ managedAdapterInventoriesMatch,
+ managedAdapterNames,
+} from '../context/manifest.js';
+import { V3ContextStore } from '../context/store.js';
+import { upgradeV3Adapters } from '../installers/adapter-upgrade.js';
+import {
+ type PlatformName,
+ getPlatformInstaller,
+} from '../installers/registry.js';
+import {
+ V3_ADAPTER_PLATFORMS,
+ inspectV3Adapter,
+ v3AdapterVersionsFromStatuses,
+} from '../installers/v3-adapter.js';
+import {
+ printV3Error,
+ printV3Result,
+ readV3CommandProject,
+ resolveV3CommandSession,
+ v3ErrorCode,
+} from './v3-support.js';
+
+export interface AdapterStatusOptions {
+ platform?: string;
+ json?: boolean;
+}
+
+export interface AdapterUpgradeOptions extends AdapterStatusOptions {
+ all?: boolean;
+ dryRun?: boolean;
+ confirm?: boolean;
+ operationId?: string;
+ session?: string;
+ client?: string;
+}
+
+export async function adapterStatus(
+ rootDir: string,
+ options: AdapterStatusOptions,
+): Promise {
+ try {
+ const platforms = selectStatusPlatforms(options.platform);
+ const project = await new V3ContextStore(rootDir).readProjectSnapshot();
+ const entries = await Promise.all(
+ platforms.map(
+ async (platform) =>
+ [platform, await inspectV3Adapter(rootDir, platform)] as const,
+ ),
+ );
+ const adapters = Object.fromEntries(entries);
+ const readinessPlatforms =
+ options.platform === undefined
+ ? new Set(
+ managedAdapterNames(project.manifest.managedAdapters),
+ )
+ : new Set(platforms);
+ const manifestAdapters = Object.fromEntries(
+ platforms.flatMap((platform) => {
+ const version = project.manifest.managedAdapters[platform];
+ return version === undefined ? [] : [[platform, version]];
+ }),
+ );
+ const actualAdapterVersions = v3AdapterVersionsFromStatuses(
+ entries,
+ readinessPlatforms,
+ );
+ const actualAdaptersReady = entries.every(
+ ([platform, status]) =>
+ actualAdapterVersions[platform] === undefined || status.ready,
+ );
+ return printV3Result(options.json, {
+ schemaVersion: 1,
+ renderer: 'mancode-adapter-digest-v1',
+ ready:
+ actualAdaptersReady &&
+ managedAdapterInventoriesMatch(manifestAdapters, actualAdapterVersions),
+ manifestAdapters,
+ adapters,
+ });
+ } catch (error) {
+ return printV3Error(
+ options.json,
+ v3ErrorCode(error, 'MANCODE_ADAPTER_STATUS_FAILED'),
+ error instanceof Error ? error.message : 'Unable to inspect adapters.',
+ );
+ }
+}
+
+export async function adapterUpgrade(
+ rootDir: string,
+ options: AdapterUpgradeOptions,
+): Promise {
+ try {
+ const platforms = selectUpgradePlatforms(options);
+ if (options.operationId !== undefined) {
+ assertUlid(options.operationId, 'adapter upgrade operationId');
+ }
+ let sessionId: string | undefined;
+ if (options.dryRun !== true && options.confirm === true) {
+ const project = await readV3CommandProject(rootDir);
+ sessionId = (
+ await resolveV3CommandSession(project, {
+ session: options.session,
+ client: options.client,
+ })
+ ).sessionId;
+ }
+ const result = await upgradeV3Adapters({
+ projectRoot: rootDir,
+ platforms,
+ dryRun: options.dryRun,
+ explicitConfirmation: options.confirm,
+ ...(options.operationId === undefined
+ ? {}
+ : { operationId: options.operationId }),
+ ...(sessionId === undefined ? {} : { sessionId }),
+ });
+ return printV3Result(options.json, result);
+ } catch (error) {
+ return printV3Error(
+ options.json,
+ v3ErrorCode(error, 'MANCODE_ADAPTER_UPGRADE_FAILED'),
+ error instanceof Error ? error.message : 'Unable to upgrade adapters.',
+ );
+ }
+}
+
+function selectStatusPlatforms(platform: string | undefined): PlatformName[] {
+ if (platform === undefined) return [...V3_ADAPTER_PLATFORMS];
+ const installer = getPlatformInstaller(platform);
+ if (installer === null) {
+ throw new Error('MANCODE_ADAPTER_UPGRADE_PLATFORM_INVALID');
+ }
+ return [installer.name];
+}
+
+function selectUpgradePlatforms(
+ options: AdapterUpgradeOptions,
+): PlatformName[] {
+ if ((options.all === true) === (options.platform !== undefined)) {
+ throw new Error(
+ 'MANCODE_ADAPTER_UPGRADE_SELECTION_REQUIRED: choose exactly one of --all or --platform.',
+ );
+ }
+ return options.all === true
+ ? [...V3_ADAPTER_PLATFORMS]
+ : selectStatusPlatforms(options.platform);
+}
diff --git a/src/commands/context.ts b/src/commands/context.ts
index 9d64da9..fbfc977 100644
--- a/src/commands/context.ts
+++ b/src/commands/context.ts
@@ -1,4 +1,7 @@
+import { CURRENT_WRITER_CAPABILITIES } from '../context/compatibility.js';
import type { ContextLevel, ContextPurpose } from '../context/context-pack.js';
+import { isUlid } from '../context/ids.js';
+import { managedAdapterNames } from '../context/manifest.js';
import {
previewV3TaskPromotion,
promoteV3Task,
@@ -10,6 +13,7 @@ import {
reconcileV3TaskHead,
} from '../context/task-head-reconcile.js';
import { parseTaskRef } from '../context/task-ref.js';
+import { inspectV3AdapterVersions } from '../installers/v3-adapter.js';
import { evaluateV3BetaGate } from '../runtime/beta-gate.js';
import {
readLocalDiagnostics,
@@ -29,6 +33,7 @@ import {
type HookApprovalStatus,
type HostSessionSource,
SESSION_SPIKE_PLATFORMS,
+ type SessionEvidenceMode,
type SessionSpikePlatform,
type SpikeEvidenceStatus,
createPlatformSessionSpike,
@@ -52,6 +57,7 @@ import {
type SessionStateV1,
closeSession,
createBootstrapSession,
+ readSession,
resumeSession,
} from '../runtime/session.js';
import { readLocalActor } from '../team/actor.js';
@@ -82,8 +88,15 @@ export interface ContextSessionNewOptions {
json?: boolean;
}
+export interface ContextSessionShowOptions {
+ session?: string;
+ client?: string;
+ json?: boolean;
+}
+
export interface ContextSessionSpikeOptions {
platform?: string;
+ sessionMode?: string;
hostSessionSource?: string;
commandPropagation?: string;
subagentInheritance?: string;
@@ -180,6 +193,49 @@ export async function contextSessionNew(
}
}
+/** Implements `mancode context session show --session ...`. */
+export async function contextSessionShow(
+ rootDir: string,
+ options: ContextSessionShowOptions,
+): Promise {
+ if (options.session === undefined) {
+ return printV3Error(
+ options.json,
+ 'MANCODE_SESSION_REQUIRED',
+ 'context session show requires --session .',
+ EXIT_V3_INVALID_ARGUMENT,
+ );
+ }
+ if (!isUlid(options.session)) {
+ return printV3Error(
+ options.json,
+ 'MANCODE_SESSION_INVALID',
+ 'context session show requires a canonical ULID in --session .',
+ EXIT_V3_INVALID_ARGUMENT,
+ );
+ }
+ try {
+ const project = await readV3CommandProject(rootDir);
+ const session = await readSession(project.projectRoot, options.session);
+ if (session === null) {
+ throw new Error('MANCODE_SESSION_NOT_FOUND');
+ }
+ if (
+ options.client !== undefined &&
+ session.client !== commandClient(options.client)
+ ) {
+ throw new Error('MANCODE_SESSION_NOT_FOUND');
+ }
+ return printV3Result(options.json, { schemaVersion: 1, session });
+ } catch (error) {
+ return printV3Error(
+ options.json,
+ v3ErrorCode(error, 'MANCODE_CONTEXT_SESSION_SHOW_FAILED'),
+ error instanceof Error ? error.message : 'Unable to show the session.',
+ );
+ }
+}
+
/**
* Records one real-host session spike without accepting or persisting host
* keys as command-line arguments. Operators supply the two window values via
@@ -191,10 +247,8 @@ export async function contextSessionSpike(
): Promise {
try {
const platform = parseSpikePlatform(options.platform);
+ const sessionMode = parseSessionEvidenceMode(options.sessionMode);
const hostSessionSource = parseHostSessionSource(options.hostSessionSource);
- if (hostSessionSource === 'none') {
- throw new Error('MANCODE_PLATFORM_SPIKE_HOST_SOURCE_REQUIRED');
- }
const releaseCandidate = parseReleaseCandidate(options.releaseCandidate);
const hostVersion = parseHostVersion(options.hostVersion);
const commandPropagation = parseRequiredSpikeEvidenceStatus(
@@ -205,23 +259,35 @@ export async function contextSessionSpike(
options.subagentInheritance,
'subagent inheritance',
);
- const firstWindowHostSessionKey =
- process.env.MANCODE_SPIKE_HOST_SESSION_KEY ?? null;
- const secondWindowHostSessionKey =
- process.env.MANCODE_SPIKE_SECOND_WINDOW_HOST_SESSION_KEY ?? null;
- if (
- firstWindowHostSessionKey === null ||
- secondWindowHostSessionKey === null
- ) {
+ const project = await readV3CommandProject(rootDir);
+ const [firstWindowSessionKey, secondWindowSessionKey] =
+ sessionMode === 'host'
+ ? [
+ process.env.MANCODE_SPIKE_HOST_SESSION_KEY ?? null,
+ process.env.MANCODE_SPIKE_SECOND_WINDOW_HOST_SESSION_KEY ?? null,
+ ]
+ : [
+ process.env.MANCODE_SPIKE_SESSION_ID ?? null,
+ process.env.MANCODE_SPIKE_SECOND_SESSION_ID ?? null,
+ ];
+ if (firstWindowSessionKey === null || secondWindowSessionKey === null) {
throw new Error('MANCODE_PLATFORM_SPIKE_WINDOW_EVIDENCE_REQUIRED');
}
- const project = await readV3CommandProject(rootDir);
+ if (sessionMode === 'explicit') {
+ await assertExplicitSpikeSessions(
+ project.projectRoot,
+ platform,
+ firstWindowSessionKey,
+ secondWindowSessionKey,
+ );
+ }
const spike = createPlatformSessionSpike({
platform,
observedAt: new Date().toISOString(),
+ sessionMode,
hostSessionSource,
- firstWindowHostSessionKey,
- secondWindowHostSessionKey,
+ firstWindowSessionKey,
+ secondWindowSessionKey,
commandPropagation,
subagentInheritance,
subagentInheritanceReason: options.subagentInheritanceReason ?? null,
@@ -261,6 +327,32 @@ export async function contextSessionSpike(
}
}
+async function assertExplicitSpikeSessions(
+ projectRoot: string,
+ platform: SessionSpikePlatform,
+ firstSessionId: string,
+ secondSessionId: string,
+): Promise {
+ if (firstSessionId === secondSessionId) {
+ throw new Error('MANCODE_PLATFORM_SPIKE_SESSION_COLLISION');
+ }
+ const [first, second] = await Promise.all([
+ readSession(projectRoot, firstSessionId),
+ readSession(projectRoot, secondSessionId),
+ ]);
+ if (
+ first === null ||
+ second === null ||
+ first.status !== 'active' ||
+ second.status !== 'active'
+ ) {
+ throw new Error('MANCODE_PLATFORM_SPIKE_SESSION_NOT_FOUND');
+ }
+ if (first.client !== platform || second.client !== platform) {
+ throw new Error('MANCODE_PLATFORM_SPIKE_SESSION_CLIENT_MISMATCH');
+ }
+}
+
/** Reports internal release readiness and exits non-zero until every gate passes. */
export async function contextBeta(
rootDir: string,
@@ -301,7 +393,7 @@ export async function contextResume(
const project = await readV3CommandProject(rootDir);
const session = await resolveV3CommandSession(project, options);
const taskRef = parseTaskRef(task);
- const resolution = await resolveContext(project, session, {
+ let resolution = await resolveContext(project, session, {
taskRef,
level: 'bootstrap',
purpose: 'orient',
@@ -310,15 +402,29 @@ export async function contextResume(
if (resolution.metadata === null || resolution.aggregate === null) {
throw new Error('MANCODE_CONTEXT_WRITE_BLOCKED');
}
- const resumed = await resumeSession(
- project.projectRoot,
- session.sessionId,
- {
+ let resumed = session;
+ let packIsCurrent = false;
+ for (let attempt = 0; attempt < 3; attempt += 1) {
+ resumed = await resumeSession(project.projectRoot, session.sessionId, {
taskRef: resolution.taskRef,
workflowMode: resolution.metadata.workflowMode,
taskRevision: resolution.aggregate.taskRevision,
- },
- );
+ });
+ resolution = await resolveContext(project, resumed, {
+ taskRef,
+ level: 'bootstrap',
+ purpose: 'orient',
+ intent: 'mutate',
+ });
+ if (resolution.metadata === null || resolution.aggregate === null) {
+ throw new Error('MANCODE_CONTEXT_WRITE_BLOCKED');
+ }
+ if (resolution.aggregate.taskRevision === resumed.lastSeenRevision) {
+ packIsCurrent = true;
+ break;
+ }
+ }
+ if (!packIsCurrent) throw new Error('MANCODE_CONTEXT_CHANGED');
return printV3Result(options.json, {
schemaVersion: 1,
session: resumed,
@@ -824,6 +930,10 @@ async function resolveContext(
),
)
: undefined;
+ const adapterVersions = await inspectV3AdapterVersions(
+ project.projectRoot,
+ managedAdapterNames(project.project.manifest.managedAdapters),
+ );
return resolver.resolve({
session,
taskRef: request.taskRef,
@@ -834,7 +944,8 @@ async function resolveContext(
expectedSchemaEpoch: project.project.manifest.epoch,
readerVersion: VERSION,
writerVersion: VERSION,
- adapterVersions: project.project.manifest.managedAdapters,
+ writerCapabilities: CURRENT_WRITER_CAPABILITIES,
+ adapterVersions,
},
codeHead: await readCheckoutCodeHead(project.projectRoot),
...(capabilities === undefined ? {} : { capabilities }),
@@ -874,6 +985,15 @@ function parseSpikePlatform(value: string | undefined): SessionSpikePlatform {
return value as SessionSpikePlatform;
}
+function parseSessionEvidenceMode(
+ value: string | undefined,
+): SessionEvidenceMode {
+ if (value !== 'host' && value !== 'explicit') {
+ throw new Error('MANCODE_PLATFORM_SPIKE_SESSION_MODE_REQUIRED');
+ }
+ return value;
+}
+
function parseHostSessionSource(value: string | undefined): HostSessionSource {
if (
value !== 'hook_stdin' &&
diff --git a/src/commands/init.ts b/src/commands/init.ts
index e609552..9fe6785 100644
--- a/src/commands/init.ts
+++ b/src/commands/init.ts
@@ -15,8 +15,9 @@ import {
getPlatformInstallers,
} from '../installers/registry.js';
import {
+ V3_ADAPTER_VERSION,
assertV3AdapterInstallable,
- installV3Adapter,
+ inspectV3Adapter,
} from '../installers/v3-adapter.js';
import { detectTeamStatus } from '../system/detect-team.js';
import { detectSystemDeps } from '../system/detect.js';
@@ -39,7 +40,7 @@ import { VERSION } from '../version.js';
import { initializeV3Project } from './v3-init.js';
/**
- * 退出码契约 — 见 docs/08-cli-spec.md §2.5
+ * 退出码契约见 docs/workflows.md。
*/
export const EXIT_OK = 0;
export const EXIT_ALREADY_INITIALIZED = 1;
@@ -123,7 +124,7 @@ export function resolveInitAuthority(options: InitOptions): 'legacy' | 'v3' {
/**
* `mancode init` 命令(完整版)。
*
- * 职责(docs/08-cli-spec.md §2.1-2.4):
+ * 职责见 docs/12-lifecycle.md:
* 1. 检测可选系统依赖(git)
* 2. 检测中立 project profile(类型、语言、框架与验证能力)
* 3. 创建 8 个文件/目录(.mancode/ + .claude/)
@@ -698,6 +699,7 @@ async function initializeV3(
}
const schemaPath = path.join(rootDir, '.mancode', 'schema.json');
let existingV3 = false;
+ let registeredAdapters = new Set();
if (await pathExists(schemaPath)) {
try {
const manifest = parseSchemaManifest(
@@ -709,6 +711,9 @@ async function initializeV3(
);
}
existingV3 = true;
+ registeredAdapters = new Set(
+ Object.keys(manifest.managedAdapters) as PlatformName[],
+ );
if (options.platform === undefined) {
console.log('ℹ️ mancode is already initialized.');
return EXIT_ALREADY_INITIALIZED;
@@ -744,29 +749,39 @@ async function initializeV3(
return EXIT_INIT_FAILED;
}
try {
+ if (
+ existingV3 &&
+ selectedPlatforms.some((platform) => !registeredAdapters.has(platform))
+ ) {
+ throw new Error(
+ 'MANCODE_ADAPTER_REGISTRATION_REQUIRED: run `mancode adapter upgrade --platform --dry-run`, then confirm that operation with an active session.',
+ );
+ }
for (const platform of selectedPlatforms) {
await assertV3AdapterInstallable(rootDir, platform);
}
if (existingV3) {
- for (const platform of selectedPlatforms) {
- await installV3Adapter(rootDir, platform);
- }
- console.log('ℹ️ mancode is already initialized.');
- if (selectedPlatforms.length > 0) {
- console.log(
- ` mancode bootstrap repaired: ${selectedPlatforms.join(', ')}`,
+ const statuses = await Promise.all(
+ selectedPlatforms.map((platform) =>
+ inspectV3Adapter(rootDir, platform),
+ ),
+ );
+ if (statuses.some((status) => !status.ready)) {
+ throw new Error(
+ 'MANCODE_ADAPTER_REPAIR_REQUIRED: run `mancode adapter status --json`, then preview and confirm the required platform upgrade with an active session.',
);
}
+ console.log('ℹ️ mancode is already initialized.');
return EXIT_ALREADY_INITIALIZED;
}
const result = await initializeV3Project({
projectRoot: rootDir,
+ managedAdapters: Object.fromEntries(
+ selectedPlatforms.map((platform) => [platform, V3_ADAPTER_VERSION]),
+ ),
teamPolicy:
options.team === undefined ? 'auto' : options.team ? 'on' : 'off',
});
- for (const platform of selectedPlatforms) {
- await installV3Adapter(rootDir, platform);
- }
console.log('✓ Initialized mancode project.');
console.log(` workspace: ${result.runtime.workspaceId}`);
console.log(` operation: ${result.journal.operationId}`);
@@ -777,7 +792,7 @@ async function initializeV3(
}
if (selectedPlatforms.length === 0) {
console.log(
- ' No platform bootstrap selected. Run `mancode install `.',
+ ' No platform bootstrap selected. Run `mancode adapter upgrade --platform --dry-run`, then confirm that operation with an active session.',
);
} else {
console.log(` mancode bootstrap: ${selectedPlatforms.join(', ')}`);
diff --git a/src/commands/install.ts b/src/commands/install.ts
index 01641bb..7278433 100644
--- a/src/commands/install.ts
+++ b/src/commands/install.ts
@@ -1,22 +1,25 @@
import { promises as fs } from 'node:fs';
import path from 'node:path';
import process from 'node:process';
+import { assertUlid } from '../context/ids.js';
import { V3ContextStore } from '../context/store.js';
+import { upgradeV3Adapters } from '../installers/adapter-upgrade.js';
import { checkPlatformStatus } from '../installers/platform-status.js';
import {
formatPlatformName,
getPlatformInstaller,
getPlatformInstallers,
} from '../installers/registry.js';
-import { installV3Adapter, stageV3Adapter } from '../installers/v3-adapter.js';
+import { stageV3Adapter } from '../installers/v3-adapter.js';
import {
detectProjectProfile,
primaryUiLibrary,
} from '../system/project-profile.js';
import { DEFAULT_CONFIG } from '../templates/defaults.js';
+import { readV3CommandProject, resolveV3CommandSession } from './v3-support.js';
/**
- * 退出码契约 — 见 docs/08-cli-spec.md §3
+ * 退出码契约见 docs/workflows.md。
*/
export const EXIT_OK = 0;
export const EXIT_NOT_INITIALIZED = 1;
@@ -35,12 +38,17 @@ export interface InstallOptions {
minimal?: boolean;
/** Render a V3 adapter candidate under staging without changing live files. */
shadow?: boolean;
+ /** Confirm a journaled V3 adapter install or repair. */
+ confirm?: boolean;
+ operationId?: string;
+ session?: string;
+ client?: string;
}
/**
* `mancode install ` 命令。
*
- * 职责(docs/08-cli-spec.md §3 + docs/15-adapters.md §8):
+ * 职责见 docs/workflows.md 和 docs/platform-adapters.md:
* 1. 检查项目已初始化(state.json 存在)
* 2. 验证平台名
* 3. 调用对应适配器安装
@@ -205,11 +213,35 @@ async function installV3(
'ℹ️ mancode adapters are already bootstrap-only; --minimal has no additional effect.',
);
}
- const installed = await installV3Adapter(rootDir, installer.name);
+ let sessionId: string | undefined;
+ if (options.operationId !== undefined) {
+ assertUlid(options.operationId, 'adapter install operationId');
+ }
+ if (options.confirm === true) {
+ const commandProject = await readV3CommandProject(rootDir);
+ sessionId = (
+ await resolveV3CommandSession(commandProject, {
+ session: options.session,
+ client: options.client,
+ })
+ ).sessionId;
+ }
+ const installed = await upgradeV3Adapters({
+ projectRoot: rootDir,
+ platforms: [installer.name],
+ explicitConfirmation: options.confirm,
+ ...(options.operationId === undefined
+ ? {}
+ : { operationId: options.operationId }),
+ ...(sessionId === undefined ? {} : { sessionId }),
+ });
console.log(
- `✓ ${formatPlatformName(platform)} mancode bootstrap installed.`,
+ installed.state === 'already_ready'
+ ? `ℹ️ ${formatPlatformName(platform)} mancode bootstrap is already ready.`
+ : `✓ ${formatPlatformName(platform)} mancode bootstrap installed.`,
);
- console.log(` ${installed.target}`);
+ for (const target of installed.filePlans)
+ console.log(` ${target.target}`);
return EXIT_OK;
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
diff --git a/src/commands/project.ts b/src/commands/project.ts
new file mode 100644
index 0000000..1ee0c5f
--- /dev/null
+++ b/src/commands/project.ts
@@ -0,0 +1,72 @@
+import { assertUlid } from '../context/ids.js';
+import {
+ dryRunProjectPolicyUpgrade,
+ upgradeProjectPolicy,
+} from '../context/project-policy-upgrade.js';
+import {
+ EXIT_V3_INVALID_ARGUMENT,
+ printV3Error,
+ printV3Result,
+ readV3CommandProject,
+ resolveV3CommandSession,
+ v3ErrorCode,
+} from './v3-support.js';
+
+export interface ProjectUpgradeOptions {
+ policy?: string;
+ dryRun?: boolean;
+ session?: string;
+ client?: string;
+ operationId?: string;
+ json?: boolean;
+}
+
+export async function projectUpgrade(
+ rootDir: string,
+ options: ProjectUpgradeOptions,
+): Promise {
+ if (options.policy !== '2') {
+ return printV3Error(
+ options.json,
+ 'MANCODE_POLICY_VERSION_UNSUPPORTED',
+ 'project upgrade currently supports only --policy 2.',
+ EXIT_V3_INVALID_ARGUMENT,
+ );
+ }
+ try {
+ if (options.operationId !== undefined) {
+ assertUlid(options.operationId, 'project upgrade operationId');
+ }
+ if (options.dryRun === true) {
+ return printV3Result(
+ options.json,
+ await dryRunProjectPolicyUpgrade({
+ projectRoot: rootDir,
+ policyVersion: 2,
+ ...(options.operationId === undefined
+ ? {}
+ : { operationId: options.operationId }),
+ }),
+ );
+ }
+ const project = await readV3CommandProject(rootDir);
+ const session = await resolveV3CommandSession(project, options);
+ return printV3Result(
+ options.json,
+ await upgradeProjectPolicy({
+ projectRoot: project.projectRoot,
+ policyVersion: 2,
+ sessionId: session.sessionId,
+ ...(options.operationId === undefined
+ ? {}
+ : { operationId: options.operationId }),
+ }),
+ );
+ } catch (error) {
+ return printV3Error(
+ options.json,
+ v3ErrorCode(error, 'MANCODE_PROJECT_UPGRADE_FAILED'),
+ error instanceof Error ? error.message : 'Project upgrade failed.',
+ );
+ }
+}
diff --git a/src/commands/refresh-style.ts b/src/commands/refresh-style.ts
index 1c3636d..d2b525a 100644
--- a/src/commands/refresh-style.ts
+++ b/src/commands/refresh-style.ts
@@ -13,7 +13,7 @@ import {
import { scanAesthetics } from '../system/scan-aesthetics.js';
/**
- * 退出码契约 — 见 docs/08-cli-spec.md §7
+ * 退出码契约见 docs/workflows.md。
*/
export const EXIT_OK = 0;
export const EXIT_NOT_INITIALIZED = 1;
@@ -22,13 +22,13 @@ export const EXIT_V3_REFRESH_FAILED = 2;
/**
* `mancode refresh-style` 命令。
*
- * 职责(docs/08-cli-spec.md §7 + docs/06-aesthetics.md §9.2):
+ * 职责见 docs/project-intelligence.md:
* 1. 检查项目已初始化
* 2. 重新扫描项目审美 token
* 3. 覆盖 .mancode/aesthetics/style-tokens.json
* 4. 输出扫描结果摘要
*
- * 触发场景(docs/06-aesthetics.md §9.2):
+ * 触发场景见 docs/project-intelligence.md:
* - 用户改了 tailwind.config.js
* - 用户改了 package.json dependencies
* - 用户手动执行
diff --git a/src/commands/requirements-input.ts b/src/commands/requirements-input.ts
index 0552485..18b130c 100644
--- a/src/commands/requirements-input.ts
+++ b/src/commands/requirements-input.ts
@@ -19,6 +19,7 @@ export function normalizeRequirementsInput(
value: unknown,
requestedTaskRef: TaskRef,
now: Date = new Date(),
+ options: { allowIncomplete?: boolean } = {},
): RequirementsLedgerV1 {
if (isRecord(value) && 'schemaVersion' in value) {
return parseRequirementsLedger(value);
@@ -28,7 +29,7 @@ export function normalizeRequirementsInput(
if (serialized === undefined) {
throw new Error('requirements input must be valid JSON');
}
- const semantic = parseSemanticRequirementsLedger(serialized);
+ const semantic = parseSemanticRequirementsLedger(serialized, options);
return buildCanonicalRequirements(semantic, requestedTaskRef, now);
}
diff --git a/src/commands/status.ts b/src/commands/status.ts
index 499a0bb..60939a2 100644
--- a/src/commands/status.ts
+++ b/src/commands/status.ts
@@ -2,9 +2,13 @@ import { spawn } from 'node:child_process';
import { promises as fs } from 'node:fs';
import path from 'node:path';
import process from 'node:process';
-import { evaluateCompatibilityGate } from '../context/compatibility.js';
+import {
+ CURRENT_WRITER_CAPABILITIES,
+ evaluateCompatibilityGate,
+} from '../context/compatibility.js';
import { assertUlid } from '../context/ids.js';
import { scanLegacyAuthority } from '../context/layout.js';
+import { managedAdapterNames } from '../context/manifest.js';
import { V3ContextStore } from '../context/store.js';
import {
type PlatformStatus,
@@ -18,7 +22,9 @@ import {
import {
type V3PlatformAdapterStatus,
inspectV3Adapter,
+ inspectV3AdapterVersions,
} from '../installers/v3-adapter.js';
+import { listUnfinishedOperationRecoveries } from '../runtime/operation-recovery-executor.js';
import { listPlatformSessionSpikes } from '../runtime/platform-spike-store.js';
import {
type SessionSpikePlatform,
@@ -51,7 +57,7 @@ import { VERSION } from '../version.js';
const HOOK_ESTIMATE_TIMEOUT_MS = 2000;
/**
- * 退出码契约 — 见 docs/08-cli-spec.md §4
+ * 退出码契约见 docs/workflows.md。
*/
export const EXIT_OK = 0;
export const EXIT_NOT_INITIALIZED = 1;
@@ -141,7 +147,7 @@ export interface V3StatusResult {
state: string;
epoch: string;
activatedAt: string | null;
- managedAdapters: Record;
+ managedAdapters: Partial>;
};
compatibility: {
readAllowed: boolean;
@@ -201,6 +207,11 @@ export interface V3StatusResult {
explicitRequiredPlatforms: SessionSpikePlatform[];
};
adapters: Record;
+ unfinishedOperations: Array<{
+ operationId: string;
+ type: string;
+ state: string;
+ }>;
legacyAuthorityPresent: boolean;
}
@@ -233,7 +244,7 @@ interface StatusConfig {
/**
* `mancode status` 命令。
*
- * 职责(docs/08-cli-spec.md §4 + progress.md Step 3):
+ * 职责见 docs/workflows.md:
* 1. 读取 .mancode/state.json(未初始化则报错退出)
* 2. 显示项目状态(mode/platform/techStack/uiLibrary)
* 3. 显示初始化时间
@@ -333,8 +344,29 @@ async function statusV3(
): Promise {
try {
const store = new V3ContextStore(rootDir);
+ const snapshot = await store.readProjectSnapshot();
+ let runtime: V3StatusResult['runtime'];
+ try {
+ const context = await readProjectRuntimeContext(rootDir);
+ runtime = {
+ binding: 'ready',
+ workspaceId: context.workspaceId,
+ checkoutId: context.checkoutId,
+ repositoryBindingId: context.repositoryBindingId,
+ gitCommonDir: context.gitCommonDir,
+ error: null,
+ };
+ } catch (error) {
+ runtime = {
+ binding: 'registration_required',
+ workspaceId: snapshot.config.workspaceId,
+ checkoutId: null,
+ repositoryBindingId: null,
+ gitCommonDir: null,
+ error: error instanceof Error ? error.message : String(error),
+ };
+ }
const [
- snapshot,
project,
legacy,
actor,
@@ -342,8 +374,9 @@ async function statusV3(
sessionSpikes,
assessmentSignals,
currentSession,
+ adapterVersions,
+ unfinishedOperations,
] = await Promise.all([
- store.readProjectSnapshot(),
getProjectName(rootDir),
scanLegacyAuthority(rootDir),
readLocalActor(rootDir).catch(() => null),
@@ -358,13 +391,21 @@ async function statusV3(
listPlatformSessionSpikes(rootDir),
detectTeamAssessmentSignals(rootDir),
readV3StatusSession(rootDir),
+ inspectV3AdapterVersions(
+ rootDir,
+ managedAdapterNames(snapshot.manifest.managedAdapters),
+ ),
+ runtime.binding === 'ready'
+ ? listUnfinishedOperationRecoveries(rootDir)
+ : Promise.resolve([]),
]);
const compatibility = evaluateCompatibilityGate({
manifest: snapshot.manifest,
expectedSchemaEpoch: snapshot.manifest.epoch,
readerVersion: VERSION,
writerVersion: VERSION,
- adapterVersions: snapshot.manifest.managedAdapters,
+ writerCapabilities: CURRENT_WRITER_CAPABILITIES,
+ adapterVersions,
currentLegacyBaseline: legacy.baseline,
legacyAuthorityPresent: legacy.authorityPresent,
operation: 'read',
@@ -392,27 +433,6 @@ async function statusV3(
revision: task.metadata.revision,
}))
.catch(() => null);
- let runtime: V3StatusResult['runtime'];
- try {
- const context = await readProjectRuntimeContext(rootDir);
- runtime = {
- binding: 'ready',
- workspaceId: context.workspaceId,
- checkoutId: context.checkoutId,
- repositoryBindingId: context.repositoryBindingId,
- gitCommonDir: context.gitCommonDir,
- error: null,
- };
- } catch (error) {
- runtime = {
- binding: 'registration_required',
- workspaceId: snapshot.config.workspaceId,
- checkoutId: null,
- repositoryBindingId: null,
- gitCommonDir: null,
- error: error instanceof Error ? error.message : String(error),
- };
- }
const result: V3StatusResult = {
schemaVersion: 1,
authority: 'v3',
@@ -465,6 +485,11 @@ async function statusV3(
currentTask,
sessionEvidence: platformSpikeFreezeStatus(sessionSpikes),
adapters,
+ unfinishedOperations: unfinishedOperations.map((operation) => ({
+ operationId: operation.journal.operationId,
+ type: operation.journal.type,
+ state: operation.journal.state,
+ })),
legacyAuthorityPresent: legacy.authorityPresent,
};
const output = options.brief ? toContinuityStatus(result) : result;
@@ -488,12 +513,16 @@ function toContinuityStatus(result: V3StatusResult): ContinuityStatusResult {
const blockers = [
...result.compatibility.failures,
...(result.runtime.error === null ? [] : [result.runtime.error]),
+ ...(result.unfinishedOperations.length === 0
+ ? []
+ : ['MANCODE_OPERATION_REPAIR_REQUIRED']),
];
const ready =
result.activation.state === 'v3_active' &&
result.compatibility.readAllowed &&
result.compatibility.writeAllowed &&
- result.runtime.binding === 'ready';
+ result.runtime.binding === 'ready' &&
+ result.unfinishedOperations.length === 0;
return {
schemaVersion: 1,
runtime: 'mancode-continuity',
@@ -805,7 +834,7 @@ function runHookEstimate(rootDir: string, hookPath: string): Promise {
}
/**
- * 文本格式输出(默认),字段命名对齐 docs/08-cli-spec.md §4.3。
+ * 文本格式输出(默认),字段命名对齐 docs/workflows.md。
*/
function printText(r: StatusResult): void {
const publicMode = r.mode === 'mamba' ? 'manba' : r.mode;
@@ -931,6 +960,11 @@ function printV3Text(result: V3StatusResult): void {
if (result.compatibility.failures.length > 0) {
console.log(`Compatibility: ${result.compatibility.failures.join(', ')}`);
}
+ if (result.unfinishedOperations.length > 0) {
+ console.log(
+ `Recovery: required (${result.unfinishedOperations.map((operation) => operation.operationId).join(', ')})`,
+ );
+ }
if (result.legacyAuthorityPresent) {
console.log(
'Legacy: legacy authority detected; mancode writes may be blocked.',
diff --git a/src/commands/v3-init.ts b/src/commands/v3-init.ts
index c9be17a..de18fc2 100644
--- a/src/commands/v3-init.ts
+++ b/src/commands/v3-init.ts
@@ -3,9 +3,8 @@ import {
initializeGreenfield,
} from '../context/greenfield-init.js';
import { type Ulid, createUlid } from '../context/ids.js';
-import type { ManagedAdapter } from '../context/manifest.js';
+import type { ManagedAdapterInventory } from '../context/manifest.js';
import { createProjectFacts } from '../context/project-facts.js';
-import { V3_ADAPTER_VERSION } from '../installers/v3-adapter.js';
import {
type ProjectRuntimeContext,
ensureProjectRuntimeContext,
@@ -23,7 +22,7 @@ export interface InitializeV3ProjectInput {
operationId?: Ulid;
workspaceId?: Ulid;
schemaEpoch?: Ulid;
- managedAdapters?: Record;
+ managedAdapters?: ManagedAdapterInventory;
/** Explicit init preference; omitted keeps V3's evidence-based auto mode. */
teamPolicy?: TeamRecommendationPolicy;
now?: Date;
@@ -84,7 +83,7 @@ export async function initializeV3Project(
schemaEpoch,
minReaderVersion: VERSION,
minWriterVersion: VERSION,
- managedAdapters: input.managedAdapters ?? defaultManagedAdapters(),
+ managedAdapters: input.managedAdapters ?? {},
projectConfig,
teamPolicy,
projectFacts,
@@ -102,13 +101,3 @@ export async function initializeV3Project(
}
return { journal, runtime };
}
-
-function defaultManagedAdapters(): Record {
- return {
- 'claude-code': V3_ADAPTER_VERSION,
- codex: V3_ADAPTER_VERSION,
- cursor: V3_ADAPTER_VERSION,
- copilot: V3_ADAPTER_VERSION,
- zcode: V3_ADAPTER_VERSION,
- };
-}
diff --git a/src/commands/version.ts b/src/commands/version.ts
index 9873949..66856d1 100644
--- a/src/commands/version.ts
+++ b/src/commands/version.ts
@@ -4,7 +4,7 @@ import { VERSION } from '../version.js';
/**
* `mancode version` 命令。
*
- * 输出格式(docs/08-cli-spec.md §5.3):
+ * 输出格式见 docs/workflows.md:
* ```
* mancode/0.1.0
* node/20.10.0
diff --git a/src/commands/workflow.ts b/src/commands/workflow.ts
index cc8c133..092156e 100644
--- a/src/commands/workflow.ts
+++ b/src/commands/workflow.ts
@@ -8,7 +8,11 @@ import {
previewV3TaskPromotion,
promoteV3Task,
} from '../context/publish-promote.js';
-import { finalizeV3Requirements } from '../context/requirements-finalize.js';
+import { reframeV3Workflow } from '../context/reframe.js';
+import {
+ finalizeV3Requirements,
+ saveV3RequirementsDraft,
+} from '../context/requirements-finalize.js';
import { applyV3ReviewLedger } from '../context/review-remediation.js';
import { changeV3WorkflowScope } from '../context/scope-change.js';
import {
@@ -16,11 +20,26 @@ import {
startV3SoloHandoff,
} from '../context/solo-handoff.js';
import { completeV3Task } from '../context/task-complete.js';
-import { formatTaskRef, parseTaskRef } from '../context/task-ref.js';
+import {
+ formatTaskRef,
+ parseTaskRef,
+ sameTaskRef,
+} from '../context/task-ref.js';
import { recordV3Verification } from '../context/verification-record.js';
import { createV3Workflow } from '../context/workflow-create.js';
import type { WorkflowMetadataV3 } from '../context/workflow-metadata.js';
import { updateV3Workflow } from '../context/workflow-update.js';
+import { resolveTaskEntityHomeStore } from '../runtime/entity-home-store.js';
+import {
+ type TaskArchiveRecoveryAction,
+ taskArchiveManifest,
+} from '../runtime/operation-recovery-payload.js';
+import { readOperationRecoveryPayload } from '../runtime/operation-recovery-store.js';
+import { readProjectRuntimeContext } from '../runtime/project-runtime.js';
+import {
+ readTaskArchiveDigestAtRoot,
+ readTaskCheckpointAtRoot,
+} from '../runtime/task-operation.js';
import { detectTeamAssessmentSignals } from '../system/detect-team.js';
import {
parseRequirementsLedger,
@@ -129,6 +148,7 @@ export interface WorkflowOptions {
exitCode?: string;
evidenceFile?: string;
reason?: string;
+ checkpointId?: string;
}
interface WorkflowView extends WorkflowMeta {
@@ -250,6 +270,12 @@ async function workflowV3(
if (subcommand === 'scope') {
return workflowScopeChangeV3(rootDir, args, options);
}
+ if (subcommand === 'reframe') {
+ return workflowReframeV3(rootDir, args, options);
+ }
+ if (subcommand === 'archive' || subcommand === 'checkpoint') {
+ return workflowArtifactShowV3(rootDir, subcommand, args, options);
+ }
if (subcommand === 'child') {
return workflowChildResultMergeV3(rootDir, args, options);
}
@@ -380,6 +406,80 @@ async function workflowShowV3(
}
}
+async function workflowArtifactShowV3(
+ rootDir: string,
+ kind: 'archive' | 'checkpoint',
+ args: string[],
+ options: WorkflowOptions,
+): Promise {
+ const [task, action, artifactId] = args;
+ if (!task || action !== 'show' || !artifactId || args.length !== 3) {
+ return printV3Error(
+ options.json,
+ `MANCODE_${kind.toUpperCase()}_SHOW_ARGUMENT_INVALID`,
+ `Use: workflow ${kind} show <${kind}Id> [--json].`,
+ EXIT_INVALID_ARG,
+ );
+ }
+ try {
+ assertUlid(artifactId, `${kind}Id`);
+ const project = await readV3CommandProject(rootDir);
+ const taskRef = parseTaskRef(task);
+ const snapshot = await project.store.readTaskSnapshot(taskRef);
+ if (kind === 'checkpoint') {
+ const checkpoint = await readTaskCheckpointAtRoot(
+ snapshot.location.taskRoot,
+ artifactId,
+ );
+ if (checkpoint === null || !sameTaskRef(checkpoint.taskRef, taskRef)) {
+ throw new Error('MANCODE_CHECKPOINT_NOT_FOUND');
+ }
+ return printV3Result(options.json, {
+ schemaVersion: 1,
+ taskRef,
+ checkpoint,
+ });
+ }
+
+ const runtime = await readProjectRuntimeContext(project.projectRoot);
+ const homeStore = resolveTaskEntityHomeStore(
+ runtime.entityHomeStoreContext,
+ taskRef,
+ );
+ const payload = await readOperationRecoveryPayload(homeStore, artifactId);
+ const archive = payload?.actions.find(
+ (candidate): candidate is TaskArchiveRecoveryAction =>
+ candidate.kind === 'task_archive' &&
+ candidate.archiveId === artifactId &&
+ sameTaskRef(candidate.taskRef, taskRef),
+ );
+ if (
+ archive === undefined ||
+ (await readTaskArchiveDigestAtRoot(
+ snapshot.location.taskRoot,
+ archive,
+ )) === null
+ ) {
+ throw new Error('MANCODE_REFRAME_ARCHIVE_NOT_FOUND');
+ }
+ return printV3Result(options.json, {
+ schemaVersion: 1,
+ taskRef,
+ archive: taskArchiveManifest(archive),
+ requirements: JSON.parse(archive.requirementsContent),
+ plan: archive.planContent,
+ });
+ } catch (error) {
+ return printV3Error(
+ options.json,
+ v3ErrorCode(error, `MANCODE_V3_${kind.toUpperCase()}_SHOW_FAILED`),
+ error instanceof Error
+ ? error.message
+ : `Unable to show the mancode ${kind}.`,
+ );
+ }
+}
+
function workflowCleanV3(options: WorkflowOptions): number {
return printV3Error(
options.json,
@@ -846,6 +946,75 @@ async function workflowScopeChangeV3(
}
}
+async function workflowReframeV3(
+ rootDir: string,
+ args: string[],
+ options: WorkflowOptions,
+): Promise {
+ const task = args[0];
+ if (!task || args.length !== 1) {
+ return printV3Error(
+ options.json,
+ 'MANCODE_REFRAME_ARGUMENT_INVALID',
+ 'Use: workflow reframe --expected-revision --checkpoint-id .',
+ EXIT_INVALID_ARG,
+ );
+ }
+ const expectedTaskRevision = parseExpectedTaskRevision(options);
+ if (expectedTaskRevision === null) {
+ return printV3Error(
+ options.json,
+ 'MANCODE_EXPECTED_REVISION_REQUIRED',
+ 'Workflow reframe requires --expected-revision .',
+ EXIT_INVALID_ARG,
+ );
+ }
+ if (!options.checkpointId) {
+ return printV3Error(
+ options.json,
+ 'MANCODE_REFRAME_CHECKPOINT_REQUIRED',
+ 'Workflow reframe requires --checkpoint-id .',
+ EXIT_INVALID_ARG,
+ );
+ }
+ try {
+ const project = await readV3CommandProject(rootDir);
+ const session = await resolveV3CommandSession(project, options);
+ assertUlid(options.checkpointId, 'checkpointId');
+ const result = await reframeV3Workflow({
+ projectRoot: project.projectRoot,
+ taskRef: parseTaskRef(task),
+ sessionId: session.sessionId,
+ expectedTaskRevision,
+ checkpointId: options.checkpointId,
+ summary: options.summary,
+ nextAction: options.nextAction,
+ });
+ return printV3Result(options.json, {
+ schemaVersion: 1,
+ taskRef: result.metadata.taskRef,
+ metadata: result.metadata,
+ requirements: result.requirements,
+ review: result.review,
+ verification: result.verification,
+ checkpoint: result.checkpoint,
+ releasedClaims: result.releasedClaims,
+ archive: result.archive,
+ aggregate: result.aggregate,
+ taskHeadFence: result.taskHeadFence,
+ operation: result.operation,
+ });
+ } catch (error) {
+ return printV3Error(
+ options.json,
+ v3ErrorCode(error, 'MANCODE_V3_REFRAME_FAILED'),
+ error instanceof Error
+ ? error.message
+ : 'Unable to reframe the mancode workflow.',
+ );
+ }
+}
+
async function workflowCompleteV3(
rootDir: string,
args: string[],
@@ -1135,11 +1304,11 @@ async function workflowRequirementsV3(
): Promise {
const task = args[0];
const action = args[1];
- if (!task || action !== 'finalize' || !options.file) {
+ if (!task || (action !== 'draft' && action !== 'finalize') || !options.file) {
return printV3Error(
options.json,
'MANCODE_REQUIREMENTS_ARGUMENT_INVALID',
- 'Use: workflow requirements finalize --expected-revision --file .',
+ 'Use: workflow requirements --expected-revision --file .',
EXIT_INVALID_ARG,
);
}
@@ -1148,7 +1317,7 @@ async function workflowRequirementsV3(
return printV3Error(
options.json,
'MANCODE_EXPECTED_REVISION_REQUIRED',
- 'Requirements finalization requires --expected-revision .',
+ 'Requirements draft/finalization requires --expected-revision .',
EXIT_INVALID_ARG,
);
}
@@ -1160,8 +1329,15 @@ async function workflowRequirementsV3(
project.projectRoot,
options.file,
);
- const requirements = normalizeRequirementsInput(requirementsInput, taskRef);
- const result = await finalizeV3Requirements({
+ const requirements = normalizeRequirementsInput(
+ requirementsInput,
+ taskRef,
+ new Date(),
+ { allowIncomplete: action === 'draft' },
+ );
+ const writeRequirements =
+ action === 'draft' ? saveV3RequirementsDraft : finalizeV3Requirements;
+ const result = await writeRequirements({
projectRoot: project.projectRoot,
taskRef,
sessionId: session.sessionId,
@@ -1185,7 +1361,7 @@ async function workflowRequirementsV3(
v3ErrorCode(error, 'MANCODE_V3_REQUIREMENTS_FINALIZE_FAILED'),
error instanceof Error
? error.message
- : 'Unable to finalize mancode requirements.',
+ : `Unable to ${action === 'draft' ? 'save the mancode requirements draft' : 'finalize mancode requirements'}.`,
);
}
}
diff --git a/src/context/compatibility.ts b/src/context/compatibility.ts
index 4140b4d..9c95098 100644
--- a/src/context/compatibility.ts
+++ b/src/context/compatibility.ts
@@ -1,20 +1,39 @@
import type {
LegacyBaseline,
ManagedAdapter,
- SchemaManifestV1,
+ SchemaManifest,
} from './manifest.js';
+import { managedAdapterInventoriesMatch } from './manifest.js';
export type CompatibilityOperation =
| 'read'
| 'v3_business_write'
+ | 'reframe'
| 'migration_stage'
| 'activation_repair'
- | 'greenfield_initialize';
+ | 'greenfield_initialize'
+ | 'adapter_upgrade'
+ | 'project_policy_upgrade';
+
+export type WriterCapability =
+ | 'planning-policy:1'
+ | 'planning-policy:2'
+ | 'adapter-digest:1'
+ | 'reframe-local:1';
+
+export const CURRENT_WRITER_CAPABILITIES: readonly WriterCapability[] = [
+ 'planning-policy:1',
+ 'planning-policy:2',
+ 'adapter-digest:1',
+ 'reframe-local:1',
+];
export type CompatibilityFailureCode =
| 'MANCODE_SCHEMA_EPOCH_MISMATCH'
| 'MANCODE_READER_VERSION_TOO_OLD'
| 'MANCODE_WRITER_VERSION_TOO_OLD'
+ | 'MANCODE_WRITER_CAPABILITY_MISSING'
+ | 'MANCODE_ADAPTER_CONTENT_STALE'
| 'MANCODE_ADAPTER_VERSION_MISMATCH'
| 'MANCODE_LEGACY_BASELINE_CHANGED'
| 'MANCODE_LEGACY_AUTHORITY_PRESENT'
@@ -23,16 +42,27 @@ export type CompatibilityFailureCode =
| 'MANCODE_REPAIR_REQUIRED';
export interface CompatibilityGateInput {
- manifest: SchemaManifestV1;
+ manifest: SchemaManifest;
expectedSchemaEpoch: string;
readerVersion: string;
writerVersion: string;
+ writerCapabilities: readonly WriterCapability[];
adapterVersions: Partial>;
currentLegacyBaseline: LegacyBaseline | null;
legacyAuthorityPresent: boolean;
operation: CompatibilityOperation;
}
+export class CompatibilityGateError extends Error {
+ constructor(
+ readonly code: CompatibilityFailureCode,
+ readonly details: Record,
+ ) {
+ super(`${code}: ${JSON.stringify(details)}`);
+ this.name = 'CompatibilityGateError';
+ }
+}
+
export interface CompatibilityGateResult {
readAllowed: boolean;
writeAllowed: boolean;
@@ -60,8 +90,12 @@ export function evaluateCompatibilityGate(
) {
failures.push('MANCODE_WRITER_VERSION_TOO_OLD');
}
- if (!adaptersMatch(input.manifest, input.adapterVersions)) {
- failures.push('MANCODE_ADAPTER_VERSION_MISMATCH');
+ if (missingWriterCapabilities(input).length > 0) {
+ failures.push('MANCODE_WRITER_CAPABILITY_MISSING');
+ }
+ const adapterFailure = adapterFailureFor(input);
+ if (adapterFailure !== null) {
+ failures.push(adapterFailure);
}
const baselineFailure = baselineFailureFor(input);
if (baselineFailure !== null) failures.push(baselineFailure);
@@ -79,7 +113,12 @@ export function assertCompatibilityGate(input: CompatibilityGateInput): void {
const allowed =
input.operation === 'read' ? result.readAllowed : result.writeAllowed;
if (!allowed) {
- throw new Error(result.failures[0] ?? 'MANCODE_COMPATIBILITY_BLOCKED');
+ const code = result.failures[0];
+ if (code === undefined) throw new Error('MANCODE_COMPATIBILITY_BLOCKED');
+ throw new CompatibilityGateError(
+ code,
+ compatibilityFailureDetails(input, code),
+ );
}
}
@@ -157,15 +196,105 @@ function stateFailureFor(
}
}
-function adaptersMatch(
- manifest: SchemaManifestV1,
- actual: Partial>,
-): boolean {
- return (Object.keys(manifest.managedAdapters) as ManagedAdapter[]).every(
- (adapter) => actual[adapter] === manifest.managedAdapters[adapter],
+function adapterFailureFor(
+ input: CompatibilityGateInput,
+): CompatibilityFailureCode | null {
+ if (input.operation === 'adapter_upgrade') return null;
+ const entries = Object.entries(input.adapterVersions) as Array<
+ [ManagedAdapter, string]
+ >;
+ if (
+ entries.some(
+ ([, version]) =>
+ version === 'missing' ||
+ version === 'stale' ||
+ version === 'unreadable',
+ )
+ ) {
+ return 'MANCODE_ADAPTER_CONTENT_STALE';
+ }
+ return managedAdapterInventoriesMatch(
+ input.manifest.managedAdapters,
+ input.adapterVersions,
+ )
+ ? null
+ : 'MANCODE_ADAPTER_VERSION_MISMATCH';
+}
+
+function requiredWriterCapabilities(
+ input: CompatibilityGateInput,
+): readonly WriterCapability[] {
+ if (input.operation === 'read') return [];
+ if (input.operation === 'adapter_upgrade') {
+ return ['planning-policy:1', 'adapter-digest:1'];
+ }
+ if (input.operation === 'reframe') {
+ return [
+ 'planning-policy:1',
+ ...(input.manifest.manifestVersion === 2
+ ? (['planning-policy:2'] as const)
+ : []),
+ 'adapter-digest:1',
+ 'reframe-local:1',
+ ];
+ }
+ if (
+ input.operation === 'project_policy_upgrade' ||
+ input.manifest.manifestVersion === 2
+ ) {
+ return ['planning-policy:1', 'planning-policy:2', 'adapter-digest:1'];
+ }
+ return ['planning-policy:1'];
+}
+
+function missingWriterCapabilities(
+ input: CompatibilityGateInput,
+): WriterCapability[] {
+ const declared = new Set(input.writerCapabilities);
+ return requiredWriterCapabilities(input).filter(
+ (capability) => !declared.has(capability),
);
}
+function compatibilityFailureDetails(
+ input: CompatibilityGateInput,
+ code: CompatibilityFailureCode,
+): Record {
+ switch (code) {
+ case 'MANCODE_WRITER_VERSION_TOO_OLD':
+ return {
+ observedVersion: input.writerVersion,
+ requiredWriter: input.manifest.minWriterVersion,
+ };
+ case 'MANCODE_WRITER_CAPABILITY_MISSING':
+ return {
+ missingCapabilities: missingWriterCapabilities(input),
+ declaredCapabilities: [...input.writerCapabilities],
+ requiredWriter: input.manifest.minWriterVersion,
+ };
+ case 'MANCODE_ADAPTER_CONTENT_STALE': {
+ const adapters = Object.fromEntries(
+ Object.entries(input.adapterVersions).filter(
+ ([, version]) =>
+ version === 'missing' ||
+ version === 'stale' ||
+ version === 'unreadable',
+ ),
+ );
+ return {
+ adapters,
+ repair: Object.keys(adapters).map((platform) => ({
+ platform,
+ previewCommand: `mancode adapter upgrade --platform ${platform} --dry-run`,
+ confirmCommand: `mancode adapter upgrade --platform ${platform} --confirm --operation-id --session