# Install Manifest 治理标准

本文定义 Codex Harness 安装包 manifest 的治理边界。目标是让安装器、doctor 和项目锁对同一份安装清单形成一致理解，避免安装输出继续依赖脚本内硬编码列表。

## 适用文件

| 文件 | 职责 |
| --- | --- |
| `install-manifest.json` | 当前包的安装模式、安装条目和目标位置声明 |
| `install-manifest.schema.json` | manifest 静态字段、路径形状和安装模式全集约束 |
| `.codex-harness/state/config.json` | 目标项目锁定的 package 版本、packageRoot、installScope 和 vendor 状态 |
| `.codex-harness/locks/package.lock.json` | 目标项目安装后的包名、lockVersion、installMode 和 vendor 状态 |
| `.codex-harness/manifests/install-manifest.json` | 安装时 manifest 快照，用于审计当次输出，不作为用户可编辑真源 |
| `tools/harness/doctor.ps1` | 对目标项目中 manifest、lock、Spec 包和 runtime 文件做只读检查 |

本地参考资料不属于安装包治理面，默认不进入 `install-manifest.json`、目标项目或运行时 truth source；需要沉淀时，应先转写为 `docs/harness/` 或当前项目自己的正式文档。

## 安装模式

| 模式 | scope | 输出位置 | 用途 |
| --- | --- | --- | --- |
| `project` | `project` | 目标项目根、`.agents/`、`.codex/`、`.codex-harness/`、`docs/ai/`、`docs/architecture/`、`docs/spec/`、`docs/testing/`、`docs/harness/`、`docs/knowledge/`、`docs/requirement-prep-kit/`、`tools/` | 新默认项目入口，初始化项目 runtime、active surface 和 truth sources |
| `full` | `project` + `vendor` + `user` | 目标项目根、`.agents/`、用户目录 | 显式 legacy full 入口，安装完整项目 runtime 和 vendor 包 |
| `vendor` | `vendor` | 目标项目 `.agents/` | 离线或强隔离场景复制完整包资产 |
| `user` | `user` | `%USERPROFILE%/.codex/packages/codex-harness/` | 只安装共享 package source 和用户级 package lock，不暴露全局 active surface |

manifest 必须声明且只声明以上 4 个模式。`full` 是组合模式，不作为 entry 的 `scope`；entry 的 `scope` 只能是 `project`、`vendor` 或 `user`。

新分层入口使用 `-Scope user|project|vendor`。`-Mode full|user|project|vendor` 继续保留为兼容入口；同一次调用不能同时传 `-Scope` 和 `-Mode`。其中 `-Scope project` 与无参数 `tools\\install/install-agent.ps1 -ProjectRoot .` 等价，都会安装完整 project scope 输出；`-Mode full` 则保留为显式 legacy full 入口。

`-Scope project` 现在按 manifest 安装完整 project scope 输出：保留项目级 `.agents/skills/`、`.agents/workflows/`，并复制 `docs/testing/`、`docs/harness/`、`docs/knowledge/`、`docs/requirement-prep-kit/` 等项目级模板；仍不默认复制完整 vendor 镜像、`.agents/` package root 文档和 installer 包说明。这些 vendor/package 层内容仍由 `-Mode full`、`-Mode vendor` 或显式 `-Scope vendor` 提供。

`-MigratePlan` 和 `-MigrateApply` 不属于 install-manifest 的普通安装模式集合：前者只读分类，后者是确认式迁移执行入口。`-MigrateApply` 的默认行为不是写完整包，也不是默认 full 切换；它只能在 `-Confirm` 存在时，按 `-MigratePlan` 的分类结果更新 `.codex-harness/locks/package.lock.json` 和必要 `.codex-harness` 元数据。

## Entry 规则

每个 `entries[]` 条目必须满足：

