Compare commits

...

4 Commits

17 changed files with 751 additions and 783 deletions

View File

@ -1,29 +1,31 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import re import re
import sys import sys
from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional
PLAN_PREFIX = "[PLAN]" PLAN_STATUS_START = "<!-- plan-status:start -->"
PLAN_SECTION_HEADER = "## Plan 状态记录" PLAN_STATUS_END = "<!-- plan-status:end -->"
PLAN_FILE_RE = re.compile(r"^(\d{4}-\d{2}-\d{2})-.+\.md$") PLAN_FILE_RE = re.compile(r"^(\d{4}-\d{2}-\d{2})-.+\.md$")
VALID_STATUSES = {"in-progress", "done", "blocked"} PLAN_LINE_RE = re.compile(
r"^- \[(?P<check>[ xX])\] `(?P<plan>[^`]+)` (?P<status>done|blocked|pending)(?:: (?P<note>.*))?$"
)
VALID_STATUSES = {"done", "blocked", "pending"}
def usage() -> str: def usage() -> str:
return ( return (
"Usage:\\n" "Usage:\n"
" python scripts/plan_progress.py select -plans <dir> -progress <file>\\n" " python scripts/plan_progress.py select -plans <dir> -progress <file>\n"
" python scripts/plan_progress.py record -plan <path> -status <status> -progress <file> [-note <text>]\\n" " python scripts/plan_progress.py record -plan <path> -status <status> -progress <file> [-note <text>]\n"
" python scripts/plan_progress.py -h\\n" " python scripts/plan_progress.py -h\n"
"Options:\\n" "Options:\n"
" -plans DIR\\n" " -plans DIR\n"
" -plan PATH\\n" " -plan PATH\n"
" -status in-progress|done|blocked\\n" " -status done|blocked|pending\n"
" -progress FILE\\n" " -progress FILE\n"
" -note TEXT\\n" " -note TEXT\n"
" -h, -help Show this help.\\n" " -h, -help Show this help.\n"
) )
@ -43,102 +45,123 @@ def parse_flags(args: list[str]) -> dict[str, str]:
return flags return flags
def normalize_plan_key(plan_value: str, cwd: Path) -> str: def normalize_plan_key(plan_value: str) -> str:
try: raw = plan_value.strip().replace("\\", "/")
return Path(plan_value).resolve().relative_to(cwd.resolve()).as_posix() raw = raw.lstrip("./")
except ValueError: if raw.startswith("docs/plans/"):
return Path(plan_value).as_posix() return raw[len("docs/plans/") :]
marker = "/docs/plans/"
if marker in raw:
return raw.split(marker, 1)[1]
return raw
def load_plan_records(progress_path: Path, cwd: Path) -> dict[str, str]: def render_plan_line(plan_key: str, status: str, note: Optional[str]) -> str:
if not progress_path.exists(): checked = "x" if status == "done" else " "
return {} if status == "blocked":
text = progress_path.read_text(encoding="utf-8") suffix = "blocked"
records: dict[str, str] = {} if note:
for line in text.splitlines(): suffix += f": {note}"
if not line.startswith(PLAN_PREFIX): elif status == "pending":
continue suffix = "pending"
payload = line[len(PLAN_PREFIX) :].strip() else:
if not payload: suffix = "done"
continue return f"- [{checked}] `{plan_key}` {suffix}"
segments = [seg.strip() for seg in payload.split("|")]
if not segments:
continue
plan_path = segments[0]
status = None
for seg in segments[1:]:
if "=" not in seg:
continue
key, value = seg.split("=", 1)
if key.strip() == "status":
status = value.strip()
if not plan_path or status is None:
continue
records[normalize_plan_key(plan_path, cwd)] = status
return records
def list_plan_files(plans_dir: Path, cwd: Path) -> list[tuple[str, Path, str]]:
entries: list[tuple[str, Path, str]] = []
for path in plans_dir.iterdir():
if not path.is_file():
continue
match = PLAN_FILE_RE.match(path.name)
if not match:
continue
date_value = match.group(1)
try:
rel = path.resolve().relative_to(cwd.resolve()).as_posix()
except ValueError:
rel = path.as_posix()
entries.append((date_value, path, rel))
return entries
def select_plan(plans_dir: Path, progress_path: Path) -> tuple[int, str]:
cwd = Path.cwd()
if not plans_dir.is_dir():
return 2, f"ERROR: plans dir not found: {plans_dir}"
plans = list_plan_files(plans_dir, cwd)
if not plans:
return 2, "ERROR: no plan files found"
records = load_plan_records(progress_path, cwd)
in_progress = [item for item in plans if records.get(item[2]) == "in-progress"]
if in_progress:
in_progress.sort(key=lambda item: (item[0], item[2]))
return 0, in_progress[-1][2]
pending = [
item
for item in plans
if records.get(item[2]) not in ("done", "blocked")
]
if not pending:
return 2, "ERROR: no pending plans"
pending.sort(key=lambda item: (item[0], item[2]))
return 0, pending[-1][2]
def ensure_plan_section(text: str) -> str:
if PLAN_SECTION_HEADER in text:
return text
suffix = text
if suffix and not suffix.endswith("\n"):
suffix += "\n"
if suffix:
suffix += "\n"
suffix += PLAN_SECTION_HEADER + "\n"
return suffix
def normalize_note(note: str) -> str: def normalize_note(note: str) -> str:
cleaned = note.replace("\n", " ").replace("|", " ").strip() cleaned = note.replace("\n", " ").replace("\r", " ").replace("`", "'").strip()
return cleaned return cleaned
def list_plan_files(plans_dir: Path) -> list[str]:
entries: list[str] = []
for path in plans_dir.iterdir():
if not path.is_file():
continue
if not PLAN_FILE_RE.match(path.name):
continue
try:
rel = path.resolve().relative_to(plans_dir.resolve()).as_posix()
except ValueError:
rel = path.name
entries.append(rel)
return sorted(entries)
def find_block(lines: list[str]) -> Optional[tuple[int, int]]:
start_idx = None
for idx, line in enumerate(lines):
if line.strip() == PLAN_STATUS_START:
start_idx = idx
break
if start_idx is None:
return None
for idx in range(start_idx + 1, len(lines)):
if lines[idx].strip() == PLAN_STATUS_END:
return start_idx, idx
return None
def parse_entries(lines: list[str], start_idx: int, end_idx: int) -> list[tuple[str, str, Optional[str], int]]:
entries: list[tuple[str, str, Optional[str], int]] = []
for idx in range(start_idx + 1, end_idx):
line = lines[idx].strip()
match = PLAN_LINE_RE.match(line)
if not match:
continue
plan_key = normalize_plan_key(match.group("plan"))
status = match.group("status")
note = match.group("note")
entries.append((plan_key, status, note, idx))
return entries
def render_progress_lines(plans: list[str]) -> list[str]:
lines = ["# Plan 状态", "", PLAN_STATUS_START]
for plan_key in plans:
lines.append(render_plan_line(plan_key, "pending", None))
lines.append(PLAN_STATUS_END)
return lines
def select_plan(plans_dir: Path, progress_path: Path) -> tuple[int, str]:
if not plans_dir.is_dir():
return 2, f"ERROR: plans dir not found: {plans_dir}"
plan_keys = list_plan_files(plans_dir)
if not plan_keys:
return 2, "ERROR: no plan files found"
progress_path.parent.mkdir(parents=True, exist_ok=True)
if progress_path.exists():
lines = progress_path.read_text(encoding="utf-8").splitlines()
else:
lines = []
block = find_block(lines)
if not block:
lines = render_progress_lines(plan_keys)
progress_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
return 0, (plans_dir / plan_keys[0]).as_posix()
start_idx, end_idx = block
entries = parse_entries(lines, start_idx, end_idx)
existing = {plan for plan, _, _, _ in entries}
missing = [plan for plan in plan_keys if plan not in existing]
if missing:
insert_lines = [render_plan_line(plan, "pending", None) for plan in missing]
lines[end_idx:end_idx] = insert_lines
end_idx += len(insert_lines)
progress_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
entries = parse_entries(lines, start_idx, end_idx)
for plan_key, status, _, _ in entries:
if status == "pending":
return 0, (plans_dir / plan_key).as_posix()
return 2, "ERROR: no pending plans"
def record_status(plan: str, status: str, progress_path: Path, note: Optional[str]) -> tuple[int, str]: def record_status(plan: str, status: str, progress_path: Path, note: Optional[str]) -> tuple[int, str]:
if status not in VALID_STATUSES: if status not in VALID_STATUSES:
return 2, f"ERROR: invalid status: {status}" return 2, f"ERROR: invalid status: {status}"
@ -147,24 +170,38 @@ def record_status(plan: str, status: str, progress_path: Path, note: Optional[st
progress_path.parent.mkdir(parents=True, exist_ok=True) progress_path.parent.mkdir(parents=True, exist_ok=True)
if progress_path.exists(): if progress_path.exists():
text = progress_path.read_text(encoding="utf-8") lines = progress_path.read_text(encoding="utf-8").splitlines()
else: else:
text = "# 开发进度追踪\n" lines = []
text = ensure_plan_section(text) plan_key = normalize_plan_key(plan)
if not text.endswith("\n"): block = find_block(lines)
text += "\n" if not block:
lines = render_progress_lines([plan_key])
block = find_block(lines)
if not block:
return 2, "ERROR: failed to create plan status block"
date_value = datetime.now().strftime("%Y-%m-%d") start_idx, end_idx = block
plan_path = Path(plan).as_posix() entries = parse_entries(lines, start_idx, end_idx)
line = f"{PLAN_PREFIX} {plan_path} | status={status} | date={date_value}"
if note: rendered_note = None
cleaned = normalize_note(note) if status == "blocked" and note:
if cleaned: rendered_note = normalize_note(note)
line += f" | note={cleaned}"
text += line + "\n" updated_line = render_plan_line(plan_key, status, rendered_note)
progress_path.write_text(text, encoding="utf-8") updated = False
return 0, line for entry_plan, _, _, idx in entries:
if entry_plan == plan_key:
lines[idx] = updated_line
updated = True
break
if not updated:
lines[end_idx:end_idx] = [updated_line]
progress_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
return 0, updated_line
def main(argv: list[str]) -> int: def main(argv: list[str]) -> int:
@ -222,4 +259,4 @@ def main(argv: list[str]) -> int:
if __name__ == "__main__": if __name__ == "__main__":
sys.exit(main(sys.argv[1:])) raise SystemExit(main(sys.argv[1:]))

View File

@ -29,8 +29,8 @@
### 工作流程 ### 工作流程
- [docs/prompts/coding/clarify.md](docs/prompts/coding/clarify.md) - 需求澄清 - [docs/prompts/coding/clarify.md](docs/prompts/coding/clarify.md) - 需求澄清
- [docs/prompts/coding/verify.md](docs/prompts/coding/verify.md) - 验证检查 - [docs/prompts/coding/review.md](docs/prompts/coding/review.md) - 复盘总结
- [docs/prompts/system/agent-behavior.md](docs/prompts/system/agent-behavior.md) - AI 行为规范 - [docs/prompts/system/agent-behavior.md](docs/prompts/system/agent-behavior.md) - 工作模式参考
<!-- playbook:templates:end --> <!-- playbook:templates:end -->
<!-- playbook:framework:end --> <!-- playbook:framework:end -->

View File

@ -1,6 +1,6 @@
# AGENT_RULES # AGENT_RULES
目的:为本仓库提供稳定的执行流程。 目的:为本仓库提供稳定的执行流程与行为规范
## 优先级 ## 优先级
@ -14,6 +14,35 @@
- 不得在代码/日志/注释中写入明文密钥、密码、Token - 不得在代码/日志/注释中写入明文密钥、密码、Token
- 修改鉴权/权限逻辑必须说明动机与风险 - 修改鉴权/权限逻辑必须说明动机与风险
- 不确定是否敏感时按敏感信息处理 - 不确定是否敏感时按敏感信息处理
- 执行修改文件系统的命令前,必须解释目的和潜在影响
## 行为准则
### 项目适应
- **模仿项目风格**:优先分析周围代码和配置,遵循现有约定
- **不假设可用性**:不假设库或框架可用,先验证再使用
- **完整完成请求**:不遗漏用户要求的任何部分
### 技术态度
- **准确性优先**:技术准确性优先于迎合用户
- **诚实纠正**:发现用户理解有误时,礼貌纠正
- **先查后答**:不确定时先调查再回答
### 避免过度工程
- **只做要求的**:不主动添加未要求的功能或重构
- **不过度抽象**:不为一次性操作创建工具函数
- **不为未来设计**:不为假设的未来需求设计
## 沟通原则
- **简洁直接**:专业、直接、简洁,避免对话填充词
- **拒绝时提供替代**:无法满足请求时,简洁说明并提供替代方案
- **不给时间估算**:专注任务本身,让用户自己判断时间
- **代码块标注语言**:输出代码时标注语言类型
- **不使用 emoji**:除非用户明确要求
## 上下文加载(每次会话开始) ## 上下文加载(每次会话开始)
@ -30,23 +59,47 @@
**目的**:让 AI 快速理解项目全貌,避免重复解释。 **目的**:让 AI 快速理解项目全貌,避免重复解释。
## 规划与执行分工
| 阶段 | 工具 | 产出 | 留痕 |
| ------------ | ---------------------- | ----------------- | -------------------- |
| 头脑风暴 | `$brainstorming` skill | 设计思路 | 无 |
| 生成计划 | `$writing-plans` skill | `docs/plans/*.md` | 无 |
| **执行计划** | **主循环** | 代码/配置变更 | **plan_progress.py** |
> **重要**:第三方 skills 不记录操作状态,执行必须通过主循环完成。
## 主循环 ## 主循环
**触发词**
| 触发词 | 模式 | 说明 |
| --------------------------------------- | ---------- | ---------------------- |
| `执行主循环`、`继续执行`、`下一个 Plan` | 常规模式 | 遇确认场景可询问用户 |
| `自动执行所有 Plan` | 无交互模式 | 不询问,按规则自动处理 |
0. 选择 Plan 0. 选择 Plan
- 运行 `python {{PLAYBOOK_SCRIPTS}}/plan_progress.py select -plans docs/plans -progress memory-bank/progress.md` - 运行 `python {{PLAYBOOK_SCRIPTS}}/plan_progress.py select -plans docs/plans -progress memory-bank/progress.md`
- 如无可执行 Plan说明情况并询问用户下一步新增 Plan/切换任务/结束) - 如无可执行 Plan跳到步骤 4
1. 标记开始: 1. 阅读 Plan
- `python {{PLAYBOOK_SCRIPTS}}/plan_progress.py record -plan <plan> -status in-progress -progress memory-bank/progress.md`
2. 阅读 Plan
- 理解目标、子任务与验证标准 - 理解目标、子任务与验证标准
3. 逐步执行: 2. 逐步执行:
- 按顺序执行子任务 - 按顺序执行子任务
- 每步完成后进行必要验证(测试/日志/diff - 每步完成后进行必要验证(测试/日志/diff
- 遇到阻塞立即记录并停止 - 遇到歧义/风险/决策点:
4. 记录结果(写入 `memory-bank/progress.md` - 常规模式:记录到回复中,可询问用户
- 无交互模式:按「需要确认的场景」规则自动处理
- 遇到阻塞:记录原因,跳到步骤 3 标记 blocked
- **安全红线阻塞**(发现明文密钥等):立即停止,不继续后续 Plan
3. 记录结果:
- 完成:`python {{PLAYBOOK_SCRIPTS}}/plan_progress.py record -plan <plan> -status done -progress memory-bank/progress.md` - 完成:`python {{PLAYBOOK_SCRIPTS}}/plan_progress.py record -plan <plan> -status done -progress memory-bank/progress.md`
- 阻塞:`python {{PLAYBOOK_SCRIPTS}}/plan_progress.py record -plan <plan> -status blocked -progress memory-bank/progress.md -note <原因>` - 阻塞:`python {{PLAYBOOK_SCRIPTS}}/plan_progress.py record -plan <plan> -status blocked -progress memory-bank/progress.md -note <原因>`
5. 如存在歧义/风险/决策点,在回复中明确提出,并视需要记录到 `memory-bank/decisions.md` - 回到步骤 0 继续下一个 Plan
4. 汇总报告(所有 Plan 处理完毕后):
- 列出已完成的 Plan
- 列出阻塞的 Plan 及原因
- 列出待确认的歧义/风险/决策点
- 如需记录重要决策,写入 `memory-bank/decisions.md`
## Plan 规则 ## Plan 规则
@ -64,20 +117,37 @@
## 执行约束 ## 执行约束
### 代码修改约束 ### 代码修改
- **必须先读文件再修改**:不读文件就提议修改是禁止的 - **必须先读文件再修改**:不读文件就提议修改是禁止的
- **必须运行测试验证**:相关测试必须通过 - **必须运行测试验证**:相关测试必须通过
- **遵循换行规则**:遵循 `.gitattributes` 规则 - **遵循换行规则**:遵循 `.gitattributes` 规则
- **命名一致性**:遵循项目现有的命名风格
- **最小改动原则**:只修改必要的部分,不顺手重构
### 决策记录约束 ### 决策记录
- **重要决策**:记录到 `memory-bank/decisions.md`ADR 格式) - **重要决策**:记录到 `memory-bank/decisions.md`ADR 格式)
- **待确认事项**:在回复中列出并等待确认 - **待确认事项**:在回复中列出并等待确认
- **进度留痕**:通过 `{{PLAYBOOK_SCRIPTS}}/plan_progress.py` 写入 `memory-bank/progress.md`,该文件为 Plan 状态唯一权威 - **进度留痕**:通过 `{{PLAYBOOK_SCRIPTS}}/plan_progress.py` 维护 `memory-bank/progress.md` 的 Plan 状态块(唯一权威)
### Git 操作
- **不使用 --amend**:除非用户明确要求,总是创建新提交
- **不使用 --force**:特别是推送到 main/master如用户要求必须警告风险
- **不跳过 hooks**:不使用 `--no-verify`
## 工具使用
- **并行执行**:独立的工具调用尽可能并行执行
- **遵循 schema**:严格遵循工具参数定义
- **避免循环**:避免重复调用同一工具获取相同信息
- **优先专用工具**:文件操作用 Read/Edit/Write搜索用 Grep/Glob
## 需要确认的场景 ## 需要确认的场景
**常规模式**(可交互):
- 需求不明确或存在多种可行方案 - 需求不明确或存在多种可行方案
- 需要行为/兼容性取舍 - 需要行为/兼容性取舍
- 风险或约束冲突 - 风险或约束冲突
@ -85,6 +155,21 @@
- **性能权衡**:需要在性能和可维护性之间选择 - **性能权衡**:需要在性能和可维护性之间选择
- **兼容性问题**:可能破坏现有用户代码 - **兼容性问题**:可能破坏现有用户代码
**无交互模式**(自动处理):
| 场景 | 处理方式 |
| -------------------------- | ---------------------------------- |
| 安全红线 | 立即停止,不继续后续 Plan |
| 架构变更/兼容性/破坏性修改 | 标记 blocked跳到下一个 Plan |
| 多种可行方案 | 选择最保守方案,记录选择理由到报告 |
| 歧义/风险/决策点 | 记录到报告,继续执行 |
**可以不确认**(两种模式通用):
- 明显的 bug 修复
- 符合现有模式的小改动
- 测试用例补充
## 验证清单 ## 验证清单
每个 Plan 完成后,必须验证: 每个 Plan 完成后,必须验证:
@ -93,7 +178,7 @@
- [ ] 相关测试通过(如有测试且未被豁免) - [ ] 相关测试通过(如有测试且未被豁免)
- [ ] 换行符正确 - [ ] 换行符正确
- [ ] 无语法错误 - [ ] 无语法错误
- [ ] 已更新 `memory-bank/progress.md` - [ ] 已通过 `plan_progress.py` 记录 Plan 状态
--- ---

View File

@ -1,109 +1,43 @@
# 架构设计 # 架构设计
<!--
填写指南:
- 【必填】:项目启动前必须填写
- 【可选】:按需填写,可随项目发展补充
- 小项目可只填核心模块,架构图可后补
-->
## 整体架构 ## 整体架构
<!-- 【可选】项目成熟后补充 -->
```txt ```txt
┌─────────────────────────────────────────────────────────────┐ {{ARCHITECTURE_DIAGRAM}}
│ {{LAYER_1}} │
└─────────────────────┬───────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ {{LAYER_2}} │
└─────────────────────┬───────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ {{LAYER_3}} │
└─────────────────────────────────────────────────────────────┘
``` ```
## 核心模块 ## 核心模块
### 1. {{MODULE_1}} <!-- 【必填】至少列出主要模块 -->
### {{MODULE_1}}
**职责**{{MODULE_1_DESC}} **职责**{{MODULE_1_DESC}}
**主要组件**
- {{COMPONENT_1}}
- {{COMPONENT_2}}
**核心方法**
- {{METHOD_1}}
- {{METHOD_2}}
---
### 2. {{MODULE_2}}
**职责**{{MODULE_2_DESC}}
**主要组件**
- {{COMPONENT_3}}
- {{COMPONENT_4}}
---
### 3. {{MODULE_3}}
**职责**{{MODULE_3_DESC}}
---
## 设计模式
### {{PATTERN_1}}
**应用**{{PATTERN_1_USAGE}}
**目的**{{PATTERN_1_PURPOSE}}
**优点**
- {{PATTERN_1_ADVANTAGE_1}}
- {{PATTERN_1_ADVANTAGE_2}}
### {{PATTERN_2}}
**应用**{{PATTERN_2_USAGE}}
**目的**{{PATTERN_2_PURPOSE}}
---
## 关键约束 ## 关键约束
### 1. {{CONSTRAINT_CATEGORY_1}} <!-- 【可选】 -->
- {{CONSTRAINT_1}} - {{CONSTRAINT_1}}
- {{CONSTRAINT_2}}
### 2. {{CONSTRAINT_CATEGORY_2}}
- {{CONSTRAINT_3}}
- {{CONSTRAINT_4}}
---
## 扩展点 ## 扩展点
### 1. {{EXTENSION_1}} <!-- 【可选】大项目建议填写 -->
### {{EXTENSION_1}}
**步骤** **步骤**
1. {{STEP_1}} 1. {{STEP_1}}
2. {{STEP_2}}
3. {{STEP_3}}
### 2. {{EXTENSION_2}}
**步骤**
1. {{STEP_4}}
2. {{STEP_5}}
--- ---

View File

@ -1,50 +1,11 @@
# 架构决策记录 # 架构决策记录
本文档记录项目中的重要架构决策,使用 ADR (Architecture Decision Record) 格式。 <!--
填写指南:
--- - 本文件记录重要架构决策,使用 ADR 格式
- 初始可为空,遇到重要决策时由 AI 或人工添加
## ADR-001: {{DECISION_1_TITLE}} - 每个决策使用下方模板
-->
**日期**: {{DATE}}
**状态**: 已采纳
### 决策
{{DECISION_1_CONTENT}}
### 理由
{{DECISION_1_REASON}}
### 影响
{{DECISION_1_IMPACT}}
### 实施细节
{{DECISION_1_IMPLEMENTATION}}
---
## ADR-002: {{DECISION_2_TITLE}}
**日期**: {{DATE}}
**状态**: 已采纳
### 决策
{{DECISION_2_CONTENT}}
### 理由
{{DECISION_2_REASON}}
### 影响
{{DECISION_2_IMPACT}}
---
## ADR 模板 ## ADR 模板
@ -65,10 +26,6 @@
### 影响 ### 影响
对项目的影响 对项目的影响
### 替代方案(可选)
考虑过但未采纳的方案
``` ```
--- ---

View File

@ -1,31 +1,4 @@
# 开发进度追踪 # Plan 状态
## 已知问题 <!-- plan-status:start -->
<!-- plan-status:end -->
<!-- 记录已知但暂不解决的问题 -->
#### {{ISSUE_CATEGORY_1}}
- {{ISSUE_1}}
- **临时方案**{{WORKAROUND_1}}
- **长期方案**{{SOLUTION_1}}
## 里程碑
#### M1: {{MILESTONE_1}}(目标:{{TARGET_DATE_1}}
- [ ] {{MILESTONE_1_TASK_1}}
- [ ] {{MILESTONE_1_TASK_2}}
#### M2: {{MILESTONE_2}}(目标:{{TARGET_DATE_2}}
- [ ] {{MILESTONE_2_TASK_1}}
- [ ] {{MILESTONE_2_TASK_2}}
## Plan 状态记录
<!-- 由 plan_progress.py 自动管理,请勿手动编辑此节内容 -->
---
**最后更新**{{DATE}}

View File

@ -1,65 +1,47 @@
# {{PROJECT_NAME}} 项目简介 # {{PROJECT_NAME}} 项目简介
<!--
填写指南:
- 【必填】:项目启动前必须填写
- 【可选】:按需填写,可随项目发展补充
- 未填写的占位符保持原样或删除整行
-->
## 项目定位 ## 项目定位
<!-- 【必填】 -->
**核心目标**{{PROJECT_GOAL}} **核心目标**{{PROJECT_GOAL}}
**一句话描述**{{PROJECT_DESCRIPTION}} **一句话描述**{{PROJECT_DESCRIPTION}}
## 为什么做这个项目?
### 问题
- {{PROBLEM_1}}
- {{PROBLEM_2}}
- {{PROBLEM_3}}
### 解决方案
- {{SOLUTION_1}}
- {{SOLUTION_2}}
- {{SOLUTION_3}}
## 项目边界 ## 项目边界
<!-- 【必填】至少填写"做什么" -->
### 做什么 ### 做什么
- {{DO_1}} - {{DO_1}}
- {{DO_2}}
- {{DO_3}}
### 不做什么 ### 不做什么
<!-- 【可选】 -->
- {{DONT_1}} - {{DONT_1}}
- {{DONT_2}}
- {{DONT_3}}
### 约束条件 ### 约束条件
<!-- 【可选】 -->
- {{CONSTRAINT_1}} - {{CONSTRAINT_1}}
- {{CONSTRAINT_2}}
- {{CONSTRAINT_3}}
## 核心概念 ## 核心概念
<!-- 根据项目需要填写核心概念 --> <!-- 【可选】项目特有的术语或概念 -->
## 技术栈
- **主语言**{{MAIN_LANGUAGE}}
- **外部依赖**{{DEPENDENCIES}}
- **测试环境**{{TEST_ENV}}
## 参考资料 ## 参考资料
- {{REFERENCE_1}} <!-- 【可选】 -->
- {{REFERENCE_2}}
## 当前状态
- {{STATUS_1}}
- {{STATUS_2}}
- {{STATUS_3}}
--- ---

View File

@ -1,117 +1,63 @@
# 技术栈与工具链 # 技术栈与工具链
<!--
填写指南:
- 【必填】:项目启动前必须填写
- 【可选】:按需填写,可随项目发展补充
- 未填写的占位符保持原样或删除整行
-->
## 核心技术 ## 核心技术
### 主语言:{{MAIN_LANGUAGE}} <!-- 【必填】 -->
**文件类型** **主语言**{{MAIN_LANGUAGE}}
- {{FILE_TYPE_1}} **文件类型**{{FILE_TYPES}}
- {{FILE_TYPE_2}}
**特点**
- {{FEATURE_1}}
- {{FEATURE_2}}
- {{FEATURE_3}}
**运行方式**
- {{RUN_METHOD_1}}
- {{RUN_METHOD_2}}
## 项目结构 ## 项目结构
<!-- 【必填】 -->
```text ```text
{{PROJECT_NAME}}/ {{PROJECT_NAME}}/
├── {{DIR_1}}/ # {{DIR_1_DESC}} ├── {{DIR_1}}/ # {{DIR_1_DESC}}
├── {{DIR_2}}/ # {{DIR_2_DESC}}
├── {{DIR_3}}/ # {{DIR_3_DESC}}
└── memory-bank/ # 项目上下文 └── memory-bank/ # 项目上下文
``` ```
## 开发环境 ## 开发环境
### {{ENV_1}} <!-- 【必填】至少填写运行测试命令 -->
**必需工具** **必需工具**
- {{TOOL_1}} - {{TOOL_1}}
- {{TOOL_2}}
**运行测试** **运行测试**
```bash ```bash
{{TEST_CMD_1}} {{TEST_CMD}}
``` ```
### {{ENV_2}}(如有)
**必需工具**
- {{TOOL_3}}
- {{TOOL_4}}
## 版本控制
### Git 配置
**换行规则**`.gitattributes`
- 遵循 `.gitattributes` 文件定义
**忽略规则**`.gitignore`
- 以 `.gitignore` 实际内容为准
### 分支策略
- `master`/`main`:主分支(稳定版本)
- 功能分支:按需创建
## 测试策略
### 测试类型
- {{TEST_TYPE_1}}
- {{TEST_TYPE_2}}
### 验证标准
**测试通过条件**
1. {{PASS_CONDITION_1}}
2. {{PASS_CONDITION_2}}
3. {{PASS_CONDITION_3}}
**常见失败原因**
- {{FAIL_REASON_1}}
- {{FAIL_REASON_2}}
## 依赖管理 ## 依赖管理
### 外部依赖 <!-- 【可选】 -->
**外部依赖**
- {{EXTERNAL_DEP_1}} - {{EXTERNAL_DEP_1}}
- {{EXTERNAL_DEP_2}}
### 内部依赖 ## 测试策略
- {{INTERNAL_DEP_1}} <!-- 【可选】大项目建议填写 -->
- {{INTERNAL_DEP_2}}
## 性能考虑 **测试类型**
### 当前瓶颈 - {{TEST_TYPE_1}}
- {{BOTTLENECK_1}} **验证标准**
- {{BOTTLENECK_2}}
### 优化方向 - {{PASS_CONDITION_1}}
- {{OPTIMIZATION_1}}
- {{OPTIMIZATION_2}}
--- ---

View File

@ -1,41 +1,47 @@
# 提示词库 # 提示词库
本目录包含 AI 代理的工作流程模板和参考文档 本目录包含 AI 代理的工作流程参考模板。
## 目录结构 ## 目录结构
```text ```text
prompts/ prompts/
├── README.md # 本文件 ├── README.md # 本文件
├── system/ # 系统级规范 ├── system/
│ └── agent-behavior.md │ └── agent-behavior.md # 工作模式参考
├── coding/ # 编码流程 ├── coding/
│ ├── clarify.md # 需求澄清模板 │ ├── clarify.md # 需求澄清模板
│ └── verify.md # 验证检查清单 │ └── review.md # 复盘总结模板
└── user/ # 用户快捷命令 └── meta/
└── quick-test.md # 快速测试命令 └── prompt-generator.md # 元提示词生成器
``` ```
## 使用方式 ## 使用方式
### AI 代理 | 模板 | 触发场景 |
| ----------------------- | ------------------------------ |
| **agent-behavior.md** | 切换工作模式(探索/开发/调试) |
| **clarify.md** | 需求不明确时澄清 |
| **review.md** | Plan 完成后复盘总结 |
| **prompt-generator.md** | 创建新的专用提示词 |
- 新会话时读取 `system/agent-behavior.md` ## 工作流程
- 需要澄清需求时参考 `coding/clarify.md`
- 完成任务前检查 `coding/verify.md`
### 用户 ```
需求不清 → clarify.md
头脑风暴 → $brainstorming skill
生成计划 → $writing-plans skill → docs/plans/*.md
执行计划 → AGENT_RULES 主循环(留痕)
完成复盘 → review.md
沉淀提示词 → prompt-generator.md可选
```
- 使用 `user/quick-test.md` 中的命令快速执行测试 > **核心规则在 `AGENT_RULES.md`**,第三方 skills 负责规划,主循环负责执行和留痕。
## 文档说明
| 文件 | 用途 |
| -------------------------- | ------------------------------- |
| `system/agent-behavior.md` | AI 行为规范、工作模式、禁止行为 |
| `coding/clarify.md` | 需求澄清步骤和问题模板 |
| `coding/verify.md` | 代码、测试、文档验证清单 |
| `user/quick-test.md` | 常用测试命令参考 |
--- ---

View File

@ -1,5 +1,10 @@
# 需求澄清模板 # 需求澄清模板
<!--
按需使用:当需求不明确或存在歧义时参考本模板。
Vibe-coding 场景下可跳过,直接开始实现。
-->
## 何时使用 ## 何时使用
- 需求描述不明确 - 需求描述不明确
@ -10,119 +15,37 @@
## 澄清步骤 ## 澄清步骤
### 1. 理解当前需求 ### 1. 复述需求
**复述需求**
```text ```text
我理解你的需求是:[用自己的话复述] 我理解你的需求是:[用自己的话复述]
``` ```
**识别歧义点** ### 2. 识别歧义
- 歧义 1[描述不明确的地方] - 歧义 1[描述不明确的地方]
- 歧义 2[可能有多种理解的地方] - 歧义 2[可能有多种理解的地方]
--- ### 3. 提出问题
### 2. 提出澄清问题
**问题模板**
> 只问阻塞问题,最多 12 个;优先给出选项让用户选择。 > 只问阻塞问题,最多 12 个;优先给出选项让用户选择。
#### 功能范围
- 这个功能是否包括 [场景 A] - 这个功能是否包括 [场景 A]
- 是否需要支持 [边界情况 B]
- 优先级如何?必须有 vs 可选
#### 行为细节
- 当 [条件 X] 时,应该 [行为 Y] 还是 [行为 Z] - 当 [条件 X] 时,应该 [行为 Y] 还是 [行为 Z]
- 如果 [异常情况],如何处理?
- 是否需要与 [现有功能] 保持一致?
#### 技术约束 ### 4. 提供选项
- 是否有性能要求?
- 是否有兼容性要求?
- 是否有安全要求?
---
### 3. 提供选项
**选项格式**
**选项 A**[方案描述] **选项 A**[方案描述]
- 优点:[列出优点] - 优点:...
- 缺点:[列出缺点] - 缺点:...
- 适用场景:[什么情况下选这个]
**选项 B**[方案描述] **选项 B**[方案描述]
- 优点:[列出优点] - 优点:...
- 缺点:[列出缺点] - 缺点:...
- 适用场景:[什么情况下选这个]
**推荐**[推荐哪个选项,为什么] **推荐**[推荐哪个,为什么]
---
### 4. 确认理解
**确认清单**
- [ ] 功能范围明确
- [ ] 行为细节清晰
- [ ] 技术约束已知
- [ ] 优先级确定
- [ ] 验收标准明确
---
## 示例
### 需求
```text
实现 XXX 功能
```
### 澄清过程
**复述需求**
```text
我理解你的需求是:为 YYY 添加 XXX 功能,
用于 ZZZ。
```
**识别歧义点**
- 歧义 1XXX 是只读还是可写?
- 歧义 2是否需要支持所有场景
**澄清问题**
- 是否需要支持 [场景 A]
- 当 [条件 X] 时,应该如何处理?
**提供选项**
**选项 A**:完整实现
- 优点:功能完整
- 缺点:开发周期长
**选项 B**:核心功能
- 优点:快速交付
- 缺点:功能有限
**推荐**:选项 A因为 [原因]。
--- ---

View File

@ -0,0 +1,66 @@
# 复盘模板
<!--
用途Plan 或阶段完成后的回顾总结
触发:主循环汇总报告时、阶段性工作完成时
-->
## 何时使用
- 一批 Plan 执行完毕后
- 阶段性工作告一段落
- 遇到重大阻塞需要总结
---
## 复盘格式
```markdown
# 复盘: [日期/阶段名称]
## 完成情况
### 已完成
- [x] Plan 1: 简述
- [x] Plan 2: 简述
### 阻塞
- [ ] Plan 3: 阻塞原因
### 跳过
- [ ] Plan 4: 跳过原因
## 关键发现
### 做得好的
- 发现1
- 发现2
### 待改进
- 问题1 → 建议改进方式
- 问题2 → 建议改进方式
## 决策记录
| 决策 | 理由 | 影响 |
|------|------|------|
| 决策1 | 为什么 | 影响范围 |
## 下一步
- [ ] 待处理事项1
- [ ] 待处理事项2
```
---
## 复盘原则
- **客观记录**:如实记录完成/阻塞/跳过
- **提取经验**:总结做得好的和待改进的
- **决策留痕**:重要决策记录到 decisions.md
- **明确下一步**:列出后续待处理事项
---
**最后更新**{{DATE}}

View File

@ -1,93 +0,0 @@
# 验证检查清单
## 代码修改验证
### 语法检查
- [ ] 代码可正常运行(无语法错误)
- [ ] 无未定义的变量或函数
- [ ] 依赖引用正确
### 风格检查
- [ ] 命名符合规范
- [ ] 缩进正确
- [ ] 换行符正确(遵循 .gitattributes
- [ ] 无冗余注释
---
## 测试验证
### 单元测试
- [ ] 相关测试脚本存在
- [ ] 测试可正常运行
- [ ] 测试通过(无失败)
### 回归测试
- [ ] 现有测试仍然通过
- [ ] 未破坏其他功能
---
## 文档验证
### 代码文档
- [ ] 复杂逻辑有注释说明
- [ ] 公开 API 有使用示例(如需)
### 项目文档
- [ ] `memory-bank/progress.md` 已更新
- [ ] 重要决策记录到 `memory-bank/decisions.md`
---
## Git 验证
### 提交前检查
- [ ] 只包含相关修改(无无关文件)
- [ ] 提交信息清晰
- [ ] 无临时文件或调试代码
### 分支检查
- [ ] 在正确的分支上工作
---
## 性能验证(如果涉及)
### 性能测试
- [ ] 处理时间可接受
- [ ] 内存使用正常
- [ ] 无明显性能退化
---
## 安全验证(如果涉及)
### 安全检查
- [ ] 无注入风险
- [ ] 敏感信息已脱敏
---
## 快速检查清单(最小集)
**每次修改必须检查**
- [ ] 代码可运行(无语法错误)
- [ ] 相关测试通过
- [ ] 换行符正确
- [ ] `memory-bank/progress.md` 已更新
---
**最后更新**{{DATE}}

View File

@ -0,0 +1,126 @@
# 提示词生成器(元提示词)
<!--
用途:根据场景自动生成专用提示词
原理:α-prompts生成+ Ω-prompts优化递归循环
-->
## 何时使用
- 需要为新场景创建专用提示词
- 现有提示词不满足特定需求
- 需要批量生成同类提示词
---
## 生成流程(α循环)
### 1. 分析场景
```markdown
**场景名称**[名称]
**目标用户**[AI/人类/两者]
**触发条件**[何时使用这个提示词]
**预期输出**[使用后应该产出什么]
```
### 2. 提取约束
```markdown
**必须做**
- 约束1
- 约束2
**禁止做**
- 禁止1
- 禁止2
**边界条件**
- 边界1
- 边界2
```
### 3. 生成草稿
```markdown
# [提示词标题]
<!--
用途:[一句话描述]
触发:[触发条件]
-->
## 何时使用
- 场景1
- 场景2
## [核心内容]
[根据场景填充]
## [约束/原则]
- 约束1
- 约束2
---
**最后更新**{{DATE}}
```
---
## 优化流程(Ω循环)
### 1. 评估维度
| 维度 | 问题 |
| ---------- | ---------------------- |
| **清晰度** | 指令是否明确无歧义? |
| **完整度** | 是否覆盖所有必要场景? |
| **简洁度** | 是否有冗余内容可删除? |
| **可操作** | AI 能否直接执行? |
### 2. 迭代优化
```
草稿 → 评估 → 修改 → 再评估 → ... → 定稿
```
### 3. 验证测试
- 用实际场景测试提示词效果
- 收集反馈,持续迭代
---
## 提示词模板库
### 标准结构
```markdown
# [标题]
<!--
用途:
触发:
-->
## 何时使用
## [核心内容]
## [约束/原则]
---
**最后更新**{{DATE}}
```
### 命名规范
- 文件名:`[动词]-[对象].template.md`
- 示例:`clarify-requirement.template.md`
---
**最后更新**{{DATE}}

View File

@ -1,18 +1,19 @@
# AI 代理行为规范 # 工作模式参考
## 工作模式 <!--
本文件定义三种工作模式,供 AI 根据任务类型选择。
核心规则(安全红线、验证清单等)见 AGENT_RULES.md。
-->
### 模式 1: 探索模式Explore ## 模式 1: 探索模式Explore
**目的**:理解代码库、分析问题、收集信息 **目的**:理解代码库、分析问题、收集信息
**行为规范** **行为**
- 使用搜索工具探索代码 - 使用搜索工具探索代码
- 输出分析报告和发现 - 输出分析报告和发现
- 提出问题和建议
- 不修改任何代码 - 不修改任何代码
- 不运行测试(除非明确要求)
**适用场景** **适用场景**
@ -22,18 +23,15 @@
--- ---
### 模式 2: 开发模式Develop ## 模式 2: 开发模式Develop
**目的**:实现功能、修复 bug、重构代码 **目的**:实现功能、修复 bug、重构代码
**行为规范** **行为**
- 先读取相关文件,理解现有逻辑 - 先读取相关文件,理解现有逻辑
- 进行精确修改 - 进行精确修改
- 修改后运行对应测试验证 - 修改后运行测试验证
- 更新 `memory-bank/progress.md`
- 不读文件就提议修改
- 不跳过测试直接提交
**适用场景** **适用场景**
@ -43,16 +41,15 @@
--- ---
### 模式 3: 调试模式Debug ## 模式 3: 调试模式Debug
**目的**:诊断问题、对比差异、验证行为 **目的**:诊断问题、对比差异、验证行为
**行为规范** **行为**
- 收集相关日志和输出 - 收集相关日志和输出
- 分析差异原因 - 分析差异原因
- 记录待确认事项并在回复中提出,或直接修复 - 修复后重新验证
- 重新验证
**适用场景** **适用场景**
@ -62,135 +59,4 @@
--- ---
## 代码风格要求
### 通用规范
**命名规范**
- 遵循项目现有的命名风格
- 保持一致性
**缩进**
- 遵循项目现有的缩进风格
**换行**
- 遵循 `.gitattributes` 规则
**注释**
- 只在逻辑不自明时添加注释
- 不添加冗余注释
---
## 禁止行为清单
### 代码修改
- **不读文件就提议修改**
- 必须先读取文件
- 理解现有逻辑后再提出修改建议
- **破坏现有架构**
- 不随意移动目录结构
- 不随意重构核心模块
- **随意改动换行符**
- 遵循 `.gitattributes` 规则
- 不混用 LF 和 CRLF
### 测试流程
- **跳过测试直接提交**
- 修改后必须运行相关测试
- 测试失败必须分析原因
### Git 操作
- **使用 `git commit --amend`**
- 除非用户明确要求
- 总是创建新提交
- **使用 `git push --force`**
- 特别是推送到 main/master 分支
- 如果用户要求,必须警告风险
- **跳过 hooks**
- 不使用 `--no-verify`
### 过度工程
- **添加未要求的功能**
- 只做用户要求的修改
- 不主动重构周边代码
- **添加不必要的注释**
- 不给自明的代码添加注释
- **过度抽象**
- 不为一次性操作创建工具函数
- 不为假设的未来需求设计
---
## 决策原则
### 何时需要确认
**必须确认**
- 需求有歧义,存在多种理解
- 有多个技术方案,需要权衡
- 可能破坏兼容性
- 涉及架构变更
**可以不确认**
- 明显的 bug 修复
- 符合现有模式的小改动
- 测试用例补充
### 何时记录到 decisions.md
**必须记录**ADR 格式):
- 影响多个模块的架构决策
- 技术栈选择
- 设计模式选择
- 重要的约束条件
---
## 沟通原则
### 输出风格
- 简洁明确,避免冗长
- 使用纯文本结构化输出,必要时用 Markdown 代码块
- 代码块标注语言
- 不使用 emoji除非用户明确要求
- 不使用过度的赞美或验证
### 技术准确性
- 优先技术准确性,而非迎合用户
- 发现用户理解有误时,礼貌纠正
- 不确定时,先调查再回答
### 时间估算
- 不给出时间估算
- 专注于任务本身,让用户自己判断时间
---
**最后更新**{{DATE}} **最后更新**{{DATE}}

View File

@ -18,7 +18,7 @@ def run_cli(*args, cwd=None):
class PlanProgressCliTests(unittest.TestCase): class PlanProgressCliTests(unittest.TestCase):
def test_select_prefers_in_progress(self): def test_select_seeds_progress_when_missing(self):
with tempfile.TemporaryDirectory() as tmp_dir: with tempfile.TemporaryDirectory() as tmp_dir:
root = Path(tmp_dir) root = Path(tmp_dir)
plans_dir = root / "docs" / "plans" plans_dir = root / "docs" / "plans"
@ -26,13 +26,6 @@ class PlanProgressCliTests(unittest.TestCase):
(plans_dir / "2026-01-01-old.md").write_text("old", encoding="utf-8") (plans_dir / "2026-01-01-old.md").write_text("old", encoding="utf-8")
(plans_dir / "2026-01-02-new.md").write_text("new", encoding="utf-8") (plans_dir / "2026-01-02-new.md").write_text("new", encoding="utf-8")
progress = root / "memory-bank" / "progress.md"
progress.parent.mkdir(parents=True)
progress.write_text(
"[PLAN] docs/plans/2026-01-01-old.md | status=in-progress | date=2026-01-03\n",
encoding="utf-8",
)
result = run_cli( result = run_cli(
"select", "select",
"-plans", "-plans",
@ -42,10 +35,17 @@ class PlanProgressCliTests(unittest.TestCase):
cwd=root, cwd=root,
) )
self.assertEqual(result.returncode, 0) self.assertEqual(result.returncode, 0, msg=result.stderr)
self.assertEqual(result.stdout.strip(), "docs/plans/2026-01-01-old.md") self.assertEqual(result.stdout.strip(), "docs/plans/2026-01-01-old.md")
def test_select_skips_done_and_blocked(self): progress = root / "memory-bank" / "progress.md"
text = progress.read_text(encoding="utf-8")
self.assertIn("<!-- plan-status:start -->", text)
self.assertIn("<!-- plan-status:end -->", text)
self.assertIn("`2026-01-01-old.md` pending", text)
self.assertIn("`2026-01-02-new.md` pending", text)
def test_select_returns_first_pending_in_order(self):
with tempfile.TemporaryDirectory() as tmp_dir: with tempfile.TemporaryDirectory() as tmp_dir:
root = Path(tmp_dir) root = Path(tmp_dir)
plans_dir = root / "docs" / "plans" plans_dir = root / "docs" / "plans"
@ -58,8 +58,12 @@ class PlanProgressCliTests(unittest.TestCase):
progress.write_text( progress.write_text(
"\n".join( "\n".join(
[ [
"[PLAN] docs/plans/2026-01-02-b.md | status=done | date=2026-01-03", "# Plan 状态",
"[PLAN] docs/plans/2026-01-01-a.md | status=blocked | date=2026-01-03", "",
"<!-- plan-status:start -->",
"- [ ] `2026-01-02-b.md` pending",
"- [ ] `2026-01-01-a.md` pending",
"<!-- plan-status:end -->",
"", "",
] ]
), ),
@ -75,13 +79,27 @@ class PlanProgressCliTests(unittest.TestCase):
cwd=root, cwd=root,
) )
self.assertNotEqual(result.returncode, 0) self.assertEqual(result.returncode, 0, msg=result.stderr)
self.assertIn("no pending plans", (result.stdout + result.stderr).lower()) self.assertEqual(result.stdout.strip(), "docs/plans/2026-01-02-b.md")
def test_record_creates_section(self): def test_record_updates_line(self):
with tempfile.TemporaryDirectory() as tmp_dir: with tempfile.TemporaryDirectory() as tmp_dir:
root = Path(tmp_dir) root = Path(tmp_dir)
progress = root / "memory-bank" / "progress.md" progress = root / "memory-bank" / "progress.md"
progress.parent.mkdir(parents=True)
progress.write_text(
"\n".join(
[
"# Plan 状态",
"",
"<!-- plan-status:start -->",
"- [ ] `2026-01-03-demo.md` pending",
"<!-- plan-status:end -->",
"",
]
),
encoding="utf-8",
)
result = run_cli( result = run_cli(
"record", "record",
@ -91,15 +109,13 @@ class PlanProgressCliTests(unittest.TestCase):
"done", "done",
"-progress", "-progress",
"memory-bank/progress.md", "memory-bank/progress.md",
"-note",
"done",
cwd=root, cwd=root,
) )
self.assertEqual(result.returncode, 0) self.assertEqual(result.returncode, 0, msg=result.stderr)
text = progress.read_text(encoding="utf-8") text = progress.read_text(encoding="utf-8")
self.assertIn("## Plan 状态记录", text) self.assertIn("- [x] `2026-01-03-demo.md` done", text)
self.assertIn("status=done", text) self.assertEqual(text.count("2026-01-03-demo.md"), 1)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -0,0 +1,100 @@
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SCRIPT = ROOT / "scripts" / "playbook.py"
def run_cli(*args):
return subprocess.run(
[sys.executable, str(SCRIPT), *args],
capture_output=True,
text=True,
)
class SyncDirectoryActionsTests(unittest.TestCase):
def test_sync_memory_bank_adds_missing_files_without_deleting_custom(self):
with tempfile.TemporaryDirectory() as tmp_dir:
root = Path(tmp_dir)
memory_bank = root / "memory-bank"
memory_bank.mkdir(parents=True)
custom = memory_bank / "custom.md"
custom.write_text("custom", encoding="utf-8")
config_body = f"""
[playbook]
project_root = "{tmp_dir}"
[sync_memory_bank]
project_name = "Demo"
"""
config_path = root / "playbook.toml"
config_path.write_text(config_body, encoding="utf-8")
result = run_cli("-config", str(config_path))
self.assertEqual(result.returncode, 0, msg=result.stderr)
self.assertTrue(custom.exists())
self.assertTrue((memory_bank / "project-brief.md").is_file())
def test_sync_prompts_adds_missing_files_without_deleting_custom(self):
with tempfile.TemporaryDirectory() as tmp_dir:
root = Path(tmp_dir)
prompts = root / "docs" / "prompts"
prompts.mkdir(parents=True)
custom = prompts / "custom.md"
custom.write_text("custom", encoding="utf-8")
config_body = f"""
[playbook]
project_root = "{tmp_dir}"
[sync_prompts]
"""
config_path = root / "playbook.toml"
config_path.write_text(config_body, encoding="utf-8")
result = run_cli("-config", str(config_path))
self.assertEqual(result.returncode, 0, msg=result.stderr)
self.assertTrue(custom.exists())
self.assertTrue((prompts / "system" / "agent-behavior.md").is_file())
def test_sync_memory_bank_force_overwrites_template_files_only(self):
with tempfile.TemporaryDirectory() as tmp_dir:
root = Path(tmp_dir)
memory_bank = root / "memory-bank"
memory_bank.mkdir(parents=True)
custom = memory_bank / "custom.md"
custom.write_text("custom", encoding="utf-8")
brief = memory_bank / "project-brief.md"
brief.write_text("OLD", encoding="utf-8")
config_body = f"""
[playbook]
project_root = "{tmp_dir}"
[sync_memory_bank]
project_name = "Demo"
force = true
no_backup = true
"""
config_path = root / "playbook.toml"
config_path.write_text(config_body, encoding="utf-8")
result = run_cli("-config", str(config_path))
self.assertEqual(result.returncode, 0, msg=result.stderr)
self.assertTrue(custom.exists())
self.assertNotIn("OLD", brief.read_text(encoding="utf-8"))
backups = list(memory_bank.glob("project-brief.md.bak.*"))
self.assertEqual(backups, [])
if __name__ == "__main__":
unittest.main()

View File

@ -0,0 +1,44 @@
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SCRIPT = ROOT / "scripts" / "playbook.py"
def run_cli(*args):
return subprocess.run(
[sys.executable, str(SCRIPT), *args],
capture_output=True,
text=True,
)
class VendorSnapshotTemplatesTests(unittest.TestCase):
def test_vendor_includes_core_templates(self):
with tempfile.TemporaryDirectory() as tmp_dir:
config_body = f"""
[playbook]
project_root = "{tmp_dir}"
[vendor]
langs = ["tsl"]
"""
config_path = Path(tmp_dir) / "playbook.toml"
config_path.write_text(config_body, encoding="utf-8")
result = run_cli("-config", str(config_path))
self.assertEqual(result.returncode, 0, msg=result.stderr)
snapshot = Path(tmp_dir) / "docs/standards/playbook"
self.assertTrue((snapshot / "templates/AGENTS.template.md").is_file())
self.assertTrue((snapshot / "templates/AGENT_RULES.template.md").is_file())
self.assertTrue((snapshot / "templates/README.md").is_file())
self.assertTrue((snapshot / "templates/memory-bank").is_dir())
self.assertTrue((snapshot / "templates/prompts").is_dir())
if __name__ == "__main__":
unittest.main()