# Soia Env Storage Cleanup

> 面向小白统计 SOIA 受管配置、状态、缓存和临时目录的空间占用，生成可清理清单并提醒删除风险；只有客户看过最新清单并明确授权后才执行删除，随后复核实际释放空间。触发：「检查 SOIA 占用」「统计缓存大小」「清理临时文件」「清理过期状态」「释放磁盘空间」；不用于未经授权的全盘清理。

- Skill: `soia-team/soia-env-storage-cleanup` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add soia-team/soia-env-storage-cleanup`
- Raw SKILL.md: https://api.skillmd.com/api/skills/soia-team/soia-env-storage-cleanup/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: soia-team (https://skillmd.com/u/soia-team)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/soia-team/soia-env-storage-cleanup

---


# soia-env-storage-cleanup

先只读统计 SOIA 受管目录，再生成有时效、带摘要的清理计划。删除属于不可逆高风险动作：必须把候选类别、大小、条件和风险展示给客户，等待客户针对这份计划明确授权后，才能执行并复核。

## 客户可读说明

### 这个技能可以做什么

| 客户想要 | 技能会做 | 客户能看到 |
|---|---|---|
| 看 SOIA 数据占了多少空间 | 只读统计配置、状态、缓存和临时目录 | 各类别大小、文件数和可清理大小 |
| 判断哪些文件能清理 | 按时效、容量、安全标记和活动状态分类 | 可清理、需授权、禁止清理及原因 |
| 清理过期数据 | 先冻结计划，提醒风险，等待明确授权后按计划删除 | 删除数量、失败项和实际释放空间 |
| 复核清理结果 | 重新检查删除结果和回执摘要 | 是否删除成功、是否有文件重新出现 |

本技能只清理 SOIA 标准受管目录，不扫描或清理整个磁盘，不删除普通下载、项目、照片、文档或其他应用数据。

### 客户如何使用

客户只需要用自然语言提出目标，不需要操作终端：

1. 客户说“检查 SOIA 占用”时，只执行扫描，不删除任何文件。
2. 客户说“清理缓存”时，先生成计划并展示风险；这句话只表示目标，不是最终删除授权。
3. Agent 必须停下来等待客户在看到最新计划后明确回复，例如“确认按计划 `<plan_id>` 删除”。
4. 只有同一份未过期计划获得授权后，Agent 才执行清理；模糊的“继续”“随便处理”不能作为授权。
5. 清理完成后重新检查，并向客户报告实际结果。

### 依赖与安装

| 依赖 | 类型 | 安装 / 配置 | 缺失时怎么处理 |
|---|---|---|---|
| Python 3.10+ | 强依赖 | 使用系统或 `soia-env-python-install` 提供的 Python | 停止清理，只说明如何补齐 |
| `soia-env-storage-cleanup` | 本技能 | `claude plugin install soia-env@soia`（或 npx 路线 `npx skills add soia-team/soia-open-env-skills -g -a '*' -s soia-env-storage-cleanup -y`） | 优先装领域插件；npx 路线会落进共享真源 |

本技能不保存账号或凭据。以下变量只能覆盖受管目录位置，值必须是目录而不是秘密：

```text
SOIA_SKILLS_CONFIG_HOME
SOIA_SKILLS_STATE_HOME
SOIA_SKILLS_CACHE_HOME
SOIA_SKILLS_TEMP_HOME
```

使用环境变量指定自定义根目录时，必须先向客户展示目标目录并取得配置确认，再写入类别匹配的 `.soia-storage-root.json`；缺少标记时执行器拒绝把任意目录认领为 SOIA 存储。

**WorkBuddy** 的装载单位是角色化专家而不是插件，`npx skills add -a '*'` 覆盖不到它，需要单独安装，见 [docs/install/workbuddy.md](https://github.com/soia-team/soia-open-skills/blob/main/docs/install/workbuddy.md)。

### 私密信息与中间数据

- 配置目录只统计、不删除；凭据仍由 provider 登录态或系统凭据库管理。
- 清理计划和回执保存在用户 state 目录，使用用户私有权限；不得提交到 Git。
- 计划内部可以保存精确路径用于防止删错，但客户回复只展示脱敏类别、数量和大小。
- 不读取文件正文，不计算内容摘要；状态清理只读取受管策略标记。
- 不把仓库、用户主目录、客户交付物或未知目录认领为可清理目录。

## 强制授权与风险提醒

扫描、计划和删除是三个独立阶段。初始“帮我清理”请求不能同时授权尚未生成的候选清单。

执行删除前必须向客户显示：

```text
风险提醒：下面的删除不可撤销，可能导致缓存重新下载、审计历史缩短，
或使依赖这些中间文件的任务需要重新运行。配置、凭据和客户交付物不会删除。
请确认是否按计划 <plan_id> 删除 <候选文件数> 个文件，预计释放 <大小>。
```

只有客户在这条提醒之后明确确认当前 `plan_id`，才能生成 `authorization_id` 并调用执行命令。以下情况必须重新扫描并重新授权：

- 计划超过 30 分钟；
- 计划摘要不匹配；
- 候选文件或状态策略发生变化；
- 客户修改清理范围；
- 需要增加新的目录或数据类别。

## 清理范围和方式

| 数据类别 | 默认条件 | 清理方式 | 风险 |
|---|---|---|---|
| 配置 | 无 | 永不自动清理，只统计 | 禁止清理 |
| 审计状态 | 超过 30 天、100 条或 10 MiB，且技能目录存在允许清理的受管标记 | 客户授权后按过期/最旧顺序删除普通文件 | 高 |
| 缓存 | 超过 7 天，或总量超过 512 MiB | 客户授权后按过期/最旧顺序删除 | 中 |
| 临时文件 | 超过 24 小时且没有活动标记 | 客户授权后删除 | 中 |

无论客户如何授权，以下对象仍必须拒绝：

- SOIA 受管根目录之外的路径；
- 符号链接、设备、socket 和非普通文件；
- 含新鲜 `.soia-active` 标记的活动目录；
- `.soia-active`、`.soia-managed-storage.json` 安全标记本身；
- `.soia-storage-root.json` 根目录标记本身；
- 未带有效状态清理标记的 state 文件；
- 文件大小或修改时间在计划后发生变化的候选；
- 配置、凭据和客户交付物。

详细机器合同和标记格式见 [清理安全合同](references/cleanup-contract.md)。

## 工作流

### 1. 只读扫描

Agent 运行：

```bash
python3 scripts/storage_cleanup.py scan --json
```

扫描不得创建目录、计划或回执。把机器输出转换为下面的客户列表，不回显精确私有路径。

### 2. 生成清理计划

客户要求清理时运行：

```bash
python3 scripts/storage_cleanup.py plan --json
```

计划只能写入受管 state 目录，包含 30 分钟有效期、候选文件元数据和 `plan_digest`。生成计划仍不代表授权，也不删除文件。

### 3. 展示计划并等待客户授权

向客户展示：

| 数据类别 | 当前大小 | 可清理大小 | 候选文件 | 清理条件 | 风险 |
|---|---:|---:|---:|---|---|
| <配置/审计状态/缓存/临时文件> | <大小> | <大小> | <数量> | <条件> | <禁止/高/中> |

然后发送强制风险提醒并结束当前执行回合。没有客户新回复，不得进入第 4 步。

### 4. 按授权计划清理

确认客户明确授权当前计划后，由 Agent 生成不含客户原话的随机 `authorization_id`，记录授权时间，并运行：

```bash
python3 scripts/storage_cleanup.py clean \
  --plan <managed-plan-path> \
  --plan-digest <plan-digest> \
  --confirmed-plan-id <plan-id-confirmed-by-customer> \
  --authorization-id <opaque-authorization-id> \
  --authorized-at <RFC3339-with-timezone> \
  --acknowledge-risk CUSTOMER_APPROVED_IRREVERSIBLE_DELETE \
  --execute --json
