颠覆传统,清理软件,删除无用文件,节省空间,编写程序,保留长期搁置的旧文档,定期随机翻阅,唤醒过去未成型的创意构思。

实际应用场景描述

在心理健康与创新能力的研究中,有一个常见的观点:创新往往源于对过去经验的重新组合与联想。我们在日常工作和学习中会产生大量"未完成"的文档——灵感碎片、半途而废的方案、草图笔记。传统清理软件(如CCleaner、SpaceSniffer等)的核心逻辑是"删除低频文件以释放空间",这虽然节省了存储空间,却可能无意中清除了未来创新所需的"认知种子"。

本程序旨在颠覆这一逻辑:不删除旧文档,而是通过定期随机召回(Random Recall)机制,将长期搁置的文件重新呈现在用户面前,模拟"心理顿悟"的过程——当旧想法与新语境碰撞时,往往能激发出意想不到的创新构思。同时,通过智能归档(而非删除)来管理存储空间。

引入痛点

1. 传统清理软件的盲区:它们只关注文件大小与访问频率,完全忽略文件的语义价值与创新潜力。

2. "创意坟墓"问题:硬盘中堆积了大量被遗忘的半成品文档,它们不是"垃圾",而是未被激活的灵感。

3. 认知偏差:人们倾向于认为"新信息更有价值",而忽视了对旧有知识的重新编码(Re-encoding)所带来的创新收益。

4. 空间焦虑:用户既想保留旧文件,又担心存储空间不足,陷入两难。

核心逻辑讲解

程序的核心是一个三层过滤与召回引擎:

1. 语义价值评估层:通过文件元数据(修改时间、类型、内容摘要)给文件打上"创意价值分",而非简单地按时间排序。

2. 随机漫步召回层:使用加权随机算法,让高价值但长期未访问的文件有更高概率被"翻牌",模拟人类记忆的随机联想机制。

3. 智能归档压缩层:对超过一定阈值(如365天未修改)的文件进行透明压缩(ZIP),释放空间但不删除,保留随时解压访问的能力。

关键设计哲学:

- 不删除(No-Deletion):所有文件至少保留元数据索引

- 随机性(Serendipity):模仿心理学中的"偶遇学习"(Incidental Learning)

- 渐进式压缩(Progressive Archival):空间不够时先压缩再提示,而非直接删除

代码模块化实现

项目结构

creative_archive/

├── README.md

├── core/

│ ├── scanner.py # 文件系统扫描与元数据提取

│ ├── evaluator.py # 创意价值评估引擎

│ ├── recall.py # 随机召回机制

│ └── archiver.py # 智能压缩归档

├── utils/

│ ├── config.py # 配置管理

│ └── logger.py # 日志记录

├── main.py # 主入口

└── requirements.txt # 依赖声明

"requirements.txt"

# 核心依赖

PyMuPDF>=1.23.0 # PDF文本提取(备选文档解析)

python-docx>=0.8.11 # .docx文件解析

openpyxl>=3.1.0 # .xlsx文件解析

markdown>=3.5.0 # Markdown解析

# 工具库

schedule>=1.2.0 # 定时任务

rich>=13.0.0 # 终端美化输出

pyyaml>=6.0 # 配置文件解析

tqdm>=4.66.0 # 进度条

"utils/config.py"

"""

配置管理模块 —— 统一管理程序的可变参数

通过读取 config.yaml 实现外部化配置,避免硬编码

"""

from pathlib import Path

import yaml

DEFAULT_CONFIG = {

"scan_paths": ["~/Documents", "~/Desktop"], # 扫描目录

"ignore_patterns": ["*.tmp", "*.log", ".git/*"], # 忽略规则

"recall": {

"max_files": 10, # 每次召回展示的最大文件数

"min_idle_days": 90, # 最少搁置天数(低于此值不进入召回池)

"weights": {

"idle_time": 0.4, # 搁置时间权重

"file_type": 0.3, # 文件类型权重(文档 > 代码 > 其他)

"content_richness": 0.3 # 内容丰富度权重

}

},

"archive": {

"idle_threshold_days": 365, # 超过此天数自动归档压缩

"archive_dir": "~/.creative_archive/vault"

}

}

