chore(logs): split agent change log outputs
This commit is contained in:
@@ -1,55 +1,63 @@
|
|||||||
---
|
---
|
||||||
name: agent-change-log
|
name: agent-change-log
|
||||||
description: Use when working in X-Financial after bug fixes, new features, refactors, behavior changes, documentation edits, config edits, concurrent-agent Git commits, or any codebase modification that should leave an incremental human-readable work log.
|
description: Use when working in X-Financial after bug fixes that need a split dev log, when maintaining document/development/YYYY-MM-DD/dev-logs/bugs, or when generating the daily 17:00 combined work-logs.med from same-day feature docs and bug logs.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Agent Change Log
|
# Agent Change Log
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
This skill keeps a living, incremental work log for X-Financial. After each meaningful modification batch, write down what changed locally, what other agents already committed upstream, what was operated on, what remains uncertain, and what should happen next.
|
This skill keeps split development logs for X-Financial. Bug fixes are recorded under the same-day `dev-logs/bugs` folder. Daily summaries combine same-day feature documents and bug records into one `work-logs.med`.
|
||||||
|
|
||||||
The log should sound like a careful teammate writing for tomorrow's teammate: concrete, warm, and honest.
|
The log should sound like a careful teammate writing for tomorrow's teammate: concrete, warm, and honest.
|
||||||
|
|
||||||
## When To Use
|
## When To Use
|
||||||
|
|
||||||
- After fixing a bug, adding a feature, refactoring, changing behavior, editing documentation, or changing config.
|
- After fixing a bug.
|
||||||
- Before the final response for any turn that changed files.
|
- When a post-commit hook detects a bug-like commit.
|
||||||
|
- At the daily 17:00 summary pass.
|
||||||
- Before updating the log in a branch where other agents may have pushed commits.
|
- Before updating the log in a branch where other agents may have pushed commits.
|
||||||
- After verification, so the entry can include what was actually checked.
|
- After verification, so the entry can include what was actually checked.
|
||||||
- When a failed attempt changed files, generated artifacts, or revealed a risk worth preserving.
|
- When a failed attempt changed files, generated artifacts, or revealed a risk worth preserving.
|
||||||
|
|
||||||
Do not use for read-only investigation with no file changes unless the user explicitly asks for a record.
|
Do not create legacy `document/work-log/YYYY-MM-DD.md` entries for new work.
|
||||||
|
|
||||||
## Log Location
|
## Log Location
|
||||||
|
|
||||||
Use one file per day:
|
Use the same date root as development documents:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
document/work-log/YYYY-MM-DD.md
|
document/development/YYYY-MM-DD/
|
||||||
|
├── feature/<feature-point>/CONCEPT.md + TODO.md
|
||||||
|
├── dev-logs/bugs/<bug-slug>.md
|
||||||
|
└── work-logs.med
|
||||||
```
|
```
|
||||||
|
|
||||||
If the file does not exist, create it. If it exists, incrementally update it. Never replace the whole file just to add a new entry.
|
If the date folder exists, reuse it. If not, create it.
|
||||||
|
|
||||||
## Required Structure
|
## Bug Log Structure
|
||||||
|
|
||||||
Each daily file must use these sections in this order:
|
For bug fixes, create or update one file per bug:
|
||||||
|
|
||||||
```markdown
|
```text
|
||||||
# YYYY-MM-DD 工作日志
|
document/development/YYYY-MM-DD/dev-logs/bugs/<bug-slug>.md
|
||||||
|
|
||||||
## 当日工作内容
|
|
||||||
|
|
||||||
## 遗留问题
|
|
||||||
|
|
||||||
## TODO
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Keep this order stable. Add entries under the existing headings instead of creating duplicate headings.
|
The file must contain `## 修复记录` and timestamped bullets similar to the old `当日工作内容`.
|
||||||
|
Do not add `遗留问题` or `TODO` sections to bug logs.
|
||||||
|
|
||||||
|
Use the helper when possible:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 tools/agent-change-log/update_change_log.py \
|
||||||
|
--kind bug \
|
||||||
|
--bug-title "<bug 名称>" \
|
||||||
|
--bug-slug <bug-slug>
|
||||||
|
```
|
||||||
|
|
||||||
## Required Git Check
|
## Required Git Check
|
||||||
|
|
||||||
Before writing or updating the daily log, manually check Git for upstream and local-ahead commits from other agents.
|
Before writing or updating a bug log, manually check Git for upstream and local-ahead commits from other agents.
|
||||||
|
|
||||||
Run:
|
Run:
|
||||||
|
|
||||||
@@ -68,28 +76,60 @@ Rules:
|
|||||||
- Treat `HEAD..@{u}` as upstream commits not yet in local history.
|
- Treat `HEAD..@{u}` as upstream commits not yet in local history.
|
||||||
- Treat `@{u}..HEAD` as local commits not yet in upstream history; these may also come from another agent working in the same checkout.
|
- Treat `@{u}..HEAD` as local commits not yet in upstream history; these may also come from another agent working in the same checkout.
|
||||||
- If the worktree is clean and the branch is only behind upstream, `git pull --ff-only` may be used to fast-forward before analysis.
|
- If the worktree is clean and the branch is only behind upstream, `git pull --ff-only` may be used to fast-forward before analysis.
|
||||||
- If the worktree is dirty, diverged, or likely to conflict, do not merge/rebase automatically. Record the upstream commits from `HEAD..@{u}` and the blocker in `遗留问题`.
|
- If the worktree is dirty, diverged, or likely to conflict, do not merge/rebase automatically. Record the upstream commits from `HEAD..@{u}` in the bug fix entry.
|
||||||
- If there is no upstream branch, record that fact in `遗留问题` and continue with local-only logging.
|
- If there is no upstream branch, record that fact in the bug fix entry and continue with local-only logging.
|
||||||
- When `HEAD..@{u}` has commits, summarize those commits under `当日工作内容` before describing local edits. Mention commit hash, subject, and inferred impact.
|
- When `HEAD..@{u}` has commits, summarize those commits in the bug fix entry before describing local edits. Mention commit hash, subject, and inferred impact.
|
||||||
- When `@{u}..HEAD` has commits that were not created in the current task, summarize them too, because another local agent may have committed without pushing yet.
|
- When `@{u}..HEAD` has commits that were not created in the current task, summarize them too, because another local agent may have committed without pushing yet.
|
||||||
- When no upstream or local-ahead commits exist, still record "Git 提交检查:未发现 upstream 新提交或本地 ahead 新提交" in the work entry.
|
- When no upstream or local-ahead commits exist, still record "Git 提交检查:未发现 upstream 新提交或本地 ahead 新提交" in the work entry.
|
||||||
|
|
||||||
## Entry Rules
|
## Bug Entry Rules
|
||||||
|
|
||||||
1. Get the current local time first:
|
1. Get the current local time first:
|
||||||
```bash
|
```bash
|
||||||
date '+%Y-%m-%d %H:%M:%S %Z'
|
date '+%Y-%m-%d %H:%M:%S %Z'
|
||||||
```
|
```
|
||||||
2. Run the required Git check and capture whether upstream or local-ahead has new commits.
|
2. Run the required Git check and capture whether upstream or local-ahead has new commits.
|
||||||
3. Append a new timestamped bullet under `## 当日工作内容`.
|
3. Ensure `document/development/YYYY-MM-DD/dev-logs/bugs/` exists.
|
||||||
4. Mention Git commits, changed files or modules, the operation, the intent, and the verification result.
|
4. Create or update one `<bug-slug>.md` file for the specific bug.
|
||||||
5. Add or update `## 遗留问题` whenever there is risk, uncertainty, skipped verification, design debt, Git divergence, or a likely follow-up.
|
5. Append a new timestamped bullet under `## 修复记录`.
|
||||||
6. Add or update `## TODO` with checkbox items.
|
6. Mention Git commits, changed files or modules, the operation, the intent, and the verification result.
|
||||||
7. When a TODO is completed, keep it in place and mark it as:
|
7. Do not add leftover issue or TODO sections.
|
||||||
```markdown
|
|
||||||
- [x] ~~任务内容~~(完成于 HH:MM,证据:...)
|
## Daily Summary
|
||||||
|
|
||||||
|
At 17:00 every day, generate the combined work log:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 tools/agent-change-log/update_change_log.py --kind summary
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This reads:
|
||||||
|
|
||||||
|
```text
|
||||||
|
document/development/YYYY-MM-DD/feature/
|
||||||
|
document/development/YYYY-MM-DD/dev-logs/bugs/
|
||||||
|
```
|
||||||
|
|
||||||
|
Then writes:
|
||||||
|
|
||||||
|
```text
|
||||||
|
document/development/YYYY-MM-DD/work-logs.med
|
||||||
|
```
|
||||||
|
|
||||||
|
The summary should cover:
|
||||||
|
|
||||||
|
- Today's feature points from `feature/*/CONCEPT.md` and `TODO.md`.
|
||||||
|
- Today's bug fixes from `dev-logs/bugs/*.md`.
|
||||||
|
- A concise combined analysis of what changed that day.
|
||||||
|
|
||||||
|
## Entry Rules
|
||||||
|
|
||||||
|
- For each bug entry, append a new timestamped bullet under `## 修复记录`.
|
||||||
|
- Mention Git commits, changed files or modules, the operation, the intent, and the verification result.
|
||||||
|
- Do not write `遗留问题`.
|
||||||
|
- Do not write `TODO`.
|
||||||
|
- If the change is a feature rather than a bug, use the development document skill to keep `feature/<feature-point>/CONCEPT.md` and `TODO.md` current instead of writing a bug log.
|
||||||
|
|
||||||
## Writing Style
|
## Writing Style
|
||||||
|
|
||||||
- Write in Simplified Chinese.
|
- Write in Simplified Chinese.
|
||||||
@@ -97,12 +137,11 @@ Rules:
|
|||||||
- Keep the tone factual. Do not turn the log into a victory lap.
|
- Keep the tone factual. Do not turn the log into a victory lap.
|
||||||
- Prefer concise file names and module names in prose, but include enough context to find the change.
|
- Prefer concise file names and module names in prose, but include enough context to find the change.
|
||||||
- Work content should be detailed enough that a future agent can continue without asking "你到底改了啥?"
|
- Work content should be detailed enough that a future agent can continue without asking "你到底改了啥?"
|
||||||
- Leftover issues should include a suggested next step, not only a complaint.
|
|
||||||
|
|
||||||
## Work Content Template
|
## Bug Entry Template
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
- HH:MM:我完成了 <本次修改目标>。
|
- HH:MM:记录 bug 修复:<bug 名称>。
|
||||||
- Git 提交检查:<git fetch 后 HEAD..upstream 与 upstream..HEAD 的结果;没有就写未发现 upstream 或本地 ahead 新提交>。
|
- Git 提交检查:<git fetch 后 HEAD..upstream 与 upstream..HEAD 的结果;没有就写未发现 upstream 或本地 ahead 新提交>。
|
||||||
- 修改:<文件/模块>,<做了什么>。
|
- 修改:<文件/模块>,<做了什么>。
|
||||||
- 操作:<运行了什么命令、迁移了什么状态、重启了什么服务等>。
|
- 操作:<运行了什么命令、迁移了什么状态、重启了什么服务等>。
|
||||||
@@ -110,26 +149,12 @@ Rules:
|
|||||||
- 影响:<用户可见变化或工程边界变化>。
|
- 影响:<用户可见变化或工程边界变化>。
|
||||||
```
|
```
|
||||||
|
|
||||||
## Leftover Issue Template
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
- HH:MM:<遗留问题或风险>。建议下一步 <具体建议>。
|
|
||||||
```
|
|
||||||
|
|
||||||
## TODO Template
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
- [ ] <下一步动作>(来源:HH:MM <上下文>)
|
|
||||||
- [x] ~~<已完成动作>~~(完成于 HH:MM,证据:<命令/文件/结果>)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Final Response Checklist
|
## Final Response Checklist
|
||||||
|
|
||||||
Before saying work is complete:
|
Before saying work is complete:
|
||||||
|
|
||||||
- Today's `document/work-log/YYYY-MM-DD.md` exists.
|
- For bug fixes, today's bug log exists under `document/development/YYYY-MM-DD/dev-logs/bugs/`.
|
||||||
- The Git check ran, and upstream plus local-ahead commits from other agents were summarized or explicitly marked as absent.
|
- For non-bug feature work, relevant `feature/<feature-point>` documents are current.
|
||||||
- `当日工作内容` includes this modification batch.
|
- Git check ran for bug logs, and upstream plus local-ahead commits were summarized or explicitly marked as absent.
|
||||||
- `遗留问题` is either updated with real risks or explicitly says no new leftover issue for this batch.
|
- No new legacy `document/work-log/YYYY-MM-DD.md` entry was created.
|
||||||
- `TODO` contains new follow-ups and completed items are checked with evidence.
|
- The final response mentions whether a bug log, feature document, or daily `work-logs.med` was updated.
|
||||||
- The final response mentions that the work log was updated.
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
interface:
|
interface:
|
||||||
display_name: "Agent Change Log"
|
display_name: "Agent Change Log"
|
||||||
short_description: "Record X-Financial changes plus upstream/local commits in the daily work log"
|
short_description: "Record bug logs and daily work summaries"
|
||||||
default_prompt: "Use $agent-change-log after a bug fix, feature, refactor, upstream/local commit check, or other file modification to update document/work-log/YYYY-MM-DD.md incrementally."
|
default_prompt: "Use $agent-change-log after an X-Financial bug fix or at 17:00 to update document/development/<date>/dev-logs/bugs or work-logs.med."
|
||||||
|
|||||||
@@ -5,5 +5,6 @@ repo_root="$(git rev-parse --show-toplevel 2>/dev/null)" || exit 0
|
|||||||
cd "$repo_root" || exit 0
|
cd "$repo_root" || exit 0
|
||||||
|
|
||||||
python3 tools/agent-change-log/update_change_log.py \
|
python3 tools/agent-change-log/update_change_log.py \
|
||||||
|
--kind auto \
|
||||||
--event "post-commit hook" \
|
--event "post-commit hook" \
|
||||||
>/tmp/x-financial-agent-change-log-hook.log 2>&1 || true
|
>/tmp/x-financial-agent-change-log-hook.log 2>&1 || true
|
||||||
|
|||||||
12
AGENTS.md
12
AGENTS.md
@@ -7,12 +7,12 @@
|
|||||||
|
|
||||||
## 变更日志 Skill 规范
|
## 变更日志 Skill 规范
|
||||||
|
|
||||||
- 每次修复 bug、新增功能、重构、修改配置或编辑项目文档后,必须调用项目级 Skill `agent-change-log`,并增量更新 `document/work-log/YYYY-MM-DD.md`。
|
- 每次修复 bug 后,必须调用项目级 Skill `agent-change-log`,并在 `document/development/YYYY-MM-DD/dev-logs/bugs/<bug-slug>.md` 记录该 bug 的修复内容。
|
||||||
- 更新日志前必须先执行 Git 拉取检查:默认运行 `git fetch --all --prune`、`git status -sb`、`git log HEAD..@{u}` 和 `git log @{u}..HEAD`;如果工作区干净且只落后上游,可以 `git pull --ff-only`。发现其他智能体已提交到上游或本地 ahead 提交时,要先把这些提交摘要分析进当天日志。
|
- 新增功能、重构、配置或项目文档变更不再写入旧的 `document/work-log/YYYY-MM-DD.md`;功能点默认沉淀到 `document/development/YYYY-MM-DD/feature/<具体功能点>/CONCEPT.md` 和 `TODO.md`。
|
||||||
- 自动化触发由 `tools/agent-change-log/update_change_log.py` 和 `.githooks/post-commit` 提供;新 checkout 需要执行 `tools/agent-change-log/install_post_commit_hook.sh` 安装到本地 `.git/hooks/post-commit` 后,提交后才会自动追加最低限度日志。
|
- 写 bug 日志前必须先执行 Git 拉取检查:默认运行 `git fetch --all --prune`、`git status -sb`、`git log HEAD..@{u}` 和 `git log @{u}..HEAD`。发现其他智能体已提交到上游或本地 ahead 提交时,要把这些提交摘要写进 bug 修复记录。
|
||||||
- 如果当前环境无法写 `.git` 或无法执行 `git fetch`,必须把这个限制写进当天日志;不能把 Skill/AGENTS 规则误当成已经有后台自动化。
|
- 自动化触发由 `tools/agent-change-log/update_change_log.py` 和 `.githooks/post-commit` 提供;新 checkout 需要执行 `tools/agent-change-log/install_post_commit_hook.sh` 安装到本地 `.git/hooks/post-commit` 后,提交后才会按 `--kind auto` 自动识别 bug-like commit 并写入 `dev-logs/bugs`。
|
||||||
- 日志必须保留 `当日工作内容`、`遗留问题`、`TODO` 三块;TODO 使用 Markdown checkbox,完成项勾选并用删除线保留历史。
|
- bug 日志只保留 `## 修复记录`,记录具体时间、改了什么、操作了什么、验证了什么和影响;不再写 `遗留问题` 和 `TODO` 两块。
|
||||||
- 记录要写清楚具体时间、改了什么、操作了什么、验证了什么、还遗留什么问题,以及建议下一步怎么处理。
|
- 每天 17:00 生成当天综合日志:读取 `document/development/YYYY-MM-DD/feature/` 和 `document/development/YYYY-MM-DD/dev-logs/bugs/`,分析功能点与 bug 修复后写入 `document/development/YYYY-MM-DD/work-logs.med`。
|
||||||
|
|
||||||
## 通用代码拆分规范
|
## 通用代码拆分规范
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
# 写日志技能拆分 概念文档
|
||||||
|
|
||||||
|
更新时间:2026-06-25
|
||||||
|
|
||||||
|
文档路径:document/development/2026-06-25/feature/agent-change-log-split/CONCEPT.md
|
||||||
|
|
||||||
|
## 功能一句话
|
||||||
|
|
||||||
|
把原来单文件三段式工作日志拆成按日期聚合的功能点文档、bug 修复日志和每日 17:00 综合工作日志。
|
||||||
|
|
||||||
|
## 背景与问题
|
||||||
|
|
||||||
|
- 当前现状:旧 `agent-change-log` 把所有变更追加到 `document/work-log/YYYY-MM-DD.md`,并固定包含 `当日工作内容`、`遗留问题`、`TODO`。
|
||||||
|
- 用户痛点:功能点规划、bug 修复和当天综合复盘混在一个日志文件里,后续追溯时难以按功能或问题拆开看。
|
||||||
|
- 业务影响:开发资料会越来越长,bug 修复证据和功能点设计边界容易互相干扰。
|
||||||
|
- 为什么现在需要做:`write-development-docs` 已经改为 `document/development/YYYY-MM-DD/feature/<功能点>/`,日志能力也需要跟随同一日期根目录拆分。
|
||||||
|
|
||||||
|
## 目标与非目标
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
- [G1] bug 修复记录落到 `document/development/YYYY-MM-DD/dev-logs/bugs/<bug-slug>.md`。
|
||||||
|
- [G2] bug 日志只保留修复记录,不再写 `遗留问题` 和 `TODO` 两块。
|
||||||
|
- [G3] 每天 17:00 汇总当天 `feature/` 和 `dev-logs/bugs/`,生成 `work-logs.med`。
|
||||||
|
- [G4] 保留 Git 双向检查,继续识别 upstream 新提交和本地 ahead 提交。
|
||||||
|
|
||||||
|
### 非目标
|
||||||
|
|
||||||
|
- [NG1] 本轮不迁移历史 `document/work-log/*.md`。
|
||||||
|
- [NG2] 本轮不删除旧历史日志,避免破坏既有追溯。
|
||||||
|
- [NG3] 本轮不把非 bug 提交强行写入 bug 日志。
|
||||||
|
|
||||||
|
## 用户与场景
|
||||||
|
|
||||||
|
- 目标用户:使用 Codex/Agent 维护 X-Financial 的开发者和后续接手的智能体。
|
||||||
|
- 使用入口:`agent-change-log` Skill、`tools/agent-change-log/update_change_log.py`、post-commit hook、每日 17:00 Codex automation。
|
||||||
|
- 核心场景:
|
||||||
|
1. 修复 bug 后写入当天 `dev-logs/bugs/<bug-slug>.md`。
|
||||||
|
2. 非 bug 功能点通过 `feature/<功能点>/CONCEPT.md` 和 `TODO.md` 沉淀。
|
||||||
|
3. 每天 17:00 汇总功能点与 bug,生成 `work-logs.med`。
|
||||||
|
- 异常场景:
|
||||||
|
- 没有 feature 或 bug 时,综合日志明确写“未发现”。
|
||||||
|
- 提交标题不像 bug 时,post-commit 自动日志跳过。
|
||||||
|
|
||||||
|
## 功能能力
|
||||||
|
|
||||||
|
- [C1] 输入能力:支持 `--kind bug`、`--kind auto`、`--kind summary` 三种模式。
|
||||||
|
- [C2] 处理能力:按日期创建 `dev-logs/bugs`,按 bug slug 记录修复内容。
|
||||||
|
- [C3] 输出能力:输出 bug 修复记录或每日 `work-logs.med`。
|
||||||
|
- [C4] 状态与权限:沿用 Git fetch/status/log 检查,不主动 merge/rebase。
|
||||||
|
- [C5] 边界与降级:官方 skill 校验脚本不可用时,用脚本单测、frontmatter 和 diff check 兜底。
|
||||||
|
|
||||||
|
## 方案设计
|
||||||
|
|
||||||
|
### 前端
|
||||||
|
|
||||||
|
当前不涉及。
|
||||||
|
|
||||||
|
### 后端
|
||||||
|
|
||||||
|
当前不涉及业务后端;只修改仓库级 Skill、脚本和 hook。
|
||||||
|
|
||||||
|
### 算法与规则
|
||||||
|
|
||||||
|
- 输入:commit subject、用户传入的 bug title/slug、当天 feature 和 bug 文档。
|
||||||
|
- 流程:bug 模式写入 bug 文件;auto 模式识别 bug-like commit;summary 模式扫描 `feature/` 和 `dev-logs/bugs/` 后生成综合日志。
|
||||||
|
- 输出:`dev-logs/bugs/*.md` 或 `work-logs.med`。
|
||||||
|
- 解释:summary 中保留来源目录和综合分析,便于复盘追溯。
|
||||||
|
|
||||||
|
### 数据与契约
|
||||||
|
|
||||||
|
- 核心字段:日期、bug slug、bug title、Git 提交检查、修改、操作、验证、影响。
|
||||||
|
- 状态枚举:`auto`、`bug`、`summary`。
|
||||||
|
- 兼容策略:保留旧 `document/work-log` 历史,不新增旧格式。
|
||||||
|
- 版本/审计:本轮变更通过本地 checkpoint commit 保留。
|
||||||
|
|
||||||
|
## 算法与公式
|
||||||
|
|
||||||
|
当前功能不涉及显式数学公式。
|
||||||
|
|
||||||
|
## 测试方案
|
||||||
|
|
||||||
|
后端:
|
||||||
|
|
||||||
|
- 当前不涉及后端服务。
|
||||||
|
|
||||||
|
前端:
|
||||||
|
|
||||||
|
- 当前不涉及前端构建。
|
||||||
|
|
||||||
|
集成:
|
||||||
|
|
||||||
|
- 运行 `python3 tools/agent-change-log/test_update_change_log.py`,覆盖 bug 日志、非 bug 自动跳过、每日综合日志聚合。
|
||||||
|
|
||||||
|
手工验证:
|
||||||
|
|
||||||
|
- 运行 `update_change_log.py --kind bug --dry-run` 和 `--kind summary --dry-run` 检查目标路径和输出内容。
|
||||||
|
|
||||||
|
## 指标与验收
|
||||||
|
|
||||||
|
- [A1] 功能验收:bug 日志路径为 `document/development/YYYY-MM-DD/dev-logs/bugs/<bug-slug>.md`。
|
||||||
|
- [A2] 性能指标:脚本单测在 60s 内完成。
|
||||||
|
- [A3] 质量指标:不再为新日志写入旧三段式 `document/work-log/YYYY-MM-DD.md`。
|
||||||
|
- [A4] 安全/权限指标:脚本不做 merge/rebase,不删除历史日志。
|
||||||
|
- [A5] 可观测性:17:00 automation 生成 `work-logs.med` 后报告路径和是否发现 feature/bug。
|
||||||
|
|
||||||
|
## 风险与开放问题
|
||||||
|
|
||||||
|
- 风险:`work-logs.med` 是按用户原文保留的扩展名,可能与常见 `.md` 扩展名不一致。
|
||||||
|
- 已处理依赖:已创建 Codex automation 执行每日 17:00 summary。
|
||||||
|
- 待确认:后续是否需要迁移历史 `document/work-log`。
|
||||||
|
- 降级策略:automation 不可用时可手动运行 `python3 tools/agent-change-log/update_change_log.py --kind summary`。
|
||||||
|
|
||||||
|
## 本轮实现记录
|
||||||
|
|
||||||
|
- 2026-06-25:重写 `agent-change-log` Skill 和脚本,新增 split log 测试,创建每日 17:00 Codex automation。
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# 写日志技能拆分 开发 TODO
|
||||||
|
|
||||||
|
更新时间:2026-06-25
|
||||||
|
|
||||||
|
文档路径:document/development/2026-06-25/feature/agent-change-log-split/TODO.md
|
||||||
|
|
||||||
|
## 使用规则
|
||||||
|
|
||||||
|
- 每个 TODO 必须对应 `CONCEPT.md` 中的目标、能力、方案或验收点。
|
||||||
|
- 只有完成并验证后,才能把 `[ ]` 改成 `[x]`。
|
||||||
|
- 勾选时在任务后补充简短证据,例如文件、接口、命令或验证结果。
|
||||||
|
- 如果需求发生变化,先更新 `CONCEPT.md`,再调整本 TODO。
|
||||||
|
|
||||||
|
## 1. 调研与边界
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 背景与问题] 确认旧日志 Skill、脚本、hook 和 AGENTS 仍指向 `document/work-log/YYYY-MM-DD.md`。
|
||||||
|
证据:`rg -n "document/work-log|当日工作内容|遗留问题|TODO" AGENTS.md .codex/skills/agent-change-log tools/agent-change-log .githooks/post-commit`。
|
||||||
|
- [x] [CONCEPT: 目标与非目标] 明确本轮不迁移历史 `document/work-log/*.md`。
|
||||||
|
证据:`CONCEPT.md` 非目标已列明历史不迁移。
|
||||||
|
|
||||||
|
## 2. 契约与设计
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 功能能力] 定义 `auto`、`bug`、`summary` 三种脚本模式。
|
||||||
|
证据:`tools/agent-change-log/update_change_log.py` 的 `--kind` 参数。
|
||||||
|
- [x] [CONCEPT: 数据与契约] 固定新路径 `document/development/YYYY-MM-DD/dev-logs/bugs` 与 `work-logs.med`。
|
||||||
|
证据:`agent-change-log` Skill、AGENTS 和脚本常量。
|
||||||
|
|
||||||
|
## 3. 后端实现
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 后端] 重写日志脚本,支持 bug 记录、auto 跳过非 bug、summary 聚合。
|
||||||
|
证据:`tools/agent-change-log/update_change_log.py`。
|
||||||
|
- [x] [CONCEPT: 后端] 更新 post-commit hook,改为 `--kind auto`。
|
||||||
|
证据:`.githooks/post-commit`。
|
||||||
|
|
||||||
|
## 4. 算法/规则实现
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 算法与规则] 实现 bug-like commit 识别规则。
|
||||||
|
证据:`looks_like_bug()` 覆盖 `fix`、`bugfix`、`修复`、`失败`、`异常` 等关键词。
|
||||||
|
- [x] [CONCEPT: 算法与规则] 实现 feature 和 bug 汇总生成 `work-logs.med`。
|
||||||
|
证据:`build_daily_summary()`。
|
||||||
|
|
||||||
|
## 5. 前端实现
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 前端] 当前不涉及前端页面。
|
||||||
|
证据:本轮只修改仓库 Skill、脚本、hook、文档和 automation。
|
||||||
|
|
||||||
|
## 6. 测试与验证
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 测试方案] 补充脚本回归测试。
|
||||||
|
证据:`tools/agent-change-log/test_update_change_log.py`。
|
||||||
|
- [x] [CONCEPT: 测试方案] 运行脚本单测。
|
||||||
|
证据:`python3 tools/agent-change-log/test_update_change_log.py`,3 tests OK。
|
||||||
|
- [x] [CONCEPT: 测试方案] dry-run 验证 bug 路径和 summary 路径。
|
||||||
|
证据:`--kind bug --dry-run` 输出 `document/development/2026-06-25/dev-logs/bugs/draft-preview-disappears.md`;`--kind summary --dry-run` 输出 `document/development/2026-06-25/work-logs.med`。
|
||||||
|
|
||||||
|
## 7. 文档收尾
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 指标与验收] 更新 `agent-change-log` Skill、AGENTS 和 README。
|
||||||
|
证据:`.codex/skills/agent-change-log/SKILL.md`、`AGENTS.md`、`tools/agent-change-log/README.md`。
|
||||||
|
- [x] [CONCEPT: 指标与验收] 创建每日 17:00 Codex automation。
|
||||||
|
证据:automation id `x-financial-daily-split-work-log`。
|
||||||
@@ -2,21 +2,51 @@
|
|||||||
|
|
||||||
这个目录提供 `agent-change-log` 的可执行部分。
|
这个目录提供 `agent-change-log` 的可执行部分。
|
||||||
|
|
||||||
## 手动写入一条日志
|
## 记录一次 bug 修复
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 tools/agent-change-log/update_change_log.py --event "manual"
|
python3 tools/agent-change-log/update_change_log.py \
|
||||||
|
--kind bug \
|
||||||
|
--bug-title "保存草稿失败后表格消失" \
|
||||||
|
--bug-slug draft-preview-disappears
|
||||||
```
|
```
|
||||||
|
|
||||||
## 安装提交后自动日志 hook
|
默认写入:
|
||||||
|
|
||||||
|
```text
|
||||||
|
document/development/YYYY-MM-DD/dev-logs/bugs/<bug-slug>.md
|
||||||
|
```
|
||||||
|
|
||||||
|
bug 日志只记录修复内容,不再写 `遗留问题` 和 `TODO`。
|
||||||
|
|
||||||
|
## 生成每日综合日志
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 tools/agent-change-log/update_change_log.py --kind summary
|
||||||
|
```
|
||||||
|
|
||||||
|
默认读取当天:
|
||||||
|
|
||||||
|
```text
|
||||||
|
document/development/YYYY-MM-DD/feature/
|
||||||
|
document/development/YYYY-MM-DD/dev-logs/bugs/
|
||||||
|
```
|
||||||
|
|
||||||
|
然后生成:
|
||||||
|
|
||||||
|
```text
|
||||||
|
document/development/YYYY-MM-DD/work-logs.med
|
||||||
|
```
|
||||||
|
|
||||||
|
## 安装提交后自动 bug 日志 hook
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
tools/agent-change-log/install_post_commit_hook.sh
|
tools/agent-change-log/install_post_commit_hook.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
安装后,每次 `git commit` 完成,`.githooks/post-commit` 会调用
|
安装后,每次 `git commit` 完成,`.githooks/post-commit` 会调用
|
||||||
`update_change_log.py`,把最近提交、Git 双向提交检查和触发时间追加到
|
`update_change_log.py --kind auto`。脚本只在提交标题看起来像 bug 修复时写入
|
||||||
`document/work-log/YYYY-MM-DD.md`。
|
`dev-logs/bugs`,例如 `fix:`、`bugfix:`、包含 `修复`、`缺陷`、`失败`、`异常`。
|
||||||
|
|
||||||
注意:hook 只能覆盖“提交后自动记录”。没有提交的普通文件修改,仍需要执行代理在任务完成前按
|
非 bug 提交会跳过,功能点仍通过 `document/development/YYYY-MM-DD/feature/`
|
||||||
`AGENTS.md` 和 `agent-change-log` 主动记录。
|
下的 `CONCEPT.md` 和 `TODO.md` 沉淀。
|
||||||
|
|||||||
109
tools/agent-change-log/test_update_change_log.py
Executable file
109
tools/agent-change-log/test_update_change_log.py
Executable file
@@ -0,0 +1,109 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
SCRIPT = Path(__file__).with_name("update_change_log.py").resolve()
|
||||||
|
|
||||||
|
|
||||||
|
def run(args: list[str], cwd: Path) -> subprocess.CompletedProcess[str]:
|
||||||
|
return subprocess.run(
|
||||||
|
args,
|
||||||
|
cwd=cwd,
|
||||||
|
text=True,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
timeout=60,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AgentChangeLogScriptTest(unittest.TestCase):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
self.tmp = tempfile.TemporaryDirectory()
|
||||||
|
self.root = Path(self.tmp.name)
|
||||||
|
run(["git", "init"], self.root).check_returncode()
|
||||||
|
run(["git", "config", "user.email", "agent@example.test"], self.root).check_returncode()
|
||||||
|
run(["git", "config", "user.name", "Agent"], self.root).check_returncode()
|
||||||
|
|
||||||
|
def tearDown(self) -> None:
|
||||||
|
self.tmp.cleanup()
|
||||||
|
|
||||||
|
def commit_file(self, subject: str) -> None:
|
||||||
|
target = self.root / "file.txt"
|
||||||
|
target.write_text(subject, encoding="utf-8")
|
||||||
|
run(["git", "add", "file.txt"], self.root).check_returncode()
|
||||||
|
run(["git", "commit", "-m", subject], self.root).check_returncode()
|
||||||
|
|
||||||
|
def test_bug_log_written_under_date_dev_logs_bugs(self) -> None:
|
||||||
|
self.commit_file("fix: keep draft preview after save failure")
|
||||||
|
|
||||||
|
result = run(
|
||||||
|
[
|
||||||
|
sys.executable,
|
||||||
|
str(SCRIPT),
|
||||||
|
"--kind",
|
||||||
|
"bug",
|
||||||
|
"--date",
|
||||||
|
"2026-06-25",
|
||||||
|
"--bug-title",
|
||||||
|
"保存草稿失败后表格消失",
|
||||||
|
"--bug-slug",
|
||||||
|
"draft-preview-disappears",
|
||||||
|
"--event",
|
||||||
|
"unit test",
|
||||||
|
"--no-fetch",
|
||||||
|
],
|
||||||
|
self.root,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
|
log_path = self.root / "document/development/2026-06-25/dev-logs/bugs/draft-preview-disappears.md"
|
||||||
|
content = log_path.read_text(encoding="utf-8")
|
||||||
|
self.assertIn("# 保存草稿失败后表格消失", content)
|
||||||
|
self.assertIn("## 修复记录", content)
|
||||||
|
self.assertIn("Git 提交检查", content)
|
||||||
|
self.assertNotIn("## 遗留问题", content)
|
||||||
|
self.assertNotIn("## TODO", content)
|
||||||
|
|
||||||
|
def test_auto_skips_non_bug_commit(self) -> None:
|
||||||
|
self.commit_file("docs: update development guide")
|
||||||
|
|
||||||
|
result = run([sys.executable, str(SCRIPT), "--kind", "auto", "--date", "2026-06-25", "--no-fetch"], self.root)
|
||||||
|
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
|
self.assertIn("skipped_non_bug_commit", result.stdout)
|
||||||
|
self.assertFalse((self.root / "document/development/2026-06-25/dev-logs/bugs").exists())
|
||||||
|
|
||||||
|
def test_daily_summary_combines_features_and_bugs(self) -> None:
|
||||||
|
feature = self.root / "document/development/2026-06-25/feature/receipt-folder-ocr"
|
||||||
|
feature.mkdir(parents=True)
|
||||||
|
feature.joinpath("CONCEPT.md").write_text(
|
||||||
|
"# 票据夹 OCR 概念文档\n\n## 功能一句话\n\n自动识别票据并生成可核对字段。\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
feature.joinpath("TODO.md").write_text("- [x] 已完成\n- [ ] 待验证\n", encoding="utf-8")
|
||||||
|
bug_dir = self.root / "document/development/2026-06-25/dev-logs/bugs"
|
||||||
|
bug_dir.mkdir(parents=True)
|
||||||
|
bug_dir.joinpath("draft-preview-disappears.md").write_text(
|
||||||
|
"# 保存草稿失败后表格消失\n\n## 修复记录\n\n- 09:30:记录 bug 修复。\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
self.commit_file("fix: create test commit")
|
||||||
|
|
||||||
|
result = run([sys.executable, str(SCRIPT), "--kind", "summary", "--date", "2026-06-25"], self.root)
|
||||||
|
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
|
summary = self.root.joinpath("document/development/2026-06-25/work-logs.med").read_text(encoding="utf-8")
|
||||||
|
self.assertIn("# 2026-06-25 综合工作日志", summary)
|
||||||
|
self.assertIn("票据夹 OCR 概念文档", summary)
|
||||||
|
self.assertIn("保存草稿失败后表格消失", summary)
|
||||||
|
self.assertIn("功能侧沉淀了 1 个功能点,问题侧记录了 1 个 bug 修复", summary)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -1,20 +1,19 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Append an incremental X-Financial agent work-log entry."""
|
"""Write split X-Financial development logs."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
SECTION_WORK = "## 当日工作内容"
|
BUG_SECTION = "## 修复记录"
|
||||||
SECTION_ISSUES = "## 遗留问题"
|
WORK_LOG_NAME = "work-logs.med"
|
||||||
SECTION_TODO = "## TODO"
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -63,7 +62,7 @@ def one_line(value: str) -> str:
|
|||||||
return " ".join(value.strip().split())
|
return " ".join(value.strip().split())
|
||||||
|
|
||||||
|
|
||||||
def indent_block(text: str, limit: int = 8) -> list[str]:
|
def compact_lines(text: str, limit: int = 8) -> list[str]:
|
||||||
lines = [line.strip() for line in text.splitlines() if line.strip()]
|
lines = [line.strip() for line in text.splitlines() if line.strip()]
|
||||||
if not lines:
|
if not lines:
|
||||||
return ["未发现"]
|
return ["未发现"]
|
||||||
@@ -73,35 +72,33 @@ def indent_block(text: str, limit: int = 8) -> list[str]:
|
|||||||
return shown
|
return shown
|
||||||
|
|
||||||
|
|
||||||
def ensure_document(path: Path, date_text: str) -> str:
|
def slugify(value: str, fallback: str) -> str:
|
||||||
if path.exists():
|
normalized = value.strip().lower()
|
||||||
content = path.read_text(encoding="utf-8")
|
chars: list[str] = []
|
||||||
else:
|
previous_dash = False
|
||||||
content = f"# {date_text} 工作日志\n\n{SECTION_WORK}\n\n{SECTION_ISSUES}\n\n{SECTION_TODO}\n"
|
for char in normalized:
|
||||||
|
keep = char.isascii() and char.isalnum()
|
||||||
if not content.endswith("\n"):
|
keep = keep or "\u4e00" <= char <= "\u9fff"
|
||||||
content += "\n"
|
if keep:
|
||||||
|
chars.append(char)
|
||||||
for heading in (SECTION_WORK, SECTION_ISSUES, SECTION_TODO):
|
previous_dash = False
|
||||||
if heading not in content:
|
elif not previous_dash:
|
||||||
content += f"\n{heading}\n"
|
chars.append("-")
|
||||||
return content
|
previous_dash = True
|
||||||
|
slug = "".join(chars).strip("-")
|
||||||
|
return slug or fallback
|
||||||
|
|
||||||
|
|
||||||
def insert_under_section(content: str, section: str, entry: str) -> str:
|
def date_dir(root: Path, date_text: str) -> Path:
|
||||||
marker = f"{section}\n"
|
return root / "document" / "development" / date_text
|
||||||
start = content.find(marker)
|
|
||||||
if start == -1:
|
|
||||||
return content.rstrip() + f"\n\n{section}\n\n{entry.rstrip()}\n"
|
|
||||||
|
|
||||||
insert_at = start + len(marker)
|
|
||||||
next_section = content.find("\n## ", insert_at)
|
def bugs_dir(root: Path, date_text: str) -> Path:
|
||||||
entry_text = "\n" + entry.rstrip() + "\n"
|
return date_dir(root, date_text) / "dev-logs" / "bugs"
|
||||||
if next_section == -1:
|
|
||||||
return content[:insert_at].rstrip() + entry_text
|
|
||||||
before = content[:next_section].rstrip()
|
def feature_dir(root: Path, date_text: str) -> Path:
|
||||||
after = content[next_section:]
|
return date_dir(root, date_text) / "feature"
|
||||||
return before + entry_text + after
|
|
||||||
|
|
||||||
|
|
||||||
def collect_git(root: Path, *, no_fetch: bool, max_commits: int) -> dict[str, str | bool]:
|
def collect_git(root: Path, *, no_fetch: bool, max_commits: int) -> dict[str, str | bool]:
|
||||||
@@ -133,56 +130,232 @@ def collect_git(root: Path, *, no_fetch: bool, max_commits: int) -> dict[str, st
|
|||||||
head_subject = git(["show", "-s", "--format=%s", "HEAD"], root)
|
head_subject = git(["show", "-s", "--format=%s", "HEAD"], root)
|
||||||
data["head_hash"] = head_hash.stdout.strip() if head_hash.ok else ""
|
data["head_hash"] = head_hash.stdout.strip() if head_hash.ok else ""
|
||||||
data["head_subject"] = head_subject.stdout.strip() if head_subject.ok else ""
|
data["head_subject"] = head_subject.stdout.strip() if head_subject.ok else ""
|
||||||
|
|
||||||
stat = git(["show", "--stat", "--oneline", "--decorate", "--max-count=1", "HEAD"], root)
|
|
||||||
data["head_stat"] = stat.stdout.strip() if stat.ok else ""
|
|
||||||
data["fetch_failed"] = not fetch_result.ok
|
data["fetch_failed"] = not fetch_result.ok
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def build_work_entry(now: datetime, event: str, git_data: dict[str, str | bool]) -> str:
|
def git_check_text(git_data: dict[str, str | bool]) -> str:
|
||||||
|
behind_text = ";".join(compact_lines(str(git_data.get("behind") or "")))
|
||||||
|
ahead_text = ";".join(compact_lines(str(git_data.get("ahead") or "")))
|
||||||
|
upstream = git_data.get("upstream") or "未配置"
|
||||||
|
return (
|
||||||
|
f"fetch {git_data.get('fetch')};upstream `{upstream}`;"
|
||||||
|
f"upstream 新提交:{behind_text};本地 ahead 提交:{ahead_text}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def looks_like_bug(subject: str) -> bool:
|
||||||
|
lower = subject.strip().lower()
|
||||||
|
if lower.startswith(("fix", "bugfix", "hotfix", "revert")):
|
||||||
|
return True
|
||||||
|
return any(keyword in lower for keyword in ("bug", "修复", "缺陷", "故障", "报错", "失败", "异常"))
|
||||||
|
|
||||||
|
|
||||||
|
def insert_under_section(content: str, section: str, entry: str) -> str:
|
||||||
|
marker = f"{section}\n"
|
||||||
|
start = content.find(marker)
|
||||||
|
if start == -1:
|
||||||
|
return content.rstrip() + f"\n\n{section}\n\n{entry.rstrip()}\n"
|
||||||
|
|
||||||
|
insert_at = start + len(marker)
|
||||||
|
next_section = content.find("\n## ", insert_at)
|
||||||
|
entry_text = "\n" + entry.rstrip() + "\n"
|
||||||
|
if next_section == -1:
|
||||||
|
return content[:insert_at].rstrip() + entry_text
|
||||||
|
before = content[:next_section].rstrip()
|
||||||
|
after = content[next_section:]
|
||||||
|
return before + entry_text + after
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_bug_document(path: Path, *, title: str, date_text: str) -> str:
|
||||||
|
if path.exists():
|
||||||
|
content = path.read_text(encoding="utf-8")
|
||||||
|
else:
|
||||||
|
rel_path = path.as_posix().split("document/development/", 1)[-1]
|
||||||
|
content = "\n".join(
|
||||||
|
[
|
||||||
|
f"# {title}",
|
||||||
|
"",
|
||||||
|
f"日期:{date_text}",
|
||||||
|
f"文档路径:document/development/{rel_path}",
|
||||||
|
"",
|
||||||
|
BUG_SECTION,
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
if not content.endswith("\n"):
|
||||||
|
content += "\n"
|
||||||
|
if BUG_SECTION not in content:
|
||||||
|
content += f"\n{BUG_SECTION}\n"
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
def build_bug_entry(now: datetime, event: str, title: str, git_data: dict[str, str | bool]) -> str:
|
||||||
time_text = now.strftime("%H:%M")
|
time_text = now.strftime("%H:%M")
|
||||||
head_hash = str(git_data.get("head_hash") or "")
|
head_hash = str(git_data.get("head_hash") or "")
|
||||||
head_subject = str(git_data.get("head_subject") or "")
|
head_subject = str(git_data.get("head_subject") or "")
|
||||||
behind = str(git_data.get("behind") or "")
|
marker = f"bug-log:{head_hash}" if head_hash else f"bug-log:{time_text}"
|
||||||
ahead = str(git_data.get("ahead") or "")
|
|
||||||
|
|
||||||
behind_lines = indent_block(behind)
|
|
||||||
ahead_lines = indent_block(ahead)
|
|
||||||
behind_text = ";".join(behind_lines)
|
|
||||||
ahead_text = ";".join(ahead_lines)
|
|
||||||
|
|
||||||
marker = f"auto-log:{head_hash}" if head_hash else "auto-log:no-head"
|
|
||||||
return "\n".join(
|
return "\n".join(
|
||||||
[
|
[
|
||||||
f"- {time_text}:自动记录 `{head_hash}` 提交后的工作日志。({marker})",
|
f"- {time_text}:记录 bug 修复:{title}。({marker})",
|
||||||
f" - Git 提交检查:fetch {git_data.get('fetch')};upstream `{git_data.get('upstream') or '未配置'}`;upstream 新提交:{behind_text};本地 ahead 提交:{ahead_text}。",
|
f" - Git 提交检查:{git_check_text(git_data)}。",
|
||||||
f" - 修改:最近提交为 `{head_hash} {head_subject}`。",
|
f" - 修改:最近提交为 `{head_hash} {head_subject}`。",
|
||||||
f" - 操作:{event} 触发 `tools/agent-change-log/update_change_log.py`,自动读取 Git 状态并写入当天日志。",
|
f" - 操作:{event} 触发 `tools/agent-change-log/update_change_log.py --kind bug`,写入当天 `dev-logs/bugs`。",
|
||||||
" - 验证:自动脚本只记录提交和 Git 状态,不替代业务测试;业务验证仍以本次任务实际运行结果为准。",
|
" - 验证:记录修复时应引用本次任务实际运行的测试、构建或手工验证结果;自动脚本不替代业务验证。",
|
||||||
" - 影响:提交后即使执行代理忘记手动写日志,也会留下最低限度的时间、提交和分支状态记录。",
|
" - 影响:该 bug 会在 17:00 综合日志中与当天功能点一起汇总。",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def build_issue_entry(now: datetime, git_data: dict[str, str | bool]) -> str | None:
|
def write_bug_log(args: argparse.Namespace, root: Path, now: datetime, git_data: dict[str, str | bool]) -> int:
|
||||||
time_text = now.strftime("%H:%M")
|
date_text = args.date or now.strftime("%Y-%m-%d")
|
||||||
issues: list[str] = []
|
head_hash = str(git_data.get("head_hash") or "")
|
||||||
if git_data.get("fetch_failed"):
|
head_subject = str(git_data.get("head_subject") or "")
|
||||||
issues.append(f"fetch 未成功:{git_data.get('fetch')}")
|
title = args.bug_title or head_subject or "未命名 bug 修复"
|
||||||
if not git_data.get("upstream"):
|
slug = slugify(args.bug_slug or title, fallback=f"bug-{head_hash or now.strftime('%H%M%S')}")
|
||||||
issues.append("当前分支没有 upstream,无法判断其他智能体是否已推送新提交")
|
path = Path(args.log_path) if args.log_path else bugs_dir(root, date_text) / f"{slug}.md"
|
||||||
if not issues:
|
entry = build_bug_entry(now, args.event, title, git_data)
|
||||||
return None
|
marker = f"bug-log:{head_hash}" if head_hash else ""
|
||||||
return f"- {time_text}:自动日志触发时发现 {';'.join(issues)}。建议后续在有 Git 写权限和网络权限的环境里重新执行拉取检查。"
|
|
||||||
|
if args.dry_run:
|
||||||
|
print(f"target_log={os.path.relpath(path, root)}")
|
||||||
|
print(entry)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
content = ensure_bug_document(path, title=title, date_text=date_text)
|
||||||
|
if marker and marker in content and not args.force:
|
||||||
|
print(f"Bug log already contains {marker}; skipping.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
content = insert_under_section(content, BUG_SECTION, entry)
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
path.write_text(content, encoding="utf-8")
|
||||||
|
print(f"updated_bug_log={os.path.relpath(path, root)}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def first_heading(path: Path) -> str:
|
||||||
|
for line in path.read_text(encoding="utf-8").splitlines():
|
||||||
|
if line.startswith("# "):
|
||||||
|
return line[2:].strip()
|
||||||
|
return path.parent.name
|
||||||
|
|
||||||
|
|
||||||
|
def section_first_text(content: str, section: str) -> str:
|
||||||
|
marker = f"## {section}"
|
||||||
|
start = content.find(marker)
|
||||||
|
if start == -1:
|
||||||
|
return ""
|
||||||
|
tail = content[start + len(marker) :].split("\n## ", 1)[0]
|
||||||
|
for line in tail.splitlines():
|
||||||
|
stripped = line.strip()
|
||||||
|
if stripped and not stripped.startswith("#"):
|
||||||
|
return stripped.strip("- ").strip()
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def summarize_feature(path: Path) -> str:
|
||||||
|
concept = path / "CONCEPT.md"
|
||||||
|
todo = path / "TODO.md"
|
||||||
|
title = path.name
|
||||||
|
sentence = ""
|
||||||
|
if concept.exists():
|
||||||
|
concept_text = concept.read_text(encoding="utf-8")
|
||||||
|
title = first_heading(concept)
|
||||||
|
sentence = section_first_text(concept_text, "功能一句话")
|
||||||
|
|
||||||
|
todo_text = todo.read_text(encoding="utf-8") if todo.exists() else ""
|
||||||
|
done_count = len(re.findall(r"^- \[x\]", todo_text, flags=re.MULTILINE | re.IGNORECASE))
|
||||||
|
open_count = len(re.findall(r"^- \[ \]", todo_text, flags=re.MULTILINE))
|
||||||
|
status = f"TODO 完成 {done_count} 项,未完成 {open_count} 项" if todo.exists() else "未找到 TODO.md"
|
||||||
|
detail = sentence or "未提取到功能一句话"
|
||||||
|
return f"- {title}:{detail}({status};目录:`{path.name}`)"
|
||||||
|
|
||||||
|
|
||||||
|
def summarize_bug(path: Path) -> str:
|
||||||
|
title = first_heading(path)
|
||||||
|
content = path.read_text(encoding="utf-8")
|
||||||
|
latest = ""
|
||||||
|
for line in content.splitlines():
|
||||||
|
stripped = line.strip()
|
||||||
|
if stripped.startswith("- ") and ":" in stripped:
|
||||||
|
latest = stripped[2:]
|
||||||
|
return f"- {title}:{latest or '已记录修复内容'}(文件:`{path.name}`)"
|
||||||
|
|
||||||
|
|
||||||
|
def build_daily_summary(root: Path, date_text: str, now: datetime) -> str:
|
||||||
|
features = []
|
||||||
|
feature_root = feature_dir(root, date_text)
|
||||||
|
if feature_root.exists():
|
||||||
|
for path in sorted(feature_root.iterdir()):
|
||||||
|
if path.is_dir():
|
||||||
|
features.append(summarize_feature(path))
|
||||||
|
|
||||||
|
bugs = []
|
||||||
|
bug_root = bugs_dir(root, date_text)
|
||||||
|
if bug_root.exists():
|
||||||
|
for path in sorted(bug_root.glob("*.md")):
|
||||||
|
bugs.append(summarize_bug(path))
|
||||||
|
|
||||||
|
feature_text = "\n".join(features) if features else "- 今日未发现功能点文档。"
|
||||||
|
bug_text = "\n".join(bugs) if bugs else "- 今日未发现 bug 修复记录。"
|
||||||
|
analysis_parts = []
|
||||||
|
if features:
|
||||||
|
analysis_parts.append(f"功能侧沉淀了 {len(features)} 个功能点")
|
||||||
|
if bugs:
|
||||||
|
analysis_parts.append(f"问题侧记录了 {len(bugs)} 个 bug 修复")
|
||||||
|
analysis = ",".join(analysis_parts) if analysis_parts else "今日目录下暂无功能点或 bug 记录"
|
||||||
|
|
||||||
|
return "\n".join(
|
||||||
|
[
|
||||||
|
f"# {date_text} 综合工作日志",
|
||||||
|
"",
|
||||||
|
f"生成时间:{now.strftime('%Y-%m-%d %H:%M:%S %Z')}",
|
||||||
|
"来源:`feature/` 功能点文档与 `dev-logs/bugs/` bug 记录",
|
||||||
|
"",
|
||||||
|
"## 今日功能点",
|
||||||
|
"",
|
||||||
|
feature_text,
|
||||||
|
"",
|
||||||
|
"## 今日 Bugs",
|
||||||
|
"",
|
||||||
|
bug_text,
|
||||||
|
"",
|
||||||
|
"## 综合分析",
|
||||||
|
"",
|
||||||
|
f"- {analysis}。",
|
||||||
|
"- 后续复盘优先看本文件,再回到对应功能点或 bug 文件追溯证据。",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def write_daily_summary(args: argparse.Namespace, root: Path, now: datetime) -> int:
|
||||||
|
date_text = args.date or now.strftime("%Y-%m-%d")
|
||||||
|
path = Path(args.log_path) if args.log_path else date_dir(root, date_text) / WORK_LOG_NAME
|
||||||
|
content = build_daily_summary(root, date_text, now)
|
||||||
|
|
||||||
|
if args.dry_run:
|
||||||
|
print(f"target_log={os.path.relpath(path, root)}")
|
||||||
|
print(content)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
path.write_text(content, encoding="utf-8")
|
||||||
|
print(f"updated_work_log={os.path.relpath(path, root)}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def parse_args() -> argparse.Namespace:
|
def parse_args() -> argparse.Namespace:
|
||||||
parser = argparse.ArgumentParser(description=__doc__)
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("--kind", choices=("auto", "bug", "summary"), default="auto")
|
||||||
parser.add_argument("--event", default="manual", help="Human-readable trigger source.")
|
parser.add_argument("--event", default="manual", help="Human-readable trigger source.")
|
||||||
parser.add_argument("--date", help="Override log date YYYY-MM-DD.")
|
parser.add_argument("--date", help="Override log date YYYY-MM-DD.")
|
||||||
parser.add_argument("--log-path", help="Override log file path.")
|
parser.add_argument("--log-path", help="Override output log path.")
|
||||||
parser.add_argument("--dry-run", action="store_true", help="Print the entry without writing files.")
|
parser.add_argument("--bug-title", help="Human-readable bug title.")
|
||||||
|
parser.add_argument("--bug-slug", help="Stable bug log file slug.")
|
||||||
|
parser.add_argument("--dry-run", action="store_true", help="Print output without writing files.")
|
||||||
parser.add_argument("--force", action="store_true", help="Write even if this commit marker already exists.")
|
parser.add_argument("--force", action="store_true", help="Write even if this commit marker already exists.")
|
||||||
parser.add_argument("--no-fetch", action="store_true", help="Skip git fetch; useful for tests or offline hooks.")
|
parser.add_argument("--no-fetch", action="store_true", help="Skip git fetch; useful for tests or offline hooks.")
|
||||||
parser.add_argument("--max-commits", type=int, default=20, help="Maximum commits to summarize per direction.")
|
parser.add_argument("--max-commits", type=int, default=20, help="Maximum commits to summarize per direction.")
|
||||||
@@ -193,34 +366,18 @@ def main() -> int:
|
|||||||
args = parse_args()
|
args = parse_args()
|
||||||
root = repo_root()
|
root = repo_root()
|
||||||
now = datetime.now().astimezone()
|
now = datetime.now().astimezone()
|
||||||
date_text = args.date or now.strftime("%Y-%m-%d")
|
|
||||||
log_path = Path(args.log_path) if args.log_path else root / "document" / "work-log" / f"{date_text}.md"
|
if args.kind == "summary":
|
||||||
|
return write_daily_summary(args, root, now)
|
||||||
|
|
||||||
git_data = collect_git(root, no_fetch=args.no_fetch, max_commits=args.max_commits)
|
git_data = collect_git(root, no_fetch=args.no_fetch, max_commits=args.max_commits)
|
||||||
entry = build_work_entry(now, args.event, git_data)
|
subject = str(git_data.get("head_subject") or "")
|
||||||
issue_entry = build_issue_entry(now, git_data)
|
|
||||||
marker = f"auto-log:{git_data.get('head_hash')}"
|
|
||||||
|
|
||||||
if args.dry_run:
|
if args.kind == "auto" and not looks_like_bug(subject):
|
||||||
print(entry)
|
print(f"skipped_non_bug_commit={subject}")
|
||||||
if issue_entry:
|
|
||||||
print()
|
|
||||||
print(issue_entry)
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
content = ensure_document(log_path, date_text)
|
return write_bug_log(args, root, now, git_data)
|
||||||
if marker in content and not args.force:
|
|
||||||
print(f"Work log already contains {marker}; skipping.")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
content = insert_under_section(content, SECTION_WORK, entry)
|
|
||||||
if issue_entry:
|
|
||||||
content = insert_under_section(content, SECTION_ISSUES, issue_entry)
|
|
||||||
|
|
||||||
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
log_path.write_text(content, encoding="utf-8")
|
|
||||||
print(f"updated_log={os.path.relpath(log_path, root)}")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user