feat: export task archives on demand (#14)
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# Agent 开发规则
|
# Agent 开发规则
|
||||||
|
|
||||||
本仓库采用 DevHarness 工作流:Gitea 工单是任务过程的事实来源,Gitea Wiki 是长期开发文档和任务归档的事实来源,Git 是代码与版本绑定资料的变更记录,`docs/` 只保存 Wiki 的只读镜像。人负责确认方案与验收,Agent 负责检查、实现、测试和留下证据。
|
本仓库采用 DevHarness 工作流:Gitea 工单是任务过程的事实来源,Gitea Wiki 是长期开发文档和任务归档的事实来源,Git 是代码与版本绑定资料的变更记录。`docs/` 默认保存核心 Wiki 的只读镜像,`docs/task/` 只保存人工按需导出的任务归档快照。人负责确认方案与验收,Agent 负责检查、实现、测试和留下证据。
|
||||||
|
|
||||||
开始工作前先阅读 [项目档案](docs/00-project-profile.md) 和任务涉及目录中的 `AGENTS.md`。目录越深的规则越具体,但不得削弱上级安全规则。
|
开始工作前先阅读 [项目档案](docs/00-project-profile.md) 和任务涉及目录中的 `AGENTS.md`。目录越深的规则越具体,但不得削弱上级安全规则。
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
6. 开始实施前检查分支和工作区,明确哪些现有改动不属于本任务。
|
6. 开始实施前检查分支和工作区,明确哪些现有改动不属于本任务。
|
||||||
7. 严格按工单范围实现;新发现的问题先记录,不顺手混入当前任务。
|
7. 严格按工单范围实现;新发现的问题先记录,不顺手混入当前任务。
|
||||||
8. 执行与风险相称的测试,把关键结果和未验证部分更新到工单。实施过程中出现计划外、当前无法解除的问题时才标记“阻塞”。
|
8. 执行与风险相称的测试,把关键结果和未验证部分更新到工单。实施过程中出现计划外、当前无法解除的问题时才标记“阻塞”。
|
||||||
9. 长期文档必须先修改 Wiki、读取确认,再运行 `python dev_scripts/sync_wiki_docs.py` 导出本地镜像;不得直接编辑 `docs/` 后反向覆盖 Wiki。
|
9. 长期核心文档必须先修改 Wiki、读取确认,再运行 `python dev_scripts/sync_wiki_docs.py` 导出本地镜像;任务归档默认只更新 Wiki,不自动导出本地。不得直接编辑镜像后反向覆盖 Wiki。
|
||||||
|
|
||||||
Gitea 不可用时,输出完整工单草稿并说明阻塞。未经用户明确授权,不得默认绕过建单。
|
Gitea 不可用时,输出完整工单草稿并说明阻塞。未经用户明确授权,不得默认绕过建单。
|
||||||
|
|
||||||
@@ -51,10 +51,12 @@ Gitea 不可用时,输出完整工单草稿并说明阻塞。未经用户明
|
|||||||
- `建工单并做`:依次建单和执行,`建工单,做`、`建工单,做` 含义相同;停在“待验收”。
|
- `建工单并做`:依次建单和执行,`建工单,做`、`建工单,做` 含义相同;停在“待验收”。
|
||||||
- `继续工单 #N`:核对工单、Git 和 Wiki 证据,从首个未完成步骤继续,不重复仍然有效的检查。
|
- `继续工单 #N`:核对工单、Git 和 Wiki 证据,从首个未完成步骤继续,不重复仍然有效的检查。
|
||||||
- `检查工单 #N`:只读核对范围、验收、测试和证据并输出报告;不自动修复。
|
- `检查工单 #N`:只读核对范围、验收、测试和证据并输出报告;不自动修复。
|
||||||
- `同步文档`:读取 Wiki、导出 `docs/` 并检查一致性;不修改 Wiki、不自动提交。
|
- `同步文档`:读取 Wiki、导出核心 `docs/` 并检查一致性,不处理任务归档;不修改 Wiki、不自动提交。
|
||||||
- `#N 验收通过`:仅在用户明确验收后,更新归档、同步并提交镜像、推送、同步父工单并关闭任务。
|
- `导出任务归档`:人工触发 `python dev_scripts/export_task_archives.py`,只导出新增或 revision 已变化的任务归档;不删除本地文件、不自动提交。
|
||||||
|
- `导出全部任务归档`:人工触发 `python dev_scripts/export_task_archives.py --all`,读取并导出全部线上任务归档;不删除本地文件、不自动提交。
|
||||||
|
- `#N 验收通过`:仅在用户明确验收后,更新 Wiki 归档、同步必要的核心文档、推送、同步父工单并关闭任务;不自动导出任务归档。
|
||||||
|
|
||||||
方案未确认或前置依赖未满足时,实施类指令必须停在对应门禁;除 `#N 验收通过` 外,快捷指令不得关闭待验收工单。Gitea 工单不导出全文,本地只保存 Wiki 任务归档镜像。详细语义见 [开发工作流](docs/01-workflow.md)。
|
方案未确认或前置依赖未满足时,实施类指令必须停在对应门禁;除 `#N 验收通过` 外,快捷指令不得关闭待验收工单。任务归档导出必须由用户明确提出,其他指令不得隐式执行。Gitea 工单不导出全文,`docs/task/` 只是可能不完整的 Wiki 任务归档快照。详细语义见 [开发工作流](docs/01-workflow.md)。
|
||||||
|
|
||||||
### 需求记录与流转
|
### 需求记录与流转
|
||||||
|
|
||||||
@@ -62,7 +64,7 @@ Gitea 不可用时,输出完整工单草稿并说明阻塞。未经用户明
|
|||||||
- 工单中的目标、非目标、已确认方案、验收标准和文档影响构成确认后的正式任务需求。
|
- 工单中的目标、非目标、已确认方案、验收标准和文档影响构成确认后的正式任务需求。
|
||||||
- 影响范围、接口、数据、风险或验收的需求变化必须记录日期、内容、原因和用户确认;会改变已确认结果时先更新工单并等待再次确认。
|
- 影响范围、接口、数据、风险或验收的需求变化必须记录日期、内容、原因和用户确认;会改变已确认结果时先更新工单并等待再次确认。
|
||||||
- 不复制完整聊天,不保存 Agent 内部推理,不写入密码、令牌、个人数据或生产数据;包含敏感信息的原话必须删除敏感部分或改写为脱敏摘要。
|
- 不复制完整聊天,不保存 Agent 内部推理,不写入密码、令牌、个人数据或生产数据;包含敏感信息的原话必须删除敏感部分或改写为脱敏摘要。
|
||||||
- 长期有效的产品需求、业务规则和系统边界进入对应 Wiki 主题页并导出 `docs/`;完成结果进入 Wiki 任务归档并导出 `docs/task/`。Gitea 工单全文不导出到仓库。
|
- 长期有效的产品需求、业务规则和系统边界进入对应 Wiki 主题页并导出核心 `docs/`;完成结果进入 Wiki 任务归档,仅在用户明确要求时导出 `docs/task/`。Gitea 工单全文不导出到仓库。
|
||||||
|
|
||||||
详细记录边界见 [开发工作流](docs/01-workflow.md) 与 [业务规则和术语](docs/03-business-rules-and-glossary.md)。
|
详细记录边界见 [开发工作流](docs/01-workflow.md) 与 [业务规则和术语](docs/03-business-rules-and-glossary.md)。
|
||||||
|
|
||||||
@@ -126,8 +128,8 @@ Gitea 不可用时,输出完整工单草稿并说明阻塞。未经用户明
|
|||||||
|
|
||||||
1. 逐项完成验收、测试和实现提交,并把最终方案、差异、结果、提交及遗留问题写回工单。
|
1. 逐项完成验收、测试和实现提交,并把最终方案、差异、结果、提交及遗留问题写回工单。
|
||||||
2. 工单保持“待验收”,用户没有明确验收通过前不得关闭。
|
2. 工单保持“待验收”,用户没有明确验收通过前不得关闭。
|
||||||
3. 运行 `python dev_scripts/new_task_archive.py <编号> "<短标题>"`,先创建 Wiki 归档,再登记并导出本地镜像。
|
3. 运行 `python dev_scripts/new_task_archive.py <编号> "<短标题>"`,只创建 Wiki 任务归档,不登记或导出本地镜像。
|
||||||
4. 读取确认 Wiki,运行 `python dev_scripts/sync_wiki_docs.py --check`;归档镜像单独提交,并把页面、revision、路径和提交哈希写回工单。
|
4. 读取确认 Wiki,运行 `python dev_scripts/sync_wiki_docs.py --check` 检查核心镜像,并把任务归档页面、revision 和提交哈希写回工单。只有用户明确提出时才增量或全量导出任务归档。
|
||||||
5. 用户验收通过后关闭单元工单,并同步更新 MVP 和 Epic。
|
5. 用户验收通过后关闭单元工单,并同步更新 MVP 和 Epic。
|
||||||
|
|
||||||
MVP 内所有单元任务通过后才能做 MVP 集成验收;MVP 通过后才能关闭 MVP。Epic 的全部范围完成后才能关闭 Epic。
|
MVP 内所有单元任务通过后才能做 MVP 集成验收;MVP 通过后才能关闭 MVP。Epic 的全部范围完成后才能关闭 Epic。
|
||||||
@@ -166,8 +168,9 @@ MVP 内所有单元任务通过后才能做 MVP 集成验收;MVP 通过后才
|
|||||||
|
|
||||||
<!-- 在项目初始化时填写不可违反的技术、安全和业务约束。复杂子项目请在其目录中增加 AGENTS.md。 -->
|
<!-- 在项目初始化时填写不可违反的技术、安全和业务约束。复杂子项目请在其目录中增加 AGENTS.md。 -->
|
||||||
|
|
||||||
- 长期开发文档以 Gitea Wiki 为事实来源,`docs/` 是显式映射生成的只读镜像。
|
- 长期开发文档以 Gitea Wiki 为事实来源,`docs/` 默认保存显式映射生成的核心只读镜像;`docs/task/` 是人工按需快照,可能不完整或不是最新状态。
|
||||||
- Wiki 与镜像的固定顺序是:修改 Wiki → 读取确认 → 导出 `docs` → 校验差异 → 提交镜像。
|
- 核心 Wiki 与镜像的固定顺序是:修改 Wiki → 读取确认 → 导出核心 `docs` → 校验差异 → 提交镜像。
|
||||||
|
- 任务归档默认只保存在 Wiki;`导出任务归档` 或 `导出全部任务归档` 必须由用户明确提出,且不得自动传播删除或重命名。
|
||||||
- 同步配置只允许写入 `docs/` 下的 Markdown;发现镜像有未提交修改时必须停止。
|
- 同步配置只允许写入 `docs/` 下的 Markdown;发现镜像有未提交修改时必须停止。
|
||||||
- Gitea 凭据只通过进程环境或 MCP 安全配置提供,不得写入仓库。
|
- Gitea 凭据只通过进程环境或 MCP 安全配置提供,不得写入仓库。
|
||||||
- `dev_scripts/` 只存放 DevHarness 自身工具;业务项目的通用脚本必须使用独立目录,不得混放。
|
- `dev_scripts/` 只存放 DevHarness 自身工具;业务项目的通用脚本必须使用独立目录,不得混放。
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ DevHarness 是一个以 Gitea 工单管理任务过程、以 Gitea Wiki 管理
|
|||||||
-> Agent 实现并测试
|
-> Agent 实现并测试
|
||||||
-> 提交代码并更新工单
|
-> 提交代码并更新工单
|
||||||
-> 人工验收
|
-> 人工验收
|
||||||
-> 先归档 Wiki,再导出 docs/task 镜像
|
-> 归档 Wiki;任务快照仅在人工提出时导出
|
||||||
-> 关闭工单并更新父工单
|
-> 关闭工单并更新父工单
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -44,17 +44,18 @@ docs/00-project-profile.md Wiki 项目档案的只读镜像
|
|||||||
docs/01-workflow.md Wiki 开发工作流的只读镜像
|
docs/01-workflow.md Wiki 开发工作流的只读镜像
|
||||||
docs/02-07*.md 代码地图、业务、验证、修改、排错和初始化镜像
|
docs/02-07*.md 代码地图、业务、验证、修改、排错和初始化镜像
|
||||||
docs/templates/task-archive.md Wiki 任务归档模板的只读镜像
|
docs/templates/task-archive.md Wiki 任务归档模板的只读镜像
|
||||||
docs/task/ Wiki 任务归档页的只读镜像
|
docs/task/ 人工按需导出的 Wiki 任务归档快照
|
||||||
wiki-docs.json Wiki 页面到本地镜像的显式映射
|
wiki-docs.json 核心 Wiki 页面到本地镜像的显式映射
|
||||||
dev_scripts/check_harness.py 模板和归档的最小自检
|
dev_scripts/check_harness.py 模板和归档的最小自检
|
||||||
dev_scripts/sync_wiki_docs.py 单向导出或检查 Wiki 镜像
|
dev_scripts/sync_wiki_docs.py 单向导出或检查 Wiki 镜像
|
||||||
dev_scripts/new_task_archive.py 先创建 Wiki 任务归档,再导出镜像
|
dev_scripts/new_task_archive.py 只创建 Wiki 任务归档
|
||||||
|
dev_scripts/export_task_archives.py 人工增量或全量导出任务归档
|
||||||
```
|
```
|
||||||
|
|
||||||
## 设计原则
|
## 设计原则
|
||||||
|
|
||||||
- 人决定目标、范围和验收结果,Agent 负责检查、实现和验证。
|
- 人决定目标、范围和验收结果,Agent 负责检查、实现和验证。
|
||||||
- 工单记录实施过程,Wiki 保存长期有效的最终事实,`docs/` 只保存可审查的镜像。
|
- 工单记录实施过程,Wiki 保存长期有效的最终事实,`docs/` 默认保存核心镜像;任务归档快照按需导出。
|
||||||
- 一个单元工单只解决一个可独立测试和回退的问题。
|
- 一个单元工单只解决一个可独立测试和回退的问题。
|
||||||
- 实现提交与归档提交分开,便于审查与追溯。
|
- 实现提交与必要的核心文档镜像提交分开,便于审查与追溯。
|
||||||
- 凭据、个人数据和生产数据不得进入代码、工单或归档。
|
- 凭据、个人数据和生产数据不得进入代码、工单或归档。
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ REQUIRED_FILES = (
|
|||||||
"wiki-docs.json",
|
"wiki-docs.json",
|
||||||
"dev_scripts/wiki_docs.py",
|
"dev_scripts/wiki_docs.py",
|
||||||
"dev_scripts/sync_wiki_docs.py",
|
"dev_scripts/sync_wiki_docs.py",
|
||||||
|
"dev_scripts/export_task_archives.py",
|
||||||
".gitea/issue_template/epic.md",
|
".gitea/issue_template/epic.md",
|
||||||
".gitea/issue_template/mvp.md",
|
".gitea/issue_template/mvp.md",
|
||||||
".gitea/issue_template/task.md",
|
".gitea/issue_template/task.md",
|
||||||
@@ -176,6 +177,15 @@ def check_archives(errors: list[str]) -> None:
|
|||||||
if not re.match(r"^\d+-.+\.md$", path.name):
|
if not re.match(r"^\d+-.+\.md$", path.name):
|
||||||
errors.append(f"归档文件名不符合 <编号>-<标题>.md:{path.name}")
|
errors.append(f"归档文件名不符合 <编号>-<标题>.md:{path.name}")
|
||||||
content = path.read_text(encoding="utf-8")
|
content = path.read_text(encoding="utf-8")
|
||||||
|
try:
|
||||||
|
metadata, _ = parse_mirror(content)
|
||||||
|
except WikiDocsError as exc:
|
||||||
|
errors.append(f"{path.name} 的任务镜像无效:{exc}")
|
||||||
|
continue
|
||||||
|
if re.fullmatch(r"Task-\d+-.+", metadata.get("wiki_page", "")) is None:
|
||||||
|
errors.append(f"{path.name} 的 wiki_page 不是任务归档页面")
|
||||||
|
if re.fullmatch(r"[0-9a-f]{40,64}", metadata.get("wiki_revision", "")) is None:
|
||||||
|
errors.append(f"{path.name} 的 wiki_revision 无效")
|
||||||
for heading in ARCHIVE_HEADINGS:
|
for heading in ARCHIVE_HEADINGS:
|
||||||
if heading not in content:
|
if heading not in content:
|
||||||
errors.append(f"{path.name} 缺少章节:{heading}")
|
errors.append(f"{path.name} 缺少章节:{heading}")
|
||||||
@@ -252,7 +262,7 @@ def check_agent_efficiency_rules(errors: list[str], root: Path = ROOT) -> None:
|
|||||||
"单元任务是唯一正式实施单位",
|
"单元任务是唯一正式实施单位",
|
||||||
"高风险修改必须停止",
|
"高风险修改必须停止",
|
||||||
"用户没有明确验收通过前不得关闭",
|
"用户没有明确验收通过前不得关闭",
|
||||||
"长期文档必须先修改 Wiki",
|
"长期核心文档必须先修改 Wiki",
|
||||||
"提交只包含当前工单相关文件",
|
"提交只包含当前工单相关文件",
|
||||||
"### 自然语言快捷指令",
|
"### 自然语言快捷指令",
|
||||||
"`只分析`",
|
"`只分析`",
|
||||||
@@ -262,6 +272,8 @@ def check_agent_efficiency_rules(errors: list[str], root: Path = ROOT) -> None:
|
|||||||
"`继续工单 #N`",
|
"`继续工单 #N`",
|
||||||
"`检查工单 #N`",
|
"`检查工单 #N`",
|
||||||
"`同步文档`",
|
"`同步文档`",
|
||||||
|
"`导出任务归档`",
|
||||||
|
"`导出全部任务归档`",
|
||||||
"`#N 验收通过`",
|
"`#N 验收通过`",
|
||||||
"### 需求记录与流转",
|
"### 需求记录与流转",
|
||||||
"不得臆造用户原话",
|
"不得臆造用户原话",
|
||||||
@@ -309,7 +321,7 @@ def core_mapping_errors(configured_mappings: dict[str, str]) -> list[str]:
|
|||||||
|
|
||||||
|
|
||||||
def check_wiki_mirrors(errors: list[str]) -> None:
|
def check_wiki_mirrors(errors: list[str]) -> None:
|
||||||
"""检查每份本地文档都有显式映射和可追踪的镜像头。"""
|
"""检查核心映射与镜像头;任务快照由 check_archives 单独检查。"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
config = load_config()
|
config = load_config()
|
||||||
@@ -323,6 +335,7 @@ def check_wiki_mirrors(errors: list[str]) -> None:
|
|||||||
mapped_paths = {mapping.path for mapping in config.mappings}
|
mapped_paths = {mapping.path for mapping in config.mappings}
|
||||||
actual_paths = {
|
actual_paths = {
|
||||||
path.relative_to(ROOT).as_posix() for path in (ROOT / "docs").rglob("*.md")
|
path.relative_to(ROOT).as_posix() for path in (ROOT / "docs").rglob("*.md")
|
||||||
|
if path.parent != ROOT / "docs" / "task"
|
||||||
}
|
}
|
||||||
for path in sorted(actual_paths - mapped_paths):
|
for path in sorted(actual_paths - mapped_paths):
|
||||||
errors.append(f"docs 中存在未登记的 Wiki 镜像:{path}")
|
errors.append(f"docs 中存在未登记的 Wiki 镜像:{path}")
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
"""把 Gitea Wiki 任务归档人工按需导出到 docs/task。"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from new_task_archive import safe_title
|
||||||
|
from wiki_docs import (
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
ROOT,
|
||||||
|
WikiClient,
|
||||||
|
WikiDocsError,
|
||||||
|
dirty_paths,
|
||||||
|
load_config,
|
||||||
|
parse_mirror,
|
||||||
|
write_mirror,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
TASK_PAGE_PATTERN = re.compile(r"^Task-(?P<number>\d+)-(?P<title>.+)$")
|
||||||
|
|
||||||
|
|
||||||
|
def task_revision(metadata: dict[str, Any], page_name: str) -> str:
|
||||||
|
last_commit = metadata.get("last_commit")
|
||||||
|
revision = last_commit.get("sha") if isinstance(last_commit, dict) else None
|
||||||
|
if not isinstance(revision, str) or not revision:
|
||||||
|
raise WikiDocsError(f"Wiki 页面缺少 revision:{page_name}")
|
||||||
|
return revision
|
||||||
|
|
||||||
|
|
||||||
|
def existing_task_mirrors(root: Path = ROOT) -> dict[str, Path]:
|
||||||
|
"""按镜像头匹配已有文件,兼容历史自定义文件名。"""
|
||||||
|
|
||||||
|
mirrors: dict[str, Path] = {}
|
||||||
|
task_dir = root / "docs" / "task"
|
||||||
|
if not task_dir.is_dir():
|
||||||
|
return mirrors
|
||||||
|
for path in task_dir.glob("*.md"):
|
||||||
|
try:
|
||||||
|
metadata, _ = parse_mirror(path.read_text(encoding="utf-8"))
|
||||||
|
except (OSError, UnicodeDecodeError, WikiDocsError) as exc:
|
||||||
|
raise WikiDocsError(f"已有任务镜像无效 {path.name}:{exc}") from exc
|
||||||
|
page_name = metadata.get("wiki_page", "")
|
||||||
|
if not TASK_PAGE_PATTERN.fullmatch(page_name):
|
||||||
|
raise WikiDocsError(f"已有任务镜像页面名无效 {path.name}:{page_name}")
|
||||||
|
if page_name in mirrors:
|
||||||
|
raise WikiDocsError(f"任务页面存在重复本地镜像:{page_name}")
|
||||||
|
mirrors[page_name] = path
|
||||||
|
return mirrors
|
||||||
|
|
||||||
|
|
||||||
|
def task_target(page_name: str, root: Path = ROOT) -> Path:
|
||||||
|
match = TASK_PAGE_PATTERN.fullmatch(page_name)
|
||||||
|
if match is None:
|
||||||
|
raise WikiDocsError(f"不是任务归档页面:{page_name}")
|
||||||
|
title = safe_title(match.group("title"))
|
||||||
|
if not title:
|
||||||
|
raise WikiDocsError(f"任务归档标题无效:{page_name}")
|
||||||
|
return root / "docs" / "task" / f"{match.group('number')}-{title}.md"
|
||||||
|
|
||||||
|
|
||||||
|
def export_task_archives(
|
||||||
|
client: WikiClient, *, export_all: bool = False, root: Path = ROOT
|
||||||
|
) -> list[str]:
|
||||||
|
"""增量或全量读取任务归档;绝不删除本地文件。"""
|
||||||
|
|
||||||
|
dirty = dirty_paths(["docs/task"], root)
|
||||||
|
if dirty:
|
||||||
|
raise WikiDocsError(
|
||||||
|
"本地任务镜像存在未提交改动,已停止以防覆盖:\n" + "\n".join(dirty)
|
||||||
|
)
|
||||||
|
|
||||||
|
existing = existing_task_mirrors(root)
|
||||||
|
pages = []
|
||||||
|
for metadata in client.list_pages():
|
||||||
|
title = metadata.get("title")
|
||||||
|
if isinstance(title, str) and TASK_PAGE_PATTERN.fullmatch(title):
|
||||||
|
pages.append((int(title.split("-", 2)[1]), title, metadata))
|
||||||
|
pages.sort(key=lambda item: (item[0], item[1]))
|
||||||
|
|
||||||
|
messages: list[str] = []
|
||||||
|
targets: set[Path] = set()
|
||||||
|
for _, page_name, metadata in pages:
|
||||||
|
target = existing.get(page_name, task_target(page_name, root))
|
||||||
|
if target in targets:
|
||||||
|
raise WikiDocsError(f"多个任务页面映射到同一本地路径:{target.name}")
|
||||||
|
targets.add(target)
|
||||||
|
revision = task_revision(metadata, page_name)
|
||||||
|
if not export_all and target.is_file():
|
||||||
|
local_metadata, _ = parse_mirror(target.read_text(encoding="utf-8"))
|
||||||
|
if (
|
||||||
|
local_metadata.get("wiki_page") == page_name
|
||||||
|
and local_metadata.get("wiki_revision") == revision
|
||||||
|
):
|
||||||
|
messages.append(f"跳过:{target.relative_to(root)} <- {page_name}@{revision[:12]}")
|
||||||
|
continue
|
||||||
|
page = client.get_page_from_metadata(metadata, page_name)
|
||||||
|
changed = write_mirror(target, page)
|
||||||
|
action = "已导出" if changed else "无变化"
|
||||||
|
messages.append(f"{action}:{target.relative_to(root)} <- {page_name}@{revision[:12]}")
|
||||||
|
return messages
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description="人工按需导出 Gitea Wiki 任务归档")
|
||||||
|
parser.add_argument(
|
||||||
|
"--all", action="store_true", help="全量读取全部线上任务归档;默认按 revision 增量"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--config", default=str(DEFAULT_CONFIG), help="核心 Wiki 映射配置"
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
try:
|
||||||
|
config = load_config(Path(args.config).resolve())
|
||||||
|
messages = export_task_archives(
|
||||||
|
WikiClient(config), export_all=args.all
|
||||||
|
)
|
||||||
|
except WikiDocsError as exc:
|
||||||
|
print(f"错误:{exc}")
|
||||||
|
return 1
|
||||||
|
for message in messages:
|
||||||
|
print(message)
|
||||||
|
print("任务归档全量导出完成" if args.all else "任务归档增量导出完成")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
"""先在 Gitea Wiki 创建任务归档,再登记并导出本地镜像。"""
|
"""只在 Gitea Wiki 创建任务归档;本地镜像由人工按需导出。"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -9,12 +9,9 @@ from pathlib import Path
|
|||||||
|
|
||||||
from wiki_docs import (
|
from wiki_docs import (
|
||||||
DEFAULT_CONFIG,
|
DEFAULT_CONFIG,
|
||||||
Mapping,
|
|
||||||
WikiClient,
|
WikiClient,
|
||||||
WikiDocsError,
|
WikiDocsError,
|
||||||
append_mapping,
|
|
||||||
load_config,
|
load_config,
|
||||||
sync_all,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -43,7 +40,7 @@ def build_archive(
|
|||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="在 Gitea Wiki 创建任务归档并导出 docs/task 镜像"
|
description="在 Gitea Wiki 创建任务归档,不自动导出本地镜像"
|
||||||
)
|
)
|
||||||
parser.add_argument("issue_number", help="Gitea 工单号,例如 123")
|
parser.add_argument("issue_number", help="Gitea 工单号,例如 123")
|
||||||
parser.add_argument("title", help="简短任务标题")
|
parser.add_argument("title", help="简短任务标题")
|
||||||
@@ -61,15 +58,9 @@ def main() -> int:
|
|||||||
try:
|
try:
|
||||||
config = load_config(Path(args.config).resolve())
|
config = load_config(Path(args.config).resolve())
|
||||||
page_name = f"Task-{args.issue_number}-{short_title}"
|
page_name = f"Task-{args.issue_number}-{short_title}"
|
||||||
local_path = f"docs/task/{args.issue_number}-{short_title}.md"
|
|
||||||
mapping = Mapping(page=page_name, path=local_path)
|
|
||||||
if any(
|
|
||||||
item.page == mapping.page or item.path == mapping.path
|
|
||||||
for item in config.mappings
|
|
||||||
):
|
|
||||||
raise WikiDocsError(f"任务归档已经登记:{page_name}")
|
|
||||||
|
|
||||||
client = WikiClient(config)
|
client = WikiClient(config)
|
||||||
|
if any(item.get("title") == page_name for item in client.list_pages()):
|
||||||
|
raise WikiDocsError(f"任务归档已经存在:{page_name}")
|
||||||
template = client.get_page("Task-Archive-Template").text
|
template = client.get_page("Task-Archive-Template").text
|
||||||
issue_url = (
|
issue_url = (
|
||||||
f"{config.gitea_url}/{config.owner}/{config.repository}/issues/"
|
f"{config.gitea_url}/{config.owner}/{config.repository}/issues/"
|
||||||
@@ -83,17 +74,12 @@ def main() -> int:
|
|||||||
content,
|
content,
|
||||||
f"docs: 创建任务 #{args.issue_number} 归档草稿",
|
f"docs: 创建任务 #{args.issue_number} 归档草稿",
|
||||||
)
|
)
|
||||||
append_mapping(config, mapping)
|
|
||||||
updated_config = load_config(config.path)
|
|
||||||
messages = sync_all(updated_config, client)
|
|
||||||
except WikiDocsError as exc:
|
except WikiDocsError as exc:
|
||||||
print(f"错误:{exc}")
|
print(f"错误:{exc}")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
print(f"已创建 Wiki:{page.html_url}")
|
print(f"已创建 Wiki:{page.html_url}")
|
||||||
for message in messages:
|
print("未导出本地任务归档;需要时运行 export_task_archives.py")
|
||||||
print(message)
|
|
||||||
print(f"已登记镜像:{local_path}")
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""从 Gitea Wiki 单向导出本地 docs 镜像。"""
|
"""从 Gitea Wiki 单向导出配置中的核心 docs 镜像。"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ from wiki_docs import DEFAULT_CONFIG, WikiClient, WikiDocsError, load_config, sy
|
|||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
parser = argparse.ArgumentParser(description="从 Gitea Wiki 单向同步 docs 镜像")
|
parser = argparse.ArgumentParser(description="从 Gitea Wiki 单向同步核心 docs 镜像")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--check", action="store_true", help="只检查 Wiki 与镜像是否一致,不写文件"
|
"--check", action="store_true", help="只检查 Wiki 与镜像是否一致,不写文件"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -210,6 +210,14 @@ class WikiClient:
|
|||||||
raise WikiDocsError(
|
raise WikiDocsError(
|
||||||
f"Wiki 页面不存在:{page_name};不会自动删除或重命名本地镜像"
|
f"Wiki 页面不存在:{page_name};不会自动删除或重命名本地镜像"
|
||||||
)
|
)
|
||||||
|
return self.get_page_from_metadata(metadata, page_name)
|
||||||
|
|
||||||
|
def get_page_from_metadata(
|
||||||
|
self, metadata: dict[str, Any], page_name: str | None = None
|
||||||
|
) -> WikiPage:
|
||||||
|
"""使用页面列表元数据读取正文,避免重复获取完整页面列表。"""
|
||||||
|
|
||||||
|
resolved_name = page_name or _required_string(metadata, "title")
|
||||||
sub_url = _required_string(metadata, "sub_url")
|
sub_url = _required_string(metadata, "sub_url")
|
||||||
page = self._request(
|
page = self._request(
|
||||||
"GET",
|
"GET",
|
||||||
@@ -218,20 +226,22 @@ class WikiClient:
|
|||||||
f"{quote(sub_url, safe='%')}",
|
f"{quote(sub_url, safe='%')}",
|
||||||
)
|
)
|
||||||
if not isinstance(page, dict):
|
if not isinstance(page, dict):
|
||||||
raise WikiDocsError(f"Wiki 页面响应格式无效:{page_name}")
|
raise WikiDocsError(f"Wiki 页面响应格式无效:{resolved_name}")
|
||||||
encoded_content = page.get("content_base64")
|
encoded_content = page.get("content_base64")
|
||||||
if not isinstance(encoded_content, str):
|
if not isinstance(encoded_content, str):
|
||||||
raise WikiDocsError(f"Wiki 页面没有 content_base64:{page_name}")
|
raise WikiDocsError(f"Wiki 页面没有 content_base64:{resolved_name}")
|
||||||
try:
|
try:
|
||||||
text = base64.b64decode(encoded_content, validate=True).decode("utf-8")
|
text = base64.b64decode(encoded_content, validate=True).decode("utf-8")
|
||||||
except (ValueError, UnicodeDecodeError) as exc:
|
except (ValueError, UnicodeDecodeError) as exc:
|
||||||
raise WikiDocsError(f"Wiki 页面不是有效的 UTF-8 Markdown:{page_name}") from exc
|
raise WikiDocsError(
|
||||||
|
f"Wiki 页面不是有效的 UTF-8 Markdown:{resolved_name}"
|
||||||
|
) from exc
|
||||||
last_commit = page.get("last_commit")
|
last_commit = page.get("last_commit")
|
||||||
revision = last_commit.get("sha") if isinstance(last_commit, dict) else None
|
revision = last_commit.get("sha") if isinstance(last_commit, dict) else None
|
||||||
if not isinstance(revision, str) or not revision:
|
if not isinstance(revision, str) or not revision:
|
||||||
raise WikiDocsError(f"Wiki 页面缺少 revision:{page_name}")
|
raise WikiDocsError(f"Wiki 页面缺少 revision:{resolved_name}")
|
||||||
title = page.get("title")
|
title = page.get("title")
|
||||||
resolved_title = title if isinstance(title, str) and title else page_name
|
resolved_title = title if isinstance(title, str) and title else resolved_name
|
||||||
html_url = (
|
html_url = (
|
||||||
f"{self.config.gitea_url}/{quote(self.config.owner, safe='')}/"
|
f"{self.config.gitea_url}/{quote(self.config.owner, safe='')}/"
|
||||||
f"{quote(self.config.repository, safe='')}/wiki/{quote(sub_url, safe='%')}"
|
f"{quote(self.config.repository, safe='')}/wiki/{quote(sub_url, safe='%')}"
|
||||||
@@ -303,7 +313,14 @@ def render_mirror(page: WikiPage, existing: str | None = None) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def dirty_mirror_paths(config: Config, root: Path = ROOT) -> list[str]:
|
def dirty_mirror_paths(config: Config, root: Path = ROOT) -> list[str]:
|
||||||
paths = [mapping.path for mapping in config.mappings]
|
return dirty_paths([mapping.path for mapping in config.mappings], root)
|
||||||
|
|
||||||
|
|
||||||
|
def dirty_paths(paths: list[str], root: Path = ROOT) -> list[str]:
|
||||||
|
"""返回指定路径中已有、修改或未跟踪的工作区条目。"""
|
||||||
|
|
||||||
|
if not paths:
|
||||||
|
return []
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["git", "status", "--porcelain", "--", *paths],
|
["git", "status", "--porcelain", "--", *paths],
|
||||||
cwd=root,
|
cwd=root,
|
||||||
@@ -329,6 +346,17 @@ def _write_atomic(path: Path, content: str) -> None:
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def write_mirror(path: Path, page: WikiPage) -> bool:
|
||||||
|
"""写入一份 Wiki 镜像;内容无变化时返回 False。"""
|
||||||
|
|
||||||
|
existing = path.read_text(encoding="utf-8") if path.is_file() else None
|
||||||
|
rendered = render_mirror(page, existing)
|
||||||
|
if existing == rendered:
|
||||||
|
return False
|
||||||
|
_write_atomic(path, rendered)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def check_mirror(mapping: Mapping, page: WikiPage, path: Path) -> list[str]:
|
def check_mirror(mapping: Mapping, page: WikiPage, path: Path) -> list[str]:
|
||||||
if not path.is_file():
|
if not path.is_file():
|
||||||
return [f"缺少镜像:{mapping.path}"]
|
return [f"缺少镜像:{mapping.path}"]
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
|
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
|
||||||
wiki_page: Project-Profile
|
wiki_page: Project-Profile
|
||||||
wiki_url: http://ilaer.eicp.net:8418/opc/dev_harness/wiki/Project-Profile.-
|
wiki_url: http://ilaer.eicp.net:8418/opc/dev_harness/wiki/Project-Profile.-
|
||||||
wiki_revision: c35c1737161ea428c487c066cb39eba4923535d2
|
wiki_revision: 62d1251f807f7d1b60017790c35d46186ffb9617
|
||||||
synchronized_at: 2026-08-10T11:02:51Z
|
synchronized_at: 2026-08-16T11:18:50Z
|
||||||
<!-- gitea-wiki-mirror:end -->
|
<!-- gitea-wiki-mirror:end -->
|
||||||
|
|
||||||
# 项目档案
|
# 项目档案
|
||||||
@@ -63,9 +63,11 @@ synchronized_at: 2026-08-10T11:02:51Z
|
|||||||
| 查看工作区 | `git status --short --branch` | 显示分支且没有无关修改 |
|
| 查看工作区 | `git status --short --branch` | 显示分支且没有无关修改 |
|
||||||
| 检查模板结构 | `python dev_scripts/check_harness.py --strict` | 输出“DevHarness 检查通过” |
|
| 检查模板结构 | `python dev_scripts/check_harness.py --strict` | 输出“DevHarness 检查通过” |
|
||||||
| 运行单元测试 | `python -m unittest discover -s tests -v` | 所有测试通过 |
|
| 运行单元测试 | `python -m unittest discover -s tests -v` | 所有测试通过 |
|
||||||
| 导出 Wiki 镜像 | `python dev_scripts/sync_wiki_docs.py` | 映射页面写入 `docs/` |
|
| 导出核心 Wiki 镜像 | `python dev_scripts/sync_wiki_docs.py` | 核心页面写入 `docs/`,不处理任务归档 |
|
||||||
| 检查 Wiki 镜像 | `python dev_scripts/sync_wiki_docs.py --check` | 输出镜像与 Wiki 一致 |
|
| 检查核心 Wiki 镜像 | `python dev_scripts/sync_wiki_docs.py --check` | 输出核心镜像与 Wiki 一致 |
|
||||||
| 创建任务归档 | `python dev_scripts/new_task_archive.py 123 "修复登录超时"` | 先创建 Wiki 归档页,再登记并导出本地镜像 |
|
| 创建任务归档 | `python dev_scripts/new_task_archive.py 123 "修复登录超时"` | 只创建 Wiki 归档页,不写入本地 |
|
||||||
|
| 增量导出任务归档 | `python dev_scripts/export_task_archives.py` | 只导出新增或 revision 已变化的任务归档 |
|
||||||
|
| 全量导出任务归档 | `python dev_scripts/export_task_archives.py --all` | 读取并导出全部线上任务归档 |
|
||||||
|
|
||||||
## 目录边界
|
## 目录边界
|
||||||
|
|
||||||
@@ -73,13 +75,13 @@ synchronized_at: 2026-08-10T11:02:51Z
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `.gitea/issue_template/` | Gitea 工单模板 | 凭据、任务最终归档 |
|
| `.gitea/issue_template/` | Gitea 工单模板 | 凭据、任务最终归档 |
|
||||||
| `docs/` | Wiki 自动导出的只读镜像 | 人工直接维护的长期文档 |
|
| `docs/` | Wiki 自动导出的只读镜像 | 人工直接维护的长期文档 |
|
||||||
| `docs/task/` | Wiki 任务归档页的只读镜像 | 讨论过程和临时方案 |
|
| `docs/task/` | 人工按需导出的 Wiki 任务归档只读快照,可能不是完整历史 | 讨论过程和临时方案 |
|
||||||
| `dev_scripts/` | Harness 检查、Wiki 同步和归档工具 | 产品功能代码 |
|
| `dev_scripts/` | Harness 检查、Wiki 同步和归档工具 | 产品功能代码 |
|
||||||
| `tests/` | Harness 工具自动化测试 | 生产数据 |
|
| `tests/` | Harness 工具自动化测试 | 生产数据 |
|
||||||
|
|
||||||
## 环境、配置与凭据
|
## 环境、配置与凭据
|
||||||
|
|
||||||
- Wiki 同步配置:仓库根目录 `wiki-docs.json`。
|
- 核心 Wiki 同步配置:仓库根目录 `wiki-docs.json`;任务归档不逐页登记,由按需导出工具动态发现。
|
||||||
- Gitea 地址可由配置提供,也可通过 `GITEA_URL` 覆盖。
|
- Gitea 地址可由配置提供,也可通过 `GITEA_URL` 覆盖。
|
||||||
- Gitea PAT 仅通过 `GITEA_TOKEN` 或 MCP 安全配置提供,不写入仓库。
|
- Gitea PAT 仅通过 `GITEA_TOKEN` 或 MCP 安全配置提供,不写入仓库。
|
||||||
- Token 至少需要读取仓库权限;创建或更新 Wiki 时还需要写仓库权限。
|
- Token 至少需要读取仓库权限;创建或更新 Wiki 时还需要写仓库权限。
|
||||||
@@ -90,7 +92,7 @@ synchronized_at: 2026-08-10T11:02:51Z
|
|||||||
|
|
||||||
## 项目专用验收要求
|
## 项目专用验收要求
|
||||||
|
|
||||||
- 长期文档必须先更新 Wiki,再导出本地镜像。
|
- 长期核心文档必须先更新 Wiki,再导出本地镜像;任务归档默认只保存在 Wiki,用户明确要求时才增量或全量导出。
|
||||||
- 镜像必须包含来源页面、revision 和同步时间。
|
- 镜像必须包含来源页面、revision 和同步时间。
|
||||||
- 页面删除、重命名和映射变更必须人工确认。
|
- 页面删除、重命名和映射变更必须人工确认。
|
||||||
- 新增核心文档时必须更新 Home、显式映射和 Harness 检查。
|
- 新增核心文档时必须更新 Home、显式映射和 Harness 检查。
|
||||||
|
|||||||
+32
-18
@@ -2,8 +2,8 @@
|
|||||||
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
|
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
|
||||||
wiki_page: Development-Workflow
|
wiki_page: Development-Workflow
|
||||||
wiki_url: http://ilaer.eicp.net:8418/opc/dev_harness/wiki/Development-Workflow.-
|
wiki_url: http://ilaer.eicp.net:8418/opc/dev_harness/wiki/Development-Workflow.-
|
||||||
wiki_revision: ea3912217443b40f0fee66a6dc8e951d06e2c2f9
|
wiki_revision: 3f50238b637bc2f896ceab6caa5ab8ab1ded2a4b
|
||||||
synchronized_at: 2026-08-10T03:51:34Z
|
synchronized_at: 2026-08-16T11:18:52Z
|
||||||
<!-- gitea-wiki-mirror:end -->
|
<!-- gitea-wiki-mirror:end -->
|
||||||
|
|
||||||
# 开发工作流
|
# 开发工作流
|
||||||
@@ -67,13 +67,13 @@ Agent 检查分支和工作区,只修改工单范围内的文件。发现新
|
|||||||
- Git 提交哈希;
|
- Git 提交哈希;
|
||||||
- 相关 Wiki 页面及 revision。
|
- 相关 Wiki 页面及 revision。
|
||||||
|
|
||||||
长期文档遵循唯一顺序:
|
长期核心文档遵循唯一顺序:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
修改 Wiki → 读取确认 → 导出 docs → 校验差异 → 提交镜像
|
修改 Wiki → 读取确认 → 导出核心 docs → 校验差异 → 提交镜像
|
||||||
```
|
```
|
||||||
|
|
||||||
不得先编辑 `docs/` 再反向覆盖 Wiki。
|
任务归档默认只更新 Wiki,不自动导出到 `docs/task/`。不得先编辑本地镜像再反向覆盖 Wiki。
|
||||||
|
|
||||||
### 4. 待验收
|
### 4. 待验收
|
||||||
|
|
||||||
@@ -81,21 +81,32 @@ Agent 检查分支和工作区,只修改工单范围内的文件。发现新
|
|||||||
|
|
||||||
### 5. 归档和关闭
|
### 5. 归档和关闭
|
||||||
|
|
||||||
使用以下命令在 Wiki 创建任务归档页、登记显式映射并导出本地镜像:
|
使用以下命令只在 Wiki 创建任务归档页:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
python dev_scripts/new_task_archive.py 123 "修复登录超时"
|
python dev_scripts/new_task_archive.py 123 "修复登录超时"
|
||||||
```
|
```
|
||||||
|
|
||||||
归档内容以 Wiki 页面为主源;本地 `docs/task/<编号>-<短标题>.md` 是镜像。归档镜像单独提交,再把 Wiki 页面、revision、镜像路径和提交哈希写回工单。用户明确验收通过后,关闭单元工单并勾选父工单中的任务。
|
归档内容以 Wiki 页面为事实来源。默认不修改 `wiki-docs.json`,也不写入 `docs/task/`。把 Wiki 页面、revision 和实现提交哈希写回工单;用户明确验收通过后,关闭单元工单并勾选父工单中的任务。
|
||||||
|
|
||||||
|
只有用户明确提出时才导出任务归档:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python dev_scripts/export_task_archives.py # 增量:新增或 revision 变化
|
||||||
|
python dev_scripts/export_task_archives.py --all # 全量:读取全部线上任务归档
|
||||||
|
```
|
||||||
|
|
||||||
|
导出不得自动删除本地文件。`docs/task/` 只是人工按需生成的只读快照,可能不是完整或最新的任务历史。
|
||||||
|
|
||||||
## 文档同步规则
|
## 文档同步规则
|
||||||
|
|
||||||
- 映射保存在 `wiki-docs.json`,每个 Wiki 页面对应唯一仓库路径。
|
- 核心页面映射保存在 `wiki-docs.json`;普通同步只处理这些核心长期文档。
|
||||||
- 同步脚本只实现 Wiki → `docs/`,不提供反向同步。
|
- 任务归档不逐页登记映射,由按需导出工具根据 `Task-<编号>-<标题>` 动态发现;已有镜像优先按镜像头匹配原页面。
|
||||||
|
- 所有同步和导出只实现 Wiki → `docs/`,不提供反向同步。
|
||||||
- 镜像头必须记录页面名、页面地址、revision 和同步时间。
|
- 镜像头必须记录页面名、页面地址、revision 和同步时间。
|
||||||
- 已跟踪镜像存在未提交改动时,同步必须停止;确认改动来源后再处理。
|
- 已跟踪镜像存在未提交改动时,同步必须停止;确认改动来源后再处理。
|
||||||
- `--check` 只检查,不写文件;页面缺失、revision 不一致或正文不一致均失败。
|
- 核心同步的 `--check` 只检查核心镜像,不要求线上任务归档全部存在于本地。
|
||||||
|
- 已经导出的任务镜像仍必须具有来源页面、revision 和同步时间,并通过 Harness 格式检查。
|
||||||
- 页面删除和重命名不会自动传播,必须先更新工单并人工确认映射变化。
|
- 页面删除和重命名不会自动传播,必须先更新工单并人工确认映射变化。
|
||||||
- Wiki 更新成功而导出失败时,在工单记录部分完成状态,不得把任务标为完成。
|
- Wiki 更新成功而导出失败时,在工单记录部分完成状态,不得把任务标为完成。
|
||||||
- 与具体代码版本强绑定的接口或迁移资料可直接随代码维护,但必须在 Wiki 提供入口或适用版本说明。
|
- 与具体代码版本强绑定的接口或迁移资料可直接随代码维护,但必须在 Wiki 提供入口或适用版本说明。
|
||||||
@@ -149,14 +160,14 @@ python dev_scripts/new_task_archive.py 123 "修复登录超时"
|
|||||||
| 关键原始需求、确认后的单次任务需求 | Gitea 单元任务工单 | 无 |
|
| 关键原始需求、确认后的单次任务需求 | Gitea 单元任务工单 | 无 |
|
||||||
| 讨论、决定和需求变化 | Gitea 工单正文或评论 | 无 |
|
| 讨论、决定和需求变化 | Gitea 工单正文或评论 | 无 |
|
||||||
| 长期有效的产品需求、业务规则和系统边界 | 对应 Gitea Wiki 主题页 | `docs/` |
|
| 长期有效的产品需求、业务规则和系统边界 | 对应 Gitea Wiki 主题页 | `docs/` |
|
||||||
| 完成后的实现、验证和遗留问题 | Wiki 任务归档 | `docs/task/` |
|
| 完成后的实现、验证和遗留问题 | Wiki 任务归档 | 人工按需导出的 `docs/task/` 快照(可能不完整) |
|
||||||
|
|
||||||
任务产生长期结论时,先更新对应 Wiki 主题页,再导出本地镜像。Gitea 工单全文不导出到仓库,避免形成第二份任务过程记录。
|
任务产生长期结论时,先更新对应 Wiki 主题页,再导出本地镜像。Gitea 工单全文不导出到仓库,避免形成第二份任务过程记录。
|
||||||
|
|
||||||
## 稳定文档与任务归档
|
## 稳定文档与任务归档
|
||||||
|
|
||||||
- Home、项目档案、代码地图、业务规则、开发验证、常见修改和故障排查描述项目现在怎样工作。
|
- Home、项目档案、代码地图、业务规则、开发验证、常见修改和故障排查描述项目现在怎样工作。
|
||||||
- 工单和任务归档解释某次为什么修改、实际改了什么以及如何验证。
|
- 工单和 Wiki 任务归档解释某次为什么修改、实际改了什么以及如何验证;本地任务快照不是完整历史。
|
||||||
- 新人先读稳定主题页,只有追查历史原因时才读任务归档。
|
- 新人先读稳定主题页,只有追查历史原因时才读任务归档。
|
||||||
- 任务产生的长期结论必须合并到主题页,不能只留在归档。
|
- 任务产生的长期结论必须合并到主题页,不能只留在归档。
|
||||||
|
|
||||||
@@ -204,16 +215,19 @@ python dev_scripts/new_task_archive.py 123 "修复登录超时"
|
|||||||
| `建工单并做` | 依次执行“建工单”和“执行工单”;`建工单,做`、`建工单,做` 含义相同 | 工单保持“待验收” |
|
| `建工单并做` | 依次执行“建工单”和“执行工单”;`建工单,做`、`建工单,做` 含义相同 | 工单保持“待验收” |
|
||||||
| `继续工单 #N` | 核对工单、Git 和 Wiki 证据,从首个未完成步骤继续,不重复仍然有效的检查 | 到达该工单当前流程的停止条件 |
|
| `继续工单 #N` | 核对工单、Git 和 Wiki 证据,从首个未完成步骤继续,不重复仍然有效的检查 | 到达该工单当前流程的停止条件 |
|
||||||
| `检查工单 #N` | 只读对照范围、验收标准、测试和证据,报告通过项、缺失项及未验证部分 | 输出检查报告;不自动修复 |
|
| `检查工单 #N` | 只读对照范围、验收标准、测试和证据,报告通过项、缺失项及未验证部分 | 输出检查报告;不自动修复 |
|
||||||
| `同步文档` | 读取 Wiki,导出已映射的 `docs/` 镜像并检查一致性 | 显示结果和差异;不修改 Wiki、不自动提交 |
|
| `同步文档` | 读取 Wiki,导出核心长期文档镜像并检查一致性,不处理任务归档 | 显示结果和差异;不修改 Wiki、不自动提交 |
|
||||||
| `#N 验收通过` | 记录明确验收,更新 Wiki 归档为“已完成”,导出并提交镜像,推送、同步父工单并关闭任务 | 工单“已完成”并关闭 |
|
| `导出任务归档` | 人工触发增量导出,只写入新增或 revision 已变化的任务归档 | 显示导出或跳过结果;不删除本地文件、不自动提交 |
|
||||||
|
| `导出全部任务归档` | 人工触发全量读取并导出线上全部任务归档 | 显示导出结果;不删除本地文件、不自动提交 |
|
||||||
|
| `#N 验收通过` | 记录明确验收,更新 Wiki 归档为“已完成”,同步必要的核心文档,推送、同步父工单并关闭任务;不自动导出任务归档 | 工单“已完成”并关闭 |
|
||||||
|
|
||||||
补充边界:
|
补充边界:
|
||||||
|
|
||||||
- 方案未确认时,`建工单`、`建工单并做` 和 `执行工单 #N` 不得绕过确认;Agent 应停在方案确认。
|
- 方案未确认时,`建工单`、`建工单并做` 和 `执行工单 #N` 不得绕过确认;Agent 应停在方案确认。
|
||||||
- 前置依赖未满足且不允许并行时,实施类指令停在“待实施”。
|
- 前置依赖未满足且不允许并行时,实施类指令停在“待实施”。
|
||||||
- `#N 验收通过` 必须来自用户明确表达;其他快捷指令不得关闭待验收工单。
|
- `#N 验收通过` 必须来自用户明确表达;其他快捷指令不得关闭待验收工单。
|
||||||
- `同步文档` 发现镜像有未提交改动时停止,不覆盖现有修改。
|
- `同步文档` 或任务归档导出发现目标镜像有未提交改动时停止,不覆盖现有修改。
|
||||||
- Gitea 工单保留讨论和过程,不把工单全文导出到本地;`docs/task/` 只保存 Wiki 最终任务归档的镜像。
|
- `导出任务归档` 和 `导出全部任务归档` 必须由用户明确提出,其他快捷指令不隐式执行。
|
||||||
|
- Gitea 工单保留讨论和过程,不把工单全文导出到本地;`docs/task/` 只保存人工按需导出的 Wiki 最终任务归档快照。
|
||||||
|
|
||||||
## 什么时候重新确认方案
|
## 什么时候重新确认方案
|
||||||
|
|
||||||
@@ -235,6 +249,6 @@ python dev_scripts/new_task_archive.py 123 "修复登录超时"
|
|||||||
| 讨论过程和临时方案 | 是 | 否 | 否 |
|
| 讨论过程和临时方案 | 是 | 否 | 否 |
|
||||||
| 实施进度和阻塞 | 是 | 否 | 否 |
|
| 实施进度和阻塞 | 是 | 否 | 否 |
|
||||||
| 长期有效的最终方案 | 链接 | 是 | 镜像 |
|
| 长期有效的最终方案 | 链接 | 是 | 镜像 |
|
||||||
| 测试结果与未验证内容 | 是 | 任务归档 | 镜像 |
|
| 测试结果与未验证内容 | 是 | 任务归档 | 按需镜像 |
|
||||||
| 提交哈希 | 是 | 任务归档 | 镜像 |
|
| 提交哈希 | 是 | 任务归档 | 按需镜像 |
|
||||||
| 与具体代码版本绑定的说明 | 可链接 | 提供入口 | 是 |
|
| 与具体代码版本绑定的说明 | 可链接 | 提供入口 | 是 |
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
|
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
|
||||||
wiki_page: New-Project-Documentation-Setup
|
wiki_page: New-Project-Documentation-Setup
|
||||||
wiki_url: http://ilaer.eicp.net:8418/opc/dev_harness/wiki/New-Project-Documentation-Setup.-
|
wiki_url: http://ilaer.eicp.net:8418/opc/dev_harness/wiki/New-Project-Documentation-Setup.-
|
||||||
wiki_revision: a443c206780ecd40ae45732ca10686427f393349
|
wiki_revision: cf49e125e7e73d8c818df61153d76abac6abfaaa
|
||||||
synchronized_at: 2026-08-10T11:03:03Z
|
synchronized_at: 2026-08-16T11:19:05Z
|
||||||
<!-- gitea-wiki-mirror:end -->
|
<!-- gitea-wiki-mirror:end -->
|
||||||
|
|
||||||
# 新项目文档初始化
|
# 新项目文档初始化
|
||||||
@@ -46,9 +46,9 @@ synchronized_at: 2026-08-10T11:03:03Z
|
|||||||
|
|
||||||
### 4. 修改镜像配置
|
### 4. 修改镜像配置
|
||||||
|
|
||||||
把 `wiki-docs.json` 中的地址、owner 和 repository 改成新项目;核心主题映射保留。
|
把 `wiki-docs.json` 中的地址、owner 和 repository 改成新项目;只保留核心主题映射,任务归档不逐页登记。
|
||||||
|
|
||||||
确认当前目录确实是新项目副本、且 DevHarness 历史归档不需要保留后,移除属于 DevHarness 的任务归档映射和对应 `docs/task/` 镜像。不要在原 DevHarness 仓库或已有业务项目中执行这项清理。
|
确认当前目录确实是新项目副本、且 DevHarness 历史归档快照不需要保留后,可以移除对应 `docs/task/` 文件。不要在原 DevHarness 仓库或已有业务项目中执行这项清理。
|
||||||
|
|
||||||
不要把 PAT 写入配置。
|
不要把 PAT 写入配置。
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ python dev_scripts/sync_wiki_docs.py --check
|
|||||||
python -m unittest discover -s tests -v
|
python -m unittest discover -s tests -v
|
||||||
```
|
```
|
||||||
|
|
||||||
只有线上 Wiki 确认后才导出 `docs/`。旧项目的任务归档不能带入新项目历史。
|
只有线上 Wiki 确认后才导出核心 `docs/`。任务归档默认不导出;用户明确要求时再运行 `python dev_scripts/export_task_archives.py` 或加 `--all`。旧项目的任务归档快照不能带入新项目历史。
|
||||||
|
|
||||||
## 完成标准
|
## 完成标准
|
||||||
|
|
||||||
|
|||||||
+7
-5
@@ -2,8 +2,8 @@
|
|||||||
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
|
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
|
||||||
wiki_page: Home
|
wiki_page: Home
|
||||||
wiki_url: http://ilaer.eicp.net:8418/opc/dev_harness/wiki/Home
|
wiki_url: http://ilaer.eicp.net:8418/opc/dev_harness/wiki/Home
|
||||||
wiki_revision: 55d89b7aec659f55a61e7ceef881ef34a62d9379
|
wiki_revision: 52e9a6f8fe4fe03db8c65792ef500cc9a5084384
|
||||||
synchronized_at: 2026-08-10T06:58:38Z
|
synchronized_at: 2026-08-16T11:18:48Z
|
||||||
<!-- gitea-wiki-mirror:end -->
|
<!-- gitea-wiki-mirror:end -->
|
||||||
|
|
||||||
# DevHarness 文档中心
|
# DevHarness 文档中心
|
||||||
@@ -65,7 +65,8 @@ python dev_scripts/sync_wiki_docs.py --check
|
|||||||
| 任务状态、讨论、阻塞、验收过程 | Gitea 工单 |
|
| 任务状态、讨论、阻塞、验收过程 | Gitea 工单 |
|
||||||
| 架构、业务规则、开发规范、操作手册、交付文档、任务归档 | Gitea Wiki |
|
| 架构、业务规则、开发规范、操作手册、交付文档、任务归档 | Gitea Wiki |
|
||||||
| 源码和与特定代码版本强绑定的文档 | Git 仓库 |
|
| 源码和与特定代码版本强绑定的文档 | Git 仓库 |
|
||||||
| 离线浏览副本 | Git 仓库中的 `docs/` Wiki 镜像 |
|
| 核心长期文档的离线浏览副本 | Git 仓库中的 `docs/` Wiki 镜像 |
|
||||||
|
| 完整任务归档 | Gitea Wiki;`docs/task/` 仅是人工按需导出的快照 |
|
||||||
|
|
||||||
本地 `docs/` 不是编辑入口。长期文档必须先修改 Wiki,读取确认后再导出镜像。
|
本地 `docs/` 不是编辑入口。长期文档必须先修改 Wiki,读取确认后再导出镜像。
|
||||||
|
|
||||||
@@ -84,9 +85,10 @@ python dev_scripts/sync_wiki_docs.py --check
|
|||||||
修改 Wiki → 读取确认 → 导出 docs → 校验差异 → 提交镜像
|
修改 Wiki → 读取确认 → 导出 docs → 校验差异 → 提交镜像
|
||||||
```
|
```
|
||||||
|
|
||||||
- 页面和本地路径通过仓库中的 `wiki-docs.json` 显式映射。
|
- 核心页面和本地路径通过仓库中的 `wiki-docs.json` 显式映射;普通同步不处理任务归档。
|
||||||
|
- 任务归档默认只保存在 Wiki,只有用户明确提出时才增量或全量导出到 `docs/task/`。
|
||||||
- 镜像头记录来源页面、Wiki revision 和同步时间。
|
- 镜像头记录来源页面、Wiki revision 和同步时间。
|
||||||
- 已映射镜像存在未提交修改时同步必须停止。
|
- 已映射镜像存在未提交修改时同步必须停止。
|
||||||
- 页面删除、重命名和映射变更必须人工确认。
|
- 页面删除、重命名和映射变更必须人工确认。
|
||||||
- Wiki 或导出失败时,相关任务不能标记为完成。
|
- 核心 Wiki 或必要同步失败时,相关任务不能标记为完成;未请求任务归档导出不阻止任务完成。
|
||||||
- 凭据、个人数据和生产数据不得进入 Wiki 或镜像。
|
- 凭据、个人数据和生产数据不得进入 Wiki 或镜像。
|
||||||
|
|||||||
@@ -43,6 +43,12 @@ class CoreDocumentTests(unittest.TestCase):
|
|||||||
for page, path in CORE_PAGE_PATHS.items():
|
for page, path in CORE_PAGE_PATHS.items():
|
||||||
self.assertEqual(mappings.get(page), path)
|
self.assertEqual(mappings.get(page), path)
|
||||||
|
|
||||||
|
def test_task_archives_are_not_core_mappings(self) -> None:
|
||||||
|
config = load_config()
|
||||||
|
self.assertFalse(
|
||||||
|
any(mapping.path.startswith("docs/task/") for mapping in config.mappings)
|
||||||
|
)
|
||||||
|
|
||||||
def test_existing_project_adoption_guide_is_core_document(self) -> None:
|
def test_existing_project_adoption_guide_is_core_document(self) -> None:
|
||||||
path = "docs/08-existing-project-adoption.md"
|
path = "docs/08-existing-project-adoption.md"
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
|||||||
+157
-1
@@ -12,7 +12,16 @@ from unittest.mock import Mock, patch
|
|||||||
ROOT = Path(__file__).resolve().parents[1]
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
sys.path.insert(0, str(ROOT / "dev_scripts"))
|
sys.path.insert(0, str(ROOT / "dev_scripts"))
|
||||||
|
|
||||||
from new_task_archive import build_archive, safe_title # noqa: E402
|
from new_task_archive import ( # noqa: E402
|
||||||
|
build_archive,
|
||||||
|
main as new_archive_main,
|
||||||
|
safe_title,
|
||||||
|
)
|
||||||
|
from export_task_archives import ( # noqa: E402
|
||||||
|
existing_task_mirrors,
|
||||||
|
export_task_archives,
|
||||||
|
task_target,
|
||||||
|
)
|
||||||
from wiki_docs import ( # noqa: E402
|
from wiki_docs import ( # noqa: E402
|
||||||
Config,
|
Config,
|
||||||
Mapping,
|
Mapping,
|
||||||
@@ -158,6 +167,153 @@ class ArchiveTests(unittest.TestCase):
|
|||||||
self.assertIn("Task-12-login", result)
|
self.assertIn("Task-12-login", result)
|
||||||
self.assertNotIn("YYYY-MM-DD", result)
|
self.assertNotIn("YYYY-MM-DD", result)
|
||||||
|
|
||||||
|
@patch("new_task_archive.WikiClient")
|
||||||
|
def test_create_archive_does_not_change_core_mapping(self, client_class) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
config_path = Path(directory) / "wiki-docs.json"
|
||||||
|
original = json.dumps(
|
||||||
|
{
|
||||||
|
"schema_version": 1,
|
||||||
|
"gitea_url": "http://gitea.example",
|
||||||
|
"owner": "o",
|
||||||
|
"repository": "r",
|
||||||
|
"mappings": [
|
||||||
|
{"page": "Home", "path": "docs/README.md"}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
config_path.write_text(original, encoding="utf-8")
|
||||||
|
client = client_class.return_value
|
||||||
|
client.list_pages.return_value = []
|
||||||
|
client.get_page.return_value = WikiPage(
|
||||||
|
title="Task-Archive-Template",
|
||||||
|
sub_url="Task-Archive-Template.-",
|
||||||
|
text="# <工单号> <标题>\nYYYY-MM-DD\n<链接>\n<页面名>\n",
|
||||||
|
revision="a" * 40,
|
||||||
|
html_url="http://gitea.example/wiki/template",
|
||||||
|
)
|
||||||
|
client.create_page.return_value = WikiPage(
|
||||||
|
title="Task-14-按需导出",
|
||||||
|
sub_url="Task-14.-",
|
||||||
|
text="# 14 按需导出\n",
|
||||||
|
revision="b" * 40,
|
||||||
|
html_url="http://gitea.example/wiki/task-14",
|
||||||
|
)
|
||||||
|
with patch.object(
|
||||||
|
sys,
|
||||||
|
"argv",
|
||||||
|
[
|
||||||
|
"new_task_archive.py",
|
||||||
|
"14",
|
||||||
|
"按需导出",
|
||||||
|
"--config",
|
||||||
|
str(config_path),
|
||||||
|
],
|
||||||
|
):
|
||||||
|
result = new_archive_main()
|
||||||
|
self.assertEqual(config_path.read_text(encoding="utf-8"), original)
|
||||||
|
self.assertEqual(result, 0)
|
||||||
|
client.create_page.assert_called_once()
|
||||||
|
|
||||||
|
def test_task_target_uses_stable_safe_name(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
target = task_target("Task-14-修复:导出", Path(directory))
|
||||||
|
self.assertEqual(target.name, "14-修复-导出.md")
|
||||||
|
|
||||||
|
def test_existing_mirror_keeps_historical_custom_filename(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
path = root / "docs" / "task" / "2-初级维护者文档体系.md"
|
||||||
|
path.parent.mkdir(parents=True)
|
||||||
|
page = WikiPage(
|
||||||
|
title="Task-2-Junior-Maintainer-Docs",
|
||||||
|
sub_url="Task-2-Junior-Maintainer-Docs.-",
|
||||||
|
text="# 2 文档\n",
|
||||||
|
revision="c" * 40,
|
||||||
|
html_url="http://gitea.example/wiki/task-2",
|
||||||
|
)
|
||||||
|
path.write_text(render_mirror(page), encoding="utf-8")
|
||||||
|
mirrors = existing_task_mirrors(root)
|
||||||
|
self.assertEqual(
|
||||||
|
mirrors["Task-2-Junior-Maintainer-Docs"].name,
|
||||||
|
"2-初级维护者文档体系.md",
|
||||||
|
)
|
||||||
|
|
||||||
|
@patch("export_task_archives.dirty_paths", return_value=[])
|
||||||
|
def test_incremental_export_skips_same_revision(self, _dirty) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
path = root / "docs" / "task" / "14-按需导出.md"
|
||||||
|
path.parent.mkdir(parents=True)
|
||||||
|
page = WikiPage(
|
||||||
|
title="Task-14-按需导出",
|
||||||
|
sub_url="Task-14.-",
|
||||||
|
text="# 14 按需导出\n",
|
||||||
|
revision="d" * 40,
|
||||||
|
html_url="http://gitea.example/wiki/task-14",
|
||||||
|
)
|
||||||
|
path.write_text(render_mirror(page), encoding="utf-8")
|
||||||
|
client = Mock()
|
||||||
|
client.list_pages.return_value = [
|
||||||
|
{
|
||||||
|
"title": page.title,
|
||||||
|
"sub_url": page.sub_url,
|
||||||
|
"last_commit": {"sha": page.revision},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
messages = export_task_archives(client, root=root)
|
||||||
|
self.assertTrue(messages[0].startswith("跳过:"))
|
||||||
|
client.get_page_from_metadata.assert_not_called()
|
||||||
|
|
||||||
|
@patch(
|
||||||
|
"export_task_archives.dirty_paths",
|
||||||
|
return_value=[" M docs/task/14-按需导出.md"],
|
||||||
|
)
|
||||||
|
def test_export_stops_before_wiki_read_when_task_mirror_is_dirty(
|
||||||
|
self, _dirty
|
||||||
|
) -> None:
|
||||||
|
client = Mock()
|
||||||
|
with self.assertRaisesRegex(WikiDocsError, "未提交改动"):
|
||||||
|
export_task_archives(client)
|
||||||
|
client.list_pages.assert_not_called()
|
||||||
|
|
||||||
|
@patch("export_task_archives.dirty_paths", return_value=[])
|
||||||
|
def test_full_export_reads_all_and_never_deletes_extra_file(self, _dirty) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
task_dir = root / "docs" / "task"
|
||||||
|
task_dir.mkdir(parents=True)
|
||||||
|
extra = task_dir / "99-历史快照.md"
|
||||||
|
extra_page = WikiPage(
|
||||||
|
title="Task-99-历史快照",
|
||||||
|
sub_url="Task-99.-",
|
||||||
|
text="# 99 历史快照\n",
|
||||||
|
revision="e" * 40,
|
||||||
|
html_url="http://gitea.example/wiki/task-99",
|
||||||
|
)
|
||||||
|
extra.write_text(render_mirror(extra_page), encoding="utf-8")
|
||||||
|
page = WikiPage(
|
||||||
|
title="Task-14-按需导出",
|
||||||
|
sub_url="Task-14.-",
|
||||||
|
text="# 14 按需导出\n",
|
||||||
|
revision="f" * 40,
|
||||||
|
html_url="http://gitea.example/wiki/task-14",
|
||||||
|
)
|
||||||
|
client = Mock()
|
||||||
|
metadata = {
|
||||||
|
"title": page.title,
|
||||||
|
"sub_url": page.sub_url,
|
||||||
|
"last_commit": {"sha": page.revision},
|
||||||
|
}
|
||||||
|
client.list_pages.return_value = [metadata]
|
||||||
|
client.get_page_from_metadata.return_value = page
|
||||||
|
messages = export_task_archives(client, export_all=True, root=root)
|
||||||
|
exported = root / "docs" / "task" / "14-按需导出.md"
|
||||||
|
self.assertTrue(exported.is_file())
|
||||||
|
self.assertTrue(extra.is_file())
|
||||||
|
self.assertTrue(messages[0].startswith("已导出:"))
|
||||||
|
client.get_page_from_metadata.assert_called_once_with(metadata, page.title)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -55,58 +55,6 @@
|
|||||||
{
|
{
|
||||||
"page": "Task-Archive-Template",
|
"page": "Task-Archive-Template",
|
||||||
"path": "docs/templates/task-archive.md"
|
"path": "docs/templates/task-archive.md"
|
||||||
},
|
|
||||||
{
|
|
||||||
"page": "Task-1-Wiki-文档主源",
|
|
||||||
"path": "docs/task/1-Wiki-文档主源.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"page": "Task-2-Junior-Maintainer-Docs",
|
|
||||||
"path": "docs/task/2-初级维护者文档体系.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"page": "Task-3-Dev-Scripts-Rename",
|
|
||||||
"path": "docs/task/3-dev_scripts目录重命名.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"page": "Task-4-Agent-Efficiency-Scope",
|
|
||||||
"path": "docs/task/4-Agent效率与范围控制.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"page": "Task-5-Simplify-Agents",
|
|
||||||
"path": "docs/task/5-精简AGENTS重复说明.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"page": "Task-6-优化ClaudeCode规则入口",
|
|
||||||
"path": "docs/task/6-优化ClaudeCode规则入口.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"page": "Task-7-ClaudeCode模型路由",
|
|
||||||
"path": "docs/task/7-ClaudeCode模型路由.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"page": "Task-8-最小工单依赖规则",
|
|
||||||
"path": "docs/task/8-最小工单依赖规则.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"page": "Task-9-Agent自然语言快捷指令",
|
|
||||||
"path": "docs/task/9-Agent自然语言快捷指令.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"page": "Task-10-需求记录与流转规则",
|
|
||||||
"path": "docs/task/10-需求记录与流转规则.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"page": "Task-11-交付文档指南与岗位文档模板",
|
|
||||||
"path": "docs/task/11-交付文档指南与岗位文档模板.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"page": "Task-12-已有项目接入DevHarness指南",
|
|
||||||
"path": "docs/task/12-已有项目接入DevHarness指南.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"page": "Task-13-多子项目与独立交付单元",
|
|
||||||
"path": "docs/task/13-多子项目与独立交付单元.md"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user