- `source` 是包内相对路径，只使用 `/`，不能是 rooted path，不能包含 `..`。
- `destination` 对 `project` 和 `vendor` 必须是项目内相对路径，只使用 `/`，不能写入 `%USERPROFILE%`。
- `destination` 对 `user` 必须以 `%USERPROFILE%/` 开头，只使用 `/`，不能包含 `..`。
- 同一 `scope` 下不能出现重复 `destination`。
- `source` 指向目录时，`source` 和 `destination` 都必须以 `/` 结尾。
- `source` 指向文件时，`source` 和 `destination` 都不能以 `/` 结尾。
- `kind` 使用小写 kebab-case，用于后续 drift check、升级报告和卸载策略分组。

## Doctor Gate

`tools/harness/doctor.ps1` 对 manifest 做只读 gate：

- 缺失或非法 JSON 是 error。
- 缺少 schema 是 warning，schema JSON 非法是 error。
- install mode 重复、缺失、未知是 error。
- entry scope/path/source/destination 不符合规则是 error。
- 必要安装覆盖缺失是 warning，用于提示包面不完整但不阻断已有项目运行。

doctor 不会修改目标项目，也不会自动删除 legacy `.agents/` 内容。需要迁移时应由独立迁移命令生成报告，并在用户确认后处理。

`installScope=project` 项目会额外执行 project scope gate：`.codex-harness/state/config.json`、`.codex-harness/locks/package.lock.json`、`docs/ai/`、`docs/architecture/constraints.md`、`docs/spec/01..14` 以及 manifest 声明的 project scope 文件缺失会作为错误；`.codex-harness/manifests/install-manifest.json` 缺失是 warning，snapshot JSON 非法或缺少 `package/version/installModes/entries` 是 error。`docs/harness/`、`docs/testing/`、`docs/knowledge/`、`docs/requirement-prep-kit/` 不再被当作 legacy residue。

在 Phase 7 切换门禁中，doctor 还必须输出 readiness gate 汇总，用于区分可直接切换和需要人工处理的项目形态：

- `Readiness gate: PASS|WARN|FAIL`
- `ProjectShape: thin-project|thin-project-missing-user-package|thin-project-with-legacy-residue|legacy-full-project|vendor-project|not-installed`
- `ReadinessChecks: read-only`
- `FixCommand: <command>`

readiness gate 只能读取项目文件。它不得自动执行安装、迁移、删除、覆盖或 lock 写入。`thin-project` 没有阻断项时输出 `FixCommand: none`；缺少用户级 package 时必须给出 `-Scope user`；legacy full 或 legacy residue 必须给出 `-MigratePlan`，并在 legacy full 中同时提示确认式 `-MigrateApply -Confirm`；vendor 项目必须继续使用 vendor lock/drift 检查，并给出 `-Scope vendor` 或重新运行 doctor 的命令；未安装项目必须给出 `-Scope project`。

## Package Lock Drift

项目初始化后，`.codex-harness/locks/package.lock.json` 必须记录当前安装模式和不可变文件快照：

```json
{
  "lockVersion": "0.1",
  "package": "codex-harness",
  "version": "0.1.0",
  "sourceCommit": "<git-sha>",
  "installMode": "project",
  "vendor": false,
  "managedFiles": [
    {
      "path": "tools/harness/codex-loop.ps1",
      "source": "templates/runtime/codex-loop.ps1",
      "scope": "project",
      "kind": "runtime-script",
      "mutable": false,
      "sha256": "<installed-file-sha256>"
    }
  ]
}
```

lock 会对 `mutable=false` 条目写入 hash。文件条目按目标文件记录；目录条目按目录内实际安装文件展开为逐文件记录，例如 `.agents/workflows/speckit.specify.md`。`generated-lock` 和 `.codex-harness/locks/package.lock.json` 自身不写入 `managedFiles`，避免自引用 hash。

doctor 对 `managedFiles` 执行只读检查：

- `path` 必须是项目内相对路径，不能是 rooted path，不能包含 `..`。
- `sha256` 不能为空。
- 文件缺失是 error。
- 文件内容 hash 和 lock 不一致是 error。
- 旧 lock 缺少 `managedFiles` 是 warning，用于兼容已安装项目。

`.codex-harness/state/config.json` 记录项目当前绑定的 package：