def load_config(config_path: str = "config.yaml") -> dict:

"""加载配置文件,缺失时返回默认配置"""

path = Path(config_path)

if path.exists():

with open(path, "r", encoding="utf-8") as f:

user_cfg = yaml.safe_load(f) or {}

# 深层合并(简单实现)

return {**DEFAULT_CONFIG, **user_cfg}

return DEFAULT_CONFIG

"utils/logger.py"

"""

日志模块 —— 统一输出格式,支持彩色终端与文件双写

"""

import logging

from rich.logging import RichHandler

def setup_logger(name: str = "CreativeArchive", level: str = "INFO") -> logging.Logger:

logger = logging.getLogger(name)

logger.setLevel(getattr(logging, level.upper(), logging.INFO))

# 终端彩色输出

console_handler = RichHandler(rich_tracebacks=True)

console_handler.setFormatter(logging.Formatter("%(message)s"))

logger.addHandler(console_handler)

# 文件日志(持久化运行记录)

file_handler = logging.FileHandler("creative_archive.log", encoding="utf-8")

file_handler.setFormatter(logging.Formatter(

"[%(asctime)s] %(levelname)-7s %(name)s - %(message)s"

))

logger.addHandler(file_handler)

return logger

"core/scanner.py"

"""

文件系统扫描器 —— 递归遍历目标目录,提取文件元数据

关键设计:使用生成器(generator)逐批产出,避免一次性加载大量文件导致内存膨胀

"""

import os

import hashlib

from pathlib import Path

from datetime import datetime

from typing import Iterator, Dict, Any

import fnmatch

from utils.logger import setup_logger

logger = setup_logger("Scanner")

class FileMetadata:

"""文件元数据结构体 —— 封装扫描阶段采集的所有信息"""

def __init__(self, path: str):

self.path = Path(path)

self.name = self.path.name

self.suffix = self.path.suffix.lower()

self.size = self.path.stat().st_size

self.mtime = datetime.fromtimestamp(self.path.stat().st_mtime)

self.atime = datetime.fromtimestamp(self.path.stat().st_atime)

self.ctime = datetime.fromtimestamp(self.path.stat().st_ctime)

self.content_hash = self._calc_hash()

def _calc_hash(self, chunk_size: int = 8192) -> str:

"""计算文件SHA-256哈希,用于内容去重(大文件分块读取)"""

h = hashlib.sha256()

try:

with open(self.path, "rb") as f:

while chunk := f.read(chunk_size):

h.update(chunk)

return h.hexdigest()

except (PermissionError, OSError):

return "" # 无法读取的文件返回空哈希

def days_idle(self) -> int:

"""计算文件搁置天数 —— 自最后一次修改至今的天数"""

delta = datetime.now() - self.mtime

return delta.days

def to_dict(self) -> Dict[str, Any]:

"""序列化为字典,便于后续存储与传输"""

return {

"path": str(self.path),

"name": self.name,

"suffix": self.suffix,

"size": self.size,

"mtime": self.mtime.isoformat(),

"atime": self.atime.isoformat(),

"days_idle": self.days_idle(),

"content_hash": self.content_hash

}

def scan_directory(

root: str,

ignore_patterns: list[str] = None

) -> Iterator[FileMetadata]:

"""

递归扫描目录,逐文件产出元数据

ignore_patterns: 类似 .gitignore 的匹配规则列表

"""

root_path = Path(root).expanduser().resolve()

if not root_path.exists():

logger.warning(f"路径不存在,跳过: {root}")

return

ignore_patterns = ignore_patterns or []

for entry in root_path.rglob("*"):

if not entry.is_file():

continue

# 忽略规则匹配

relative = str(entry.relative_to(root_path))

if any(fnmatch.fnmatch(relative, pat) for pat in ignore_patterns):

continue

try:

yield FileMetadata(str(entry))

except (PermissionError, OSError) as e:

logger.debug(f"无法读取文件 {entry}: {e}")

continue

"core/evaluator.py"

"""

创意价值评估引擎 —— 为每份文档计算"创意唤醒分"

设计思路:模仿心理学中的"记忆激活扩散"模型(Spreading Activation)

"""