```

不得替客户预填授权，不得把 `--execute` 放进扫描或计划命令。`--confirmed-plan-id` 必须来自客户在风险提醒后的明确回复，并与计划精确匹配。执行器只删除计划中仍未变化且仍符合条件的普通文件；不确定项进入 `skipped`。

### 5. 复核

清理后运行：

```bash
python3 scripts/storage_cleanup.py verify --receipt <managed-receipt-path> --json
```

核对实际删除数量、删除字节数、失败/跳过项、磁盘空闲空间变化和文件是否重新出现。删除命令返回 0 不能代替复核。

## 客户状态列表（强制）

| 技能 | 当前状态 | 当前版本 | 最新版本 | 运行状态 | 更新时间 | 处理结果 |
|---|---|---|---|---|---|---|
| 存储清理 | <已扫描/等待授权/已清理/被阻塞> | `1.0.0` | <版本或未取得> | <正常/预警/异常/未验证> | <RFC3339-with-timezone> | <发现候选/等待客户确认/已释放空间/被阻塞：原因> |

- 只有扫描完成时写“已扫描”；生成计划后写“等待授权”。
- 没有客户针对当前计划的明确授权时，处理结果必须写“等待客户确认”，不能写“已清理”。
- `更新时间` 是最后一次扫描或删除复核完成时间，不是技能文件修改时间。

## 日志与完成回执

客户可见回复只报告：

- 四类受管数据的大小、文件数和候选大小；
- 当前 `plan_id`、有效期和是否等待授权；
- 风险提醒和禁止清理类别；
- 授权后实际删除/跳过数量、删除字节数和复核状态；
- 不显示精确私有路径、文件名、用户名、token 或文件正文。

本地机器回执保存 `plan_digest`、`authorization_id`、`authorized_at`、`checked_at`、删除结果和 `receipt_digest`，权限尽量使用目录 `0700`、文件 `0600`。

## 前向测试

至少覆盖以下 fixture：配置文件永不删除；过期 cache/temp 可进入计划；新文件和活动目录不进入计划；state 缺标记时拒绝；符号链接拒绝；错误/过期摘要和缺少授权时阻断；候选在计划后变化时跳过；授权成功后只删除计划内文件并复核回执。

真实机器验收只运行 `scan`。删除前向测试必须在测试临时目录内完成，不能用真实客户目录验证。