```json
{
  "package": "codex-harness",
  "version": "0.1.0",
  "packageRoot": "%USERPROFILE%\\.codex\\packages\\codex-harness\\0.1.0",
  "installScope": "project",
  "vendor": false
}
```

`installScope=project` 时，项目默认不要求存在 `.agents/`；`installScope=vendor` 时，`packageRoot` 指向项目内 `.agents/`。`.codex-harness/manifests/install-manifest.json` 是安装时快照，来源于 package 的 `install-manifest.json`，用于审计和排查，不作为后续编辑入口。snapshot 缺失会提示 warning，JSON 非法或缺少 `package/version/installModes/entries` 会作为 doctor error。

迁移计划与执行边界：

- `-MigratePlan` 仍然只读，不能写 lock、不能删文件、不能把 legacy docs 归类为可删除内容。
- `-MigrateApply` 只能消费迁移分类报告，不能跳过 `blocked-user-drift`、`unknown-legacy-file`、`manual-review` 直接写入。
- `missing-vendor-file` 只保留缺失记录，不得写入 `managedFiles`。
- `adopt-vendor-managed` 只有在显式 `-Confirm` 后才允许写入 lock / metadata。

## 升级策略

安装器提供升级计划和安全执行入口：

```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\\install/install-agent.ps1 -ProjectRoot . -UpgradePlan
powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\\install/install-agent.ps1 -ProjectRoot . -Upgrade
```

`-UpgradePlan` 只读取当前包 manifest 和目标项目 `.codex-harness/locks/package.lock.json`，不会写入、覆盖或删除文件。计划输出会按 entry 标记：

- `keep`：不可变文件和当前包一致。
- `update-immutable`：不可变文件未被用户改动，可由后续升级执行命令覆盖。
- `blocked-user-drift`：目标文件已偏离 lock，需要人工确认。
- `manual-review`：`mutable=true` 文件，只提示不覆盖。
- `install-missing` / `no-lock-entry`：目标缺失或旧 lock 信息不足。

`-Upgrade` 会按以下顺序执行：

1. 读取目标项目 `.codex-harness/locks/package.lock.json`。
2. 读取当前包 `install-manifest.json` 和 schema。
3. 对比 lock 中的 `installMode`、`vendor` 和当前 manifest entries。
4. 如发现任意 `blocked-user-drift`，停止执行并返回非 0。
5. 只覆盖 `mutable=false` 且未发生用户改动的 `update-immutable` / `install-missing` 文件。
6. 跳过 `mutable=true`、`manual-review` 和 `no-lock-entry` 项。
7. 刷新 `.codex-harness/locks/package.lock.json` 中的逐文件 hash。

执行命令仍保持保守：旧 lock 缺少逐文件记录时不会猜测覆盖，必须先人工确认或重新安装形成新的 lock。

## 升级操作手册

对已安装项目升级时，先在目标项目中运行只读计划：

```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\\install/install-agent.ps1 -ProjectRoot . -UpgradePlan
```

按计划输出处理：

- 只有 `keep` 时，不需要执行升级。
- 出现 `update-immutable` 或 `install-missing`，且没有 `blocked-user-drift` 时，可以运行 `-Upgrade`。
- 出现 `manual-review` 时，说明该文件是用户可编辑面，安装器不会覆盖；需要人工判断是否参考新包内容合并。
- 出现 `no-lock-entry` 时，说明旧 lock 信息不足；安装器不会猜测覆盖，应先人工确认来源，必要时重新安装以生成新 lock。
- 出现 `blocked-user-drift` 时，说明目标文件已被用户或本地流程改动。先对比目标文件、lock 记录和当前包内容，决定保留用户改动、手工合并，或回退该文件后再升级。

只有计划中没有 `blocked-user-drift` 时才执行：

```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\\install/install-agent.ps1 -ProjectRoot . -Upgrade
```

`-Upgrade` 成功后会刷新 `.codex-harness/locks/package.lock.json`。升级后建议立即运行 `tools/harness/doctor.ps1` 验证 drift 状态。

## 卸载策略

安装器提供卸载计划和保守执行入口：