from typing import Dict, Any, List

from datetime import datetime

from core.scanner import FileMetadata

from utils.logger import setup_logger

logger = setup_logger("Evaluator")

# 文件类型创意价值映射 —— 基于经验设定的基础分

# 文档类(文字密度高)> 代码类(结构化强)> 媒体类(已固化)

TYPE_SCORES = {

# 文档 —— 高创意唤醒潜力

".docx": 0.9, ".doc": 0.85, ".pdf": 0.7,

".txt": 0.6, ".md": 0.8, ".rtf": 0.5,

# 表格/演示 —— 中等

".xlsx": 0.65, ".pptx": 0.6, ".csv": 0.5,

# 代码 —— 结构化高,创意唤醒中等

".py": 0.55, ".js": 0.5, ".java": 0.45, ".cpp": 0.4,

# 媒体 —— 低(但非删除)

".png": 0.2, ".jpg": 0.15, ".mp4": 0.1,

}

# 小文件(<1KB)通常是占位符或碎片,降低权重

SIZE_PENALTY_THRESHOLD = 1024 # 1KB

def evaluate_file(meta: FileMetadata, config: Dict[str, Any]) -> Dict[str, Any]:

"""

对单个文件进行多维度评分,返回评估结果字典

评分维度:

1. 搁置时间分(idle_score) —— 越久未动,召回价值越高

2. 文件类型分(type_score) —— 文本类高于二进制类

3. 内容丰富度分(richness) —— 文件越大(合理范围内)信息密度越高

4. 最终加权得分(final_score)—— 各维度加权求和

"""

weights = config.get("recall", {}).get("weights", {})

# ---- 维度1:搁置时间分(0~1 归一化,180天为满分拐点) ----

days = meta.days_idle()

idle_score = min(days / 180.0, 1.0) # 180天以上均为满分

# ---- 维度2:文件类型分 ----

type_score = TYPE_SCORES.get(meta.suffix, 0.3) # 未知类型给基础分0.3

# ---- 维度3:内容丰富度分 ----

size_kb = meta.size / 1024

if size_kb < 1:

richness = 0.1 # 极小文件可能是碎片

elif size_kb < 100:

richness = min(size_kb / 100.0, 1.0) # 100KB 以下线性增长

else:

richness = 1.0 # 大于100KB认为内容充分

# ---- 加权汇总 ----

final_score = (

idle_score * weights.get("idle_time", 0.4) +

type_score * weights.get("file_type", 0.3) +

richness * weights.get("content_richness", 0.3)

)

return {

**meta.to_dict(),

"idle_score": round(idle_score, 3),

"type_score": round(type_score, 3),

"richness": round(richness, 3),

"final_score": round(final_score, 4)

}

def batch_evaluate(

files: List[FileMetadata],

config: Dict[str, Any]

) -> List[Dict[str, Any]]:

"""批量评估文件,返回按分数降序排列的评估结果列表"""

results = [evaluate_file(f, config) for f in files]

results.sort(key=lambda x: x["final_score"], reverse=True)

logger.info(f"评估完成: {len(results)} 个文件进入召回池")

return results

"core/recall.py"

"""

随机召回引擎 —— 加权随机挑选"被遗忘的创意"

核心算法:加权轮盘赌(Weighted Roulette Wheel Selection)

配合"探索-利用"平衡策略(Epsilon-Greedy 的变体)

"""

import random

from typing import List, Dict, Any, Optional

from datetime import datetime, timedelta

from utils.logger import setup_logger

logger = setup_logger("Recall")

def build_recall_pool(

evaluated_files: List[Dict[str, Any]],

min_idle_days: int = 90,

max_idle_days: Optional[int] = None

) -> List[Dict[str, Any]]:

"""

构建召回候选池 —— 过滤不符合条件的文件

条件:

1. 搁置天数 >= min_idle_days(太新的文件不需要唤醒)

2. 如果指定了 max_idle_days,则不超过该值(用于分桶测试)

"""

pool = []

for f in evaluated_files:

if f["days_idle"] < min_idle_days:

continue

if max_idle_days and f["days_idle"] > max_idle_days:

continue

pool.append(f)

logger.info(f"召回池构建完成: {len(pool)} 个候选文件(最小搁置 {min_idle_days} 天)")

return pool

def weighted_random_pick(

pool: List[Dict[str, Any]],

n: int = 10,

temperature: float = 1.0

) -> List[Dict[str, Any]]:

"""

加权随机召回 —— 核心算法

temperature: 温度参数

- =1.0 完全按分数加权

- >1.0 增加随机性(更多探索)

- <1.0 偏向高分文件(更多利用)

实现方式:对分数做 softmax 变换后按概率采样,不放回

"""

if not pool:

return []

n = min(n, len(pool))

scores = [f["final_score"] for f in pool]

# Softmax 变换

import math

exp_scores = [math.exp(s / temperature) for s in scores]

total = sum(exp_scores)

probabilities = [es / total for es in exp_scores]

# 不放回加权采样

selected_indices = []

indices = list(range(len(pool)))

probs = probabilities.copy()

for _ in range(n):

if not indices:

break

chosen = random.choices(indices, weights=probs, k=1)[0]

selected_indices.append(chosen)

# 移除已选,重新归一化

idx_in_probs = indices.index(chosen)

indices.pop(idx_in_probs)

probs.pop(idx_in_probs)

if probs:

norm = sum(probs)

probs = [p / norm for p in probs]

return [pool[i] for i in selected_indices]

def display_recall(files: List[Dict[str, Any]]) -> None:

"""格式化展示召回结果 —— 使用 rich 库输出美观的终端表格"""

from rich.table import Table

from rich.console import Console

from rich.text import Text

console = Console()

# 标题

console.print("\n" + "="*60, style="dim")

console.print(" 🧠 创意唤醒 —— 被遗忘的文件召回", style="bold cyan")

console.print("="*60, style="dim")

table = Table(show_header=True, header_style="bold magenta", box=None)

table.add_column("#", width=4, justify="center")

table.add_column("文件名", style="green", no_wrap=False)

table.add_column("搁置天数", justify="center", style="yellow")

table.add_column("创意唤醒分", justify="center", style="cyan")

table.add_column("路径", style="dim", no_wrap=False)

for i, f in enumerate(files, 1):

# 分数可视化

score_bar = "█" * int(f["final_score"] * 20)

score_text = Text(f"{f['final_score']:.2f} {score_bar}", style="cyan")

idle_text = Text(f"{f['days_idle']}天", style="yellow")

table.add_row(

str(i),

f["name"][:40] + ("..." if len(f["name"]) > 40 else ""),

idle_text,

score_text,

f["path"][:60] + ("..." if len(f["path"]) > 60 else "")

)

console.print(table)

console.print("="*60, style="dim")

console.print(f"\n 💡 共召回 {len(files)} 个文件,试着打开看看?\n", style="bold green")

"core/archiver.py"

"""

智能归档模块 —— 对长期未访问的大文件进行透明压缩

策略:不删除,只压缩。用户可随时解压恢复

"""

import zipfile

import os

from pathlib import Path

from typing import List, Dict, Any

from datetime import datetime

from utils.logger import setup_logger

logger = setup_logger("Archiver")

def should_archive(meta_dict: Dict[str, Any], threshold_days: int = 365) -> bool:

"""判断文件是否需要归档:搁置超过阈值天数"""

return meta_dict["days_idle"] >= threshold_days

def compress_to_vault(

files: List[Dict[str, Any]],

vault_dir: str = "~/.creative_archive/vault"

) -> Dict[str, Any]:

"""

将符合条件的文件压缩到保险库(vault)中

返回归档报告:成功数、跳过数、释放空间

"""

vault_path = Path(vault_dir).expanduser()

vault_path.mkdir(parents=True, exist_ok=True)

# 按日期命名归档包,避免冲突

timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")

archive_path = vault_path / f"archive_{timestamp}.zip"

archived_count = 0

skipped_count = 0

freed_bytes = 0

archived_files = []

with zipfile.ZipFile(archive_path, "w", zipfile.ZIP_DEFLATED) as zf:

for f in files:

src = Path(f["path"])