```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\\install/install-agent.ps1 -ProjectRoot . -UninstallPlan
powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\\install/install-agent.ps1 -ProjectRoot . -Uninstall
```

`-UninstallPlan` 默认保留 `project` scope 内容，避免误删 `task.json`、`progress.txt`、`docs/spec/`、`docs/architecture/` 和项目 truth source；legacy 项目中的 `docs/testing/` 也不会由卸载命令主动清理。`vendor` scope 会标记为可删除候选，`user` scope 会标记为需要逐项人工确认。

`-Uninstall` 采用保守策略：

- 必须先读取 `.codex-harness/locks/package.lock.json`；缺少 lock 时拒绝执行。
- 默认只删除 lock 中 `scope=vendor` 的 `managedFiles`，不按当前 manifest 猜测删除。
- 删除前会校验 vendor 文件 hash；如发现 `blocked-user-drift`，停止执行并返回非 0。
- `project` scope 默认保留，避免删除 `task.json`、`progress.txt`、`docs/spec/` 等 truth source。
- `user` scope 不由项目卸载命令自动清理，需要人工逐项处理。
- 删除完成后刷新 lock，并把 `vendor` 标记为 `false`。

## 卸载操作手册

卸载前先运行只读计划：

```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\\install/install-agent.ps1 -ProjectRoot . -UninstallPlan
```

计划输出中：

- `project` scope 默认保留，避免删除项目 runtime、任务和文档 truth source。
- `vendor` scope 是可删除候选，但执行时仍以 lock 中的逐文件记录为准。
- `user` scope 只提示人工清理，不由项目卸载命令处理。

确认只移除 vendor 包后执行：

```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\\install/install-agent.ps1 -ProjectRoot . -Uninstall
```

如果卸载输出 `blocked-user-drift`，说明 `.agents/` 下至少一个 vendor 文件和 lock hash 不一致。此时不要强删整个 `.agents/`，应先确认这些文件是否包含本地修改；需要保留时手工迁出，需要放弃时先恢复到 lock 对应内容或重新安装后再卸载。

## 迁移计划

旧项目从完整 `.agents/` vendor 输出迁移到 thin project 前，先运行只读迁移计划：

```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\\install/install-agent.ps1 -ProjectRoot . -MigratePlan
```

`-MigratePlan` 只输出报告，不写入 `.codex-harness/locks/package.lock.json`，不删除 `.agents/`，也不覆盖项目文档。报告会扫描 `.agents/`、`docs/harness/`、`docs/testing/`、`docs/knowledge/` 和 `docs/requirement-prep-kit/`，并按以下动作归类：

- `adopt-vendor-managed`：目标 vendor 文件与当前 package 一致，可在后续人工确认后纳入 managedFiles。
- `blocked-user-drift`：目标 vendor 文件与当前 package 不一致，需要人工确认或手工合并。
- `missing-vendor-file`：当前 manifest 声明 vendor 文件，但目标项目缺失，仅报告。
- `unknown-legacy-file`：`.agents/` 中存在当前 manifest 未声明的文件，仅报告。
- `manual-review`：legacy 文档目录或残留内容需要人工判断，迁移计划不会自动清理。

如果项目没有 `.agents/`，迁移计划输出 `Status: no-legacy-vendor`。后续自动落锁和自动清理 vendor 内容必须由独立命令实现，并继续以用户确认和 drift 检查为前提。

## 迁移执行契约

Phase 5 只冻结确认式迁移执行，不切换默认 `full`，也不改变 `-MigratePlan` 的只读语义。执行入口统一为：

本契约对应 Requirement ID `PKG-LAYER-P5-MIGRATE-CONTRACT`，与 `docs/agent-package-redesign/migration-plan.md` 的 Phase 5 章节共同作为后续实现任务的规格来源。当前阶段只定义命令、参数、状态、输出字段、失败条件和允许写入范围，不实现递归清理，不切换默认安装模式。