if not src.exists():

skipped_count += 1

continue

# 使用相对路径存储,避免解压时路径冲突

arcname = f"archive_{timestamp}/{src.name}"

zf.write(src, arcname=arcname)

freed_bytes += f["size"]

archived_files.append({

"original_path": f["path"],

"archived_as": arcname,

"size": f["size"]

})

archived_count += 1

logger.debug(f"已归档: {src.name}")

# 归档成功后,删除原文件(保留索引记录)

for af in archived_files:

try:

Path(af["original_path"]).unlink()

except OSError as e:

logger.warning(f"删除原文件失败 {af['original_path']}: {e}")

report = {

"archive_path": str(archive_path),

"archived_count": archived_count,

"skipped_count": skipped_count,

"freed_mb": round(freed_bytes / (1024 * 1024), 2),

"files": archived_files

}

logger.info(

f"归档完成: {archived_count} 个文件 → {archive_path} "

f"(释放 {report['freed_mb']} MB)"

)

return report

def restore_from_vault(archive_path: str, restore_dir: str = ".") -> int:

"""从保险库恢复文件 —— 解压归档包到指定目录"""

count = 0

with zipfile.ZipFile(archive_path, "r") as zf:

zf.extractall(restore_dir)

count = len(zf.namelist())

logger.info(f"恢复完成: {count} 个文件 → {restore_dir}")

return count

"main.py"

"""

主程序入口 —— 编排扫描→评估→召回→归档的完整流水线

支持两种模式:

1. 单次运行(--run-once):立即执行一次完整流程

2. 定时运行(默认):按 schedule 配置周期性执行

"""

import argparse

import sys

from pathlib import Path

from datetime import datetime

from utils.config import load_config

from utils.logger import setup_logger

from core.scanner import scan_directory

from core.evaluator import batch_evaluate

from core.recall import build_recall_pool, weighted_random_pick, display_recall

from core.archiver import should_archive, compress_to_vault

logger = setup_logger("Main")

def collect_all_files(config: dict) -> list:

"""从所有配置路径收集文件元数据"""

all_files = []

scan_paths = config.get("scan_paths", [])

ignore_patterns = config.get("ignore_patterns", [])

for p in scan_paths:

logger.info(f"开始扫描: {p}")

count_before = len(all_files)

for meta in scan_directory(p, ignore_patterns):

all_files.append(meta)

new_count = len(all_files) - count_before

logger.info(f" → 发现 {new_count} 个文件")

return all_files

def run_pipeline(config: dict, max_recall: int = None) -> None:

"""执行完整流水线"""

# Step 1: 扫描

files = collect_all_files(config)

if not files:

logger.warning("未发现任何文件,请检查 scan_paths 配置")

return

logger.info(f"扫描完成,共发现 {len(files)} 个文件")

# Step 2: 评估

evaluated = batch_evaluate(files, config)

# Step 3: 构建召回池

recall_cfg = config.get("recall", {})

pool = build_recall_pool(

evaluated,

min_idle_days=recall_cfg.get("min_idle_days", 90)

)

if not pool:

logger.info("召回池为空(所有文件都太"年轻"),本次无需召回")

else:

# Step 4: 随机召回

n = max_recall or recall_cfg.get("max_files", 10)

picked = weighted_random_pick(pool, n=n, temperature=1.2)

display_recall(picked)

# Step 5: 智能归档(空间管理)

archive_cfg = config.get("archive", {})

threshold = archive_cfg.get("idle_threshold_days", 365)

to_archive = [f for f in evaluated if should_archive(f, threshold)]

if to_archive:

logger.info(f"发现 {len(to_archive)} 个文件超过 {threshold} 天,准备归档...")

report = compress_to_vault(

to_archive,

vault_dir=archive_cfg.get("archive_dir", "~/.creative_archive/vault")

)

logger.info(f"归档报告: 释放 {report['freed_mb']} MB 空间")

else:

logger.info("暂无需要归档的文件")

def main():

parser = argparse.ArgumentParser(

description="Creative Archive —— 不删文件,唤醒创意"

)

parser.add_argument(

"--run-once", action="store_true",

help="立即执行一次完整流水线(默认启动定时任务)"

)

parser.add_argument(

"--config", default="config.yaml",

help="配置文件路径(默认: config.yaml)"

)

parser.add_argument(

"--max-recall", type=int, default=None,

help="每次召回的最大文件数(覆盖配置文件)"

)

args = parser.parse_args()

config = load_config(args.config)

if args.run_once:

logger.info("单次运行模式")

run_pipeline(config, args.max_recall)

else:

# 定时模式:每天执行一次

import schedule

import time

logger.info("定时运行模式,每天 09:00 执行召回")

schedule.every().day.at("09:00").do(run_pipeline, config, args.max_recall)

while True:

schedule.run_pending()

time.sleep(60)

if __name__ == "__main__":

main()

README.md

# Creative Archive — 创意唤醒归档工具

> *"Innovation is the re-ordering of existing elements."* — attributed to various sources

## 是什么

Creative Archive 是一个 Python 命令行工具,它**不删除任何文件**,而是通过智能评估 + 加权随机召回 + 透明压缩归档,帮助你在清理存储空间的同时,**定期"翻看"被遗忘的旧文档**,唤醒沉睡的创意构思。

## 核心设计原则

| 原则 | 说明 |

|------|------|

| **No-Deletion(不删除)** | 所有文件至少保留压缩副本,索引永不丢失 |

| **Serendipity(偶遇性)** | 加权随机召回,模拟人类记忆的随机联想 |

| **Progressive Archival(渐进归档)** | 空间不足时压缩而非删除,随时可恢复 |

## 安装

bash

克隆或下载本仓库

cd creative_archive

安装依赖(推荐虚拟环境)

python -m venv .venv

source .venv/bin/activate # Windows: .venv\Scripts\activate

pip install -r requirements.txt

## 配置

编辑 `config.yaml`(首次运行会自动生成默认配置):

yaml

scan_paths:

- "~/Documents"

- "~/Desktop"ignore_patterns:

- "*.tmp"

- "*.log"

- ".git/*"recall:max_files: 10min_idle_days: 90weights:idle_time: 0.4file_type: 0.3content_richness: 0.3archive:idle_threshold_days: 365archive_dir: "~/.creative_archive/vault"

## 使用

bash

立即执行一次完整流程

python main.py --run-once

启动定时任务(每天 09:00 自动召回)

python main.py

指定召回数量

python main.py --run-once --max-recall 5

## 模块说明

| 模块 | 职责 |

|------|------|

| `core/scanner.py` | 递归扫描文件系统,提取文件元数据 |

| `core/evaluator.py` | 多维度创意价值评分引擎 |

| `core/recall.py` | 加权随机召回 + 终端可视化展示 |

| `core/archiver.py` | 透明压缩归档与恢复 |

| `utils/config.py` | 配置加载与默认值管理 |

| `utils/logger.py` | 彩色日志与文件双写 |

## 输出示例

============================================================

🧠 创意唤醒 —— 被遗忘的文件召回

文件名 搁置天数 创意唤醒分 路径

1 老项目的脑洞笔记.docx 412天 0.82 ████████████████ /Users/...

2 未完成的API设计.md 301天 0.76 ██████████████ /Users/...

...

💡 共召回 10 个文件,试着打开看看?

## 恢复归档文件

python

from core.archiver import restore_from_vault

restore_from_vault("~/.creative_archive/vault/archive_20250615_143022.zip")

## 许可

MIT License —— 自由使用、修改、分发。

核心知识点卡片

┌─────────────────────────────────────────────────────────┐

│ 知识点卡片 #1:加权随机采样 │

├─────────────────────────────────────────────────────────┤

│ 概念:Softmax 加权随机不放回采样 │

│ 场景:需要按概率分布选人/选文件,且不允许重复 │

│ 核心公式:P(i) = exp(s_i / T) / Σ exp(s_j / T) │

│ 温度 T 的作用: │

│ T > 1 → 更均匀(探索) T < 1 → 更集中(利用) │

│ 代码位置:core/recall.py → weighted_random_pick() │

└─────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────

利用AI解决实际问题,如果你觉得这个工具好用,欢迎关注长安牧笛!