实现任务 `LAYERING-P5-APPLY-001` 已将该入口落到 `tools/install/install-agent.ps1 -MigrateApply [-Confirm]`：默认 dry-run，`-Confirm` 后只把 `adopt-vendor-managed` 分类中 hash 一致的 vendor 文件写入 `.codex-harness/locks/package.lock.json`，并在 `blocked-user-drift`、`unknown-legacy-file` 或 `manual-review` 存在时返回 `status=blocked` 且不写入。

```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\\install/install-agent.ps1 -ProjectRoot . -MigrateApply
powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\\install/install-agent.ps1 -ProjectRoot . -MigrateApply -Confirm
```

`-MigrateApply` 必须遵守以下边界：

- 输入语义来源是 `-MigratePlan` 的分类报告；不得绕过 `adopt-vendor-managed`、`blocked-user-drift`、`missing-vendor-file`、`unknown-legacy-file`、`manual-review` 分类直接写 lock、删除文件或推断缺失内容。
- 默认 dry-run；未提供显式确认开关 `-Confirm` 时，只输出计划动作，不写入任何文件。
- 默认写入面只允许 `.codex-harness/locks/package.lock.json` 和必要 `.codex-harness` config/install metadata。
- 不自动删除 `.agents`。
- 不覆盖用户文档。
- 不处理 legacy docs 自动删除。

分类执行规则：

- `adopt-vendor-managed`：只有显式确认后才可写入 `managedFiles`，且只能写与 package 一致且已分类的 vendor 文件。
- `missing-vendor-file`：只记录缺失并保留报告，不凭空安装、不自动删除、不补写为已安装。
- `blocked-user-drift`：必须阻断自动执行。
- `unknown-legacy-file`：必须阻断自动执行。
- `manual-review`：必须阻断自动执行。

除非后续任务引入逐项确认机制并补齐测试覆盖，否则阻断分类不允许进入自动写入路径。

`-MigrateApply` 输出格式至少包含：

- `status`
- `summaryCounts`
- `actions`
- `blockedReasons`
- `plannedWrites`
- `actualWrites`
- `nextSteps`

其中 `plannedWrites` 用于 dry-run 和确认前审计，`actualWrites` 只记录真实发生的 lock / metadata 写入；dry-run 时必须为空。`summaryCounts` 至少要能表达分类结果和写入计数，`actions` 需要列出计划执行或实际执行的迁移动作，`blockedReasons` 需要对应阻断分类或安全边界。

状态和失败语义必须保持稳定：

- `status=dry-run`：只解析 `-MigratePlan` 分类并输出计划动作，不写文件，`actualWrites` 必须为空。
- `status=confirmed-write`：收到 `-Confirm`，且仅写入 `.codex-harness/locks/package.lock.json` 和必要 `.codex-harness` config/install metadata。
- `status=blocked`：存在 `blocked-user-drift`、`unknown-legacy-file`、`manual-review` 或其他安全边界阻断；命令必须返回非 0，`actualWrites` 必须为空。
- `status=no-op`：没有可执行写入项或计划结果不需要变更。
- dry-run 阶段出现非空 `actualWrites` 时，调用方必须按失败处理并回到 `-MigratePlan` 排查。

`-MigrateApply` 的参数语义也必须显式冻结：

- `ProjectRoot`：目标项目根目录，作为 `-MigratePlan` 和 `-MigrateApply` 的共同输入。
- `MigrateApply`：确认式迁移执行开关，对应只读计划之后的应用阶段。
- `Confirm`：显式写入确认开关，没有它就只能 dry-run，不能修改 lock 或 metadata。
- `Confirm` 在本契约中是迁移写入授权语义，不是交互式提示替代；非交互 driver 运行 `-MigrateApply` 时不得因为 PowerShell 默认确认机制而产生写入。

`-MigrateApply` 的执行必须建立在 `-MigratePlan` 的 drift 分类结果之上，不能跳过计划阶段直接执行写入，也不能把 `blocked-user-drift`、`unknown-legacy-file`、`manual-review` 自动降级成可执行项。

## 验证命令

```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\\harness/doctor.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\harness\test-install-modes.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\harness\doctor.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\harness\tests\test-project-root-layout.ps1 -Root .
git diff --check
```
