chore(agent): 新增变更日志/checkpoint skill 与 post-commit hook
- AGENTS.md 新增变更日志 Skill 规范:变更后增量更新 document/work-log,更新前先做 Git 拉取检查 - 新增 .codex/skills 下 agent-change-log、git-checkpoint-commit 两个 skill - 新增 .githooks/post-commit 与 tools/agent-change-log 自动化脚本,提供提交后最低限度日志追加
This commit is contained in:
135
.codex/skills/agent-change-log/SKILL.md
Normal file
135
.codex/skills/agent-change-log/SKILL.md
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
---
|
||||||
|
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.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Agent Change Log
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
The log should sound like a careful teammate writing for tomorrow's teammate: concrete, warm, and honest.
|
||||||
|
|
||||||
|
## When To Use
|
||||||
|
|
||||||
|
- After fixing a bug, adding a feature, refactoring, changing behavior, editing documentation, or changing config.
|
||||||
|
- Before the final response for any turn that changed files.
|
||||||
|
- 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.
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
## Log Location
|
||||||
|
|
||||||
|
Use one file per day:
|
||||||
|
|
||||||
|
```text
|
||||||
|
document/work-log/YYYY-MM-DD.md
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Required Structure
|
||||||
|
|
||||||
|
Each daily file must use these sections in this order:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# YYYY-MM-DD 工作日志
|
||||||
|
|
||||||
|
## 当日工作内容
|
||||||
|
|
||||||
|
## 遗留问题
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
```
|
||||||
|
|
||||||
|
Keep this order stable. Add entries under the existing headings instead of creating duplicate headings.
|
||||||
|
|
||||||
|
## Required Git Check
|
||||||
|
|
||||||
|
Before writing or updating the daily log, manually check Git for upstream and local-ahead commits from other agents.
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
date '+%Y-%m-%d %H:%M:%S %Z'
|
||||||
|
git fetch --all --prune
|
||||||
|
git status -sb
|
||||||
|
git rev-parse --abbrev-ref --symbolic-full-name @{u}
|
||||||
|
git log --oneline --decorate --max-count=20 HEAD..@{u}
|
||||||
|
git log --oneline --decorate --max-count=20 @{u}..HEAD
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- Treat `git fetch --all --prune` as the default safe "pull check"; it updates remote refs without merging into a dirty worktree.
|
||||||
|
- 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.
|
||||||
|
- 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 there is no upstream branch, record that fact in `遗留问题` 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 `@{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.
|
||||||
|
|
||||||
|
## Entry Rules
|
||||||
|
|
||||||
|
1. Get the current local time first:
|
||||||
|
```bash
|
||||||
|
date '+%Y-%m-%d %H:%M:%S %Z'
|
||||||
|
```
|
||||||
|
2. Run the required Git check and capture whether upstream or local-ahead has new commits.
|
||||||
|
3. Append a new timestamped bullet under `## 当日工作内容`.
|
||||||
|
4. Mention Git commits, changed files or modules, the operation, the intent, and the verification result.
|
||||||
|
5. Add or update `## 遗留问题` whenever there is risk, uncertainty, skipped verification, design debt, Git divergence, or a likely follow-up.
|
||||||
|
6. Add or update `## TODO` with checkbox items.
|
||||||
|
7. When a TODO is completed, keep it in place and mark it as:
|
||||||
|
```markdown
|
||||||
|
- [x] ~~任务内容~~(完成于 HH:MM,证据:...)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Writing Style
|
||||||
|
|
||||||
|
- Write in Simplified Chinese.
|
||||||
|
- Be specific and a little human: "我把...", "这次先...", "还需要留意..." are good.
|
||||||
|
- 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.
|
||||||
|
- 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
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
- HH:MM:我完成了 <本次修改目标>。
|
||||||
|
- Git 提交检查:<git fetch 后 HEAD..upstream 与 upstream..HEAD 的结果;没有就写未发现 upstream 或本地 ahead 新提交>。
|
||||||
|
- 修改:<文件/模块>,<做了什么>。
|
||||||
|
- 操作:<运行了什么命令、迁移了什么状态、重启了什么服务等>。
|
||||||
|
- 验证:<测试/构建/检查结果;如果没跑,说明原因>。
|
||||||
|
- 影响:<用户可见变化或工程边界变化>。
|
||||||
|
```
|
||||||
|
|
||||||
|
## Leftover Issue Template
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
- HH:MM:<遗留问题或风险>。建议下一步 <具体建议>。
|
||||||
|
```
|
||||||
|
|
||||||
|
## TODO Template
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
- [ ] <下一步动作>(来源:HH:MM <上下文>)
|
||||||
|
- [x] ~~<已完成动作>~~(完成于 HH:MM,证据:<命令/文件/结果>)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Final Response Checklist
|
||||||
|
|
||||||
|
Before saying work is complete:
|
||||||
|
|
||||||
|
- Today's `document/work-log/YYYY-MM-DD.md` exists.
|
||||||
|
- The Git check ran, and upstream plus local-ahead commits from other agents were summarized or explicitly marked as absent.
|
||||||
|
- `当日工作内容` includes this modification batch.
|
||||||
|
- `遗留问题` is either updated with real risks or explicitly says no new leftover issue for this batch.
|
||||||
|
- `TODO` contains new follow-ups and completed items are checked with evidence.
|
||||||
|
- The final response mentions that the work log was updated.
|
||||||
4
.codex/skills/agent-change-log/agents/openai.yaml
Normal file
4
.codex/skills/agent-change-log/agents/openai.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
interface:
|
||||||
|
display_name: "Agent Change Log"
|
||||||
|
short_description: "Record X-Financial changes plus upstream/local commits in the daily work log"
|
||||||
|
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."
|
||||||
9
.githooks/post-commit
Executable file
9
.githooks/post-commit
Executable file
@@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Auto-append a minimal X-Financial agent work-log entry after each commit.
|
||||||
|
|
||||||
|
repo_root="$(git rev-parse --show-toplevel 2>/dev/null)" || exit 0
|
||||||
|
cd "$repo_root" || exit 0
|
||||||
|
|
||||||
|
python3 tools/agent-change-log/update_change_log.py \
|
||||||
|
--event "post-commit hook" \
|
||||||
|
>/tmp/x-financial-agent-change-log-hook.log 2>&1 || true
|
||||||
@@ -5,6 +5,15 @@
|
|||||||
- 所有分析、解释、计划、提交说明和最终回复默认使用简体中文。
|
- 所有分析、解释、计划、提交说明和最终回复默认使用简体中文。
|
||||||
- 技术结论要直击重点,必要时给出可验证的文件、命令或测试结果。
|
- 技术结论要直击重点,必要时给出可验证的文件、命令或测试结果。
|
||||||
|
|
||||||
|
## 变更日志 Skill 规范
|
||||||
|
|
||||||
|
- 每次修复 bug、新增功能、重构、修改配置或编辑项目文档后,必须调用项目级 Skill `agent-change-log`,并增量更新 `document/work-log/YYYY-MM-DD.md`。
|
||||||
|
- 更新日志前必须先执行 Git 拉取检查:默认运行 `git fetch --all --prune`、`git status -sb`、`git log HEAD..@{u}` 和 `git log @{u}..HEAD`;如果工作区干净且只落后上游,可以 `git pull --ff-only`。发现其他智能体已提交到上游或本地 ahead 提交时,要先把这些提交摘要分析进当天日志。
|
||||||
|
- 自动化触发由 `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` 后,提交后才会自动追加最低限度日志。
|
||||||
|
- 如果当前环境无法写 `.git` 或无法执行 `git fetch`,必须把这个限制写进当天日志;不能把 Skill/AGENTS 规则误当成已经有后台自动化。
|
||||||
|
- 日志必须保留 `当日工作内容`、`遗留问题`、`TODO` 三块;TODO 使用 Markdown checkbox,完成项勾选并用删除线保留历史。
|
||||||
|
- 记录要写清楚具体时间、改了什么、操作了什么、验证了什么、还遗留什么问题,以及建议下一步怎么处理。
|
||||||
|
|
||||||
## 通用代码拆分规范
|
## 通用代码拆分规范
|
||||||
|
|
||||||
无论写前端、后端还是算法代码,都必须主动避免“所有方法堆在一个类里 / 一个组件里 / 一个模块里”的写法。遇到类、组件或核心模块持续变大时,优先按职责拆分,而不是继续追加方法和状态。
|
无论写前端、后端还是算法代码,都必须主动避免“所有方法堆在一个类里 / 一个组件里 / 一个模块里”的写法。遇到类、组件或核心模块持续变大时,优先按职责拆分,而不是继续追加方法和状态。
|
||||||
|
|||||||
109
hermes/skills/domain/write-development-docs/SKILL.md
Normal file
109
hermes/skills/domain/write-development-docs/SKILL.md
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
---
|
||||||
|
name: write-development-docs
|
||||||
|
description: 为 X-Financial 落地开发文档。Use when a task asks to write, create, update, or standardize planning/development documentation under document/development, especially when the expected output is a CONCEPT.md capability document plus a TODO.md implementation checklist following this repository's existing development-doc format.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Write Development Docs
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
使用本技能为一个功能、重构、算法、页面或业务能力创建标准开发文档。
|
||||||
|
默认输出位置:
|
||||||
|
|
||||||
|
```text
|
||||||
|
document/development/<feature-slug>/
|
||||||
|
├── CONCEPT.md
|
||||||
|
└── TODO.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 工作流
|
||||||
|
|
||||||
|
1. 先阅读相邻或同类文档,优先参考 `document/development/*/CONCEPT.md` 和 `document/development/*/TODO.md`。
|
||||||
|
2. 读取与本次能力相关的代码、接口、页面、测试或历史文档,不凭空写实现细节。
|
||||||
|
3. 创建或更新 `CONCEPT.md`,先写清能力边界,再写方案和验收。
|
||||||
|
4. 创建或更新 `TODO.md`,每个任务必须能回链到 `CONCEPT.md` 的章节。
|
||||||
|
5. 如果已有实现,TODO 可以勾选 `[x]`,但必须写证据;没有验证的任务保持 `[ ]`。
|
||||||
|
6. 完成后检查两份文档是否互相一致,避免 TODO 承诺了 CONCEPT 没定义的能力。
|
||||||
|
|
||||||
|
## 命名规则
|
||||||
|
|
||||||
|
- 目录名使用小写 kebab-case,例如 `receipt-folder`、`agent-trace-center`。
|
||||||
|
- 能力名在正文中使用清晰中文,例如“票据夹功能”“Agent 链路追踪中心”。
|
||||||
|
- 两个文件名固定为 `CONCEPT.md` 和 `TODO.md`。
|
||||||
|
- 不额外创建 README、CHANGELOG、SUMMARY 等文件,除非用户明确要求。
|
||||||
|
|
||||||
|
## CONCEPT.md 格式
|
||||||
|
|
||||||
|
参考 `assets/CONCEPT.md` 模板。
|
||||||
|
|
||||||
|
必须包含:
|
||||||
|
|
||||||
|
- 标题:`# <功能名> 概念文档`
|
||||||
|
- `更新时间:YYYY-MM-DD`
|
||||||
|
- `## 功能一句话`
|
||||||
|
- `## 背景与问题`
|
||||||
|
- `## 目标与非目标`
|
||||||
|
- `## 用户与场景`
|
||||||
|
- `## 功能能力`
|
||||||
|
- `## 方案设计`
|
||||||
|
- `## 算法与公式`
|
||||||
|
- `## 测试方案`
|
||||||
|
- `## 指标与验收`
|
||||||
|
- `## 风险与开放问题`
|
||||||
|
|
||||||
|
如任务已经实现或正在收口,可追加:
|
||||||
|
|
||||||
|
- `## 本轮实现记录`
|
||||||
|
|
||||||
|
写法要求:
|
||||||
|
|
||||||
|
- 先讲业务问题和边界,再讲技术方案。
|
||||||
|
- 目标与非目标必须分开写,避免需求无限扩张。
|
||||||
|
- 方案设计按前端、后端、算法/规则、数据、权限、降级策略分块;没有的块可以写“当前不涉及”。
|
||||||
|
- 算法与公式必须明确“不涉及”或写出公式、变量说明和适用边界。
|
||||||
|
- 验收标准要可验证,不写空泛口号。
|
||||||
|
|
||||||
|
## TODO.md 格式
|
||||||
|
|
||||||
|
参考 `assets/TODO.md` 模板。
|
||||||
|
|
||||||
|
必须包含:
|
||||||
|
|
||||||
|
- 标题:`# <功能名> 开发 TODO`
|
||||||
|
- `更新时间:YYYY-MM-DD`
|
||||||
|
- `## 使用规则`
|
||||||
|
- 分阶段 checklist。
|
||||||
|
|
||||||
|
TODO 条目规则:
|
||||||
|
|
||||||
|
- 每条用 `- [ ]` 或 `- [x]`。
|
||||||
|
- 每条必须包含 `[CONCEPT: <章节名>]`。
|
||||||
|
- 已完成项必须补证据,格式为 `证据:<文件、接口、命令或验证结果>`。
|
||||||
|
- 没有真实证据时不得勾选 `[x]`。
|
||||||
|
- 阶段建议使用:
|
||||||
|
- `## 1. 调研与边界`
|
||||||
|
- `## 2. 契约与设计`
|
||||||
|
- `## 3. 后端实现`
|
||||||
|
- `## 4. 算法/规则实现`
|
||||||
|
- `## 5. 前端实现`
|
||||||
|
- `## 6. 测试与验证`
|
||||||
|
- `## 7. 文档收尾`
|
||||||
|
|
||||||
|
## 更新既有文档
|
||||||
|
|
||||||
|
更新已有 `CONCEPT.md` / `TODO.md` 时:
|
||||||
|
|
||||||
|
- 先读两份文件的全文。
|
||||||
|
- 新需求先补 `CONCEPT.md`,再补 `TODO.md`。
|
||||||
|
- 如果实现发生变化,同步更新“非目标”“风险与开放问题”“本轮实现记录”。
|
||||||
|
- 不删除历史证据;除非证据已经明显错误,才替换为新证据。
|
||||||
|
|
||||||
|
## 验收检查
|
||||||
|
|
||||||
|
交付前检查:
|
||||||
|
|
||||||
|
- `CONCEPT.md` 和 `TODO.md` 都存在。
|
||||||
|
- TODO 的 `[CONCEPT: ...]` 都能在 `CONCEPT.md` 找到对应章节或语义段落。
|
||||||
|
- 已勾选项都有证据。
|
||||||
|
- 文档没有遗留模板占位符,例如 `<功能名>`、`TODO`、`待补充`。
|
||||||
|
- 文档路径位于 `document/development/<feature-slug>/`。
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
interface:
|
||||||
|
display_name: "开发文档落地"
|
||||||
|
short_description: "按项目格式生成 CONCEPT 和 TODO 文档"
|
||||||
|
default_prompt: "Use $write-development-docs to create a development CONCEPT and TODO document for a new X-Financial feature."
|
||||||
132
hermes/skills/domain/write-development-docs/assets/CONCEPT.md
Normal file
132
hermes/skills/domain/write-development-docs/assets/CONCEPT.md
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
# <功能名> 概念文档
|
||||||
|
|
||||||
|
更新时间:<YYYY-MM-DD>
|
||||||
|
|
||||||
|
## 功能一句话
|
||||||
|
|
||||||
|
用一句话说明这个能力解决什么问题、服务谁、交付什么结果。
|
||||||
|
|
||||||
|
## 背景与问题
|
||||||
|
|
||||||
|
- 当前现状:
|
||||||
|
- 用户痛点:
|
||||||
|
- 业务影响:
|
||||||
|
- 为什么现在需要做:
|
||||||
|
|
||||||
|
## 目标与非目标
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
- [G1]
|
||||||
|
- [G2]
|
||||||
|
- [G3]
|
||||||
|
|
||||||
|
### 非目标
|
||||||
|
|
||||||
|
- [NG1] 本轮不做:
|
||||||
|
- [NG2] 本轮不改变:
|
||||||
|
- [NG3] 后续再评估:
|
||||||
|
|
||||||
|
## 用户与场景
|
||||||
|
|
||||||
|
- 目标用户:
|
||||||
|
- 使用入口:
|
||||||
|
- 核心场景:
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
- 异常场景:
|
||||||
|
-
|
||||||
|
|
||||||
|
## 功能能力
|
||||||
|
|
||||||
|
- [C1] 输入能力:
|
||||||
|
- [C2] 处理能力:
|
||||||
|
- [C3] 输出能力:
|
||||||
|
- [C4] 状态与权限:
|
||||||
|
- [C5] 边界与降级:
|
||||||
|
|
||||||
|
## 方案设计
|
||||||
|
|
||||||
|
### 前端
|
||||||
|
|
||||||
|
- 页面/组件:
|
||||||
|
- 交互状态:
|
||||||
|
- 展示规则:
|
||||||
|
- 降级处理:
|
||||||
|
|
||||||
|
### 后端
|
||||||
|
|
||||||
|
- 接口/服务:
|
||||||
|
- 权限与校验:
|
||||||
|
- 持久化:
|
||||||
|
- 降级处理:
|
||||||
|
|
||||||
|
### 算法与规则
|
||||||
|
|
||||||
|
- 输入:
|
||||||
|
- 流程:
|
||||||
|
- 输出:
|
||||||
|
- 解释:
|
||||||
|
|
||||||
|
### 数据与契约
|
||||||
|
|
||||||
|
- 核心字段:
|
||||||
|
- 状态枚举:
|
||||||
|
- 兼容策略:
|
||||||
|
- 版本/审计:
|
||||||
|
|
||||||
|
## 算法与公式
|
||||||
|
|
||||||
|
如果当前功能不涉及公式,写明:
|
||||||
|
|
||||||
|
当前功能不涉及显式数学公式。
|
||||||
|
|
||||||
|
如果涉及公式,使用如下格式:
|
||||||
|
|
||||||
|
$$
|
||||||
|
metric = input_a + input_b
|
||||||
|
$$
|
||||||
|
|
||||||
|
变量说明:
|
||||||
|
|
||||||
|
- $metric$:
|
||||||
|
- $input_a$:
|
||||||
|
- $input_b$:
|
||||||
|
|
||||||
|
## 测试方案
|
||||||
|
|
||||||
|
后端:
|
||||||
|
|
||||||
|
-
|
||||||
|
|
||||||
|
前端:
|
||||||
|
|
||||||
|
-
|
||||||
|
|
||||||
|
集成:
|
||||||
|
|
||||||
|
-
|
||||||
|
|
||||||
|
手工验证:
|
||||||
|
|
||||||
|
-
|
||||||
|
|
||||||
|
## 指标与验收
|
||||||
|
|
||||||
|
- [A1] 功能验收:
|
||||||
|
- [A2] 性能指标:
|
||||||
|
- [A3] 质量指标:
|
||||||
|
- [A4] 安全/权限指标:
|
||||||
|
- [A5] 可观测性:
|
||||||
|
|
||||||
|
## 风险与开放问题
|
||||||
|
|
||||||
|
- 风险:
|
||||||
|
- 已处理依赖:
|
||||||
|
- 待确认:
|
||||||
|
- 降级策略:
|
||||||
|
|
||||||
|
## 本轮实现记录
|
||||||
|
|
||||||
|
-
|
||||||
71
hermes/skills/domain/write-development-docs/assets/TODO.md
Normal file
71
hermes/skills/domain/write-development-docs/assets/TODO.md
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# <功能名> 开发 TODO
|
||||||
|
|
||||||
|
更新时间:<YYYY-MM-DD>
|
||||||
|
|
||||||
|
## 使用规则
|
||||||
|
|
||||||
|
- 每个 TODO 必须对应 `CONCEPT.md` 中的目标、能力、方案或验收点。
|
||||||
|
- 只有完成并验证后,才能把 `[ ]` 改成 `[x]`。
|
||||||
|
- 勾选时在任务后补充简短证据,例如文件、接口、命令或验证结果。
|
||||||
|
- 如果需求发生变化,先更新 `CONCEPT.md`,再调整本 TODO。
|
||||||
|
|
||||||
|
## 1. 调研与边界
|
||||||
|
|
||||||
|
- [ ] [CONCEPT: 背景与问题] 阅读相关页面、接口、服务、测试和历史文档,记录当前实现事实。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 目标与非目标] 确认本轮开发范围,写清楚不做项。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 风险与开放问题] 标记无法立即确认的依赖、风险和假设。
|
||||||
|
证据:
|
||||||
|
|
||||||
|
## 2. 契约与设计
|
||||||
|
|
||||||
|
- [ ] [CONCEPT: 功能能力] 定义输入、输出、状态、权限和边界条件。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 方案设计] 明确前端、后端、算法、数据的职责边界。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 算法与公式] 补全公式、变量解释或明确当前不涉及公式。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 指标与验收] 把验收标准转成可验证的检查点。
|
||||||
|
证据:
|
||||||
|
|
||||||
|
## 3. 后端实现
|
||||||
|
|
||||||
|
- [ ] [CONCEPT: 后端] 新增或调整 schema、service、endpoint、权限和持久化逻辑。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 数据与契约] 保持响应结构、状态枚举和兼容策略清晰。
|
||||||
|
证据:
|
||||||
|
|
||||||
|
## 4. 算法/规则实现
|
||||||
|
|
||||||
|
- [ ] [CONCEPT: 算法与规则] 实现核心处理流程、规则判断或计算逻辑。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 结果解释] 输出可读解释、证据链、贡献项或降级原因。
|
||||||
|
证据:
|
||||||
|
|
||||||
|
## 5. 前端实现
|
||||||
|
|
||||||
|
- [ ] [CONCEPT: 前端] 新增或调整页面、组件、服务 API 和视图模型。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 前端] 实现加载、空态、错误态、权限态和刷新。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 前端] 对齐现有企业级直角、低饱和、密集信息风格。
|
||||||
|
证据:
|
||||||
|
|
||||||
|
## 6. 测试与验证
|
||||||
|
|
||||||
|
- [ ] [CONCEPT: 测试方案] 补充后端 service/API 定向测试,容器内运行,超时控制在 60s 内。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 测试方案] 补充前端视图模型、路由、组件或构建验证。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 指标与验收] 记录验证命令、结果和未覆盖风险。
|
||||||
|
证据:
|
||||||
|
|
||||||
|
## 7. 文档收尾
|
||||||
|
|
||||||
|
- [ ] [CONCEPT: 指标与验收] 回看所有验收点,确认均有实现或验证证据。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 风险与开放问题] 更新剩余风险、后续任务和明确不做项。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 功能一句话] 确认最终实现没有偏离原始目标。
|
||||||
|
证据:
|
||||||
22
tools/agent-change-log/README.md
Normal file
22
tools/agent-change-log/README.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Agent Change Log Automation
|
||||||
|
|
||||||
|
这个目录提供 `agent-change-log` 的可执行部分。
|
||||||
|
|
||||||
|
## 手动写入一条日志
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 tools/agent-change-log/update_change_log.py --event "manual"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 安装提交后自动日志 hook
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tools/agent-change-log/install_post_commit_hook.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
安装后,每次 `git commit` 完成,`.githooks/post-commit` 会调用
|
||||||
|
`update_change_log.py`,把最近提交、Git 双向提交检查和触发时间追加到
|
||||||
|
`document/work-log/YYYY-MM-DD.md`。
|
||||||
|
|
||||||
|
注意:hook 只能覆盖“提交后自动记录”。没有提交的普通文件修改,仍需要执行代理在任务完成前按
|
||||||
|
`AGENTS.md` 和 `agent-change-log` 主动记录。
|
||||||
13
tools/agent-change-log/install_post_commit_hook.sh
Executable file
13
tools/agent-change-log/install_post_commit_hook.sh
Executable file
@@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Install the versioned agent change-log post-commit hook into this checkout.
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
repo_root="$(git rev-parse --show-toplevel)"
|
||||||
|
hook_path="$(git rev-parse --git-path hooks/post-commit)"
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$hook_path")"
|
||||||
|
cp "$repo_root/.githooks/post-commit" "$hook_path"
|
||||||
|
chmod +x "$hook_path"
|
||||||
|
|
||||||
|
printf 'installed_hook=%s\n' "$hook_path"
|
||||||
227
tools/agent-change-log/update_change_log.py
Executable file
227
tools/agent-change-log/update_change_log.py
Executable file
@@ -0,0 +1,227 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Append an incremental X-Financial agent work-log entry."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
SECTION_WORK = "## 当日工作内容"
|
||||||
|
SECTION_ISSUES = "## 遗留问题"
|
||||||
|
SECTION_TODO = "## TODO"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class CommandResult:
|
||||||
|
args: list[str]
|
||||||
|
returncode: int
|
||||||
|
stdout: str
|
||||||
|
stderr: str
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ok(self) -> bool:
|
||||||
|
return self.returncode == 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def message(self) -> str:
|
||||||
|
return (self.stderr.strip() or self.stdout.strip()).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def run(args: list[str], cwd: Path, timeout: int = 60) -> CommandResult:
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
args,
|
||||||
|
cwd=cwd,
|
||||||
|
text=True,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
timeout=timeout,
|
||||||
|
)
|
||||||
|
except subprocess.TimeoutExpired as exc:
|
||||||
|
return CommandResult(args, 124, exc.stdout or "", exc.stderr or "command timed out")
|
||||||
|
return CommandResult(args, result.returncode, result.stdout, result.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
def git(args: list[str], cwd: Path, timeout: int = 60) -> CommandResult:
|
||||||
|
return run(["git", *args], cwd, timeout=timeout)
|
||||||
|
|
||||||
|
|
||||||
|
def repo_root() -> Path:
|
||||||
|
result = run(["git", "rev-parse", "--show-toplevel"], Path.cwd())
|
||||||
|
if not result.ok:
|
||||||
|
raise SystemExit(f"Not inside a git repository: {result.message}")
|
||||||
|
return Path(result.stdout.strip())
|
||||||
|
|
||||||
|
|
||||||
|
def one_line(value: str) -> str:
|
||||||
|
return " ".join(value.strip().split())
|
||||||
|
|
||||||
|
|
||||||
|
def indent_block(text: str, limit: int = 8) -> list[str]:
|
||||||
|
lines = [line.strip() for line in text.splitlines() if line.strip()]
|
||||||
|
if not lines:
|
||||||
|
return ["未发现"]
|
||||||
|
shown = lines[:limit]
|
||||||
|
if len(lines) > limit:
|
||||||
|
shown.append(f"... 另有 {len(lines) - limit} 条")
|
||||||
|
return shown
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_document(path: Path, date_text: str) -> str:
|
||||||
|
if path.exists():
|
||||||
|
content = path.read_text(encoding="utf-8")
|
||||||
|
else:
|
||||||
|
content = f"# {date_text} 工作日志\n\n{SECTION_WORK}\n\n{SECTION_ISSUES}\n\n{SECTION_TODO}\n"
|
||||||
|
|
||||||
|
if not content.endswith("\n"):
|
||||||
|
content += "\n"
|
||||||
|
|
||||||
|
for heading in (SECTION_WORK, SECTION_ISSUES, SECTION_TODO):
|
||||||
|
if heading not in content:
|
||||||
|
content += f"\n{heading}\n"
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
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 collect_git(root: Path, *, no_fetch: bool, max_commits: int) -> dict[str, str | bool]:
|
||||||
|
data: dict[str, str | bool] = {}
|
||||||
|
|
||||||
|
fetch_result = CommandResult(["git", "fetch"], 0, "", "")
|
||||||
|
if no_fetch:
|
||||||
|
data["fetch"] = "已跳过 fetch(--no-fetch)"
|
||||||
|
else:
|
||||||
|
fetch_result = git(["fetch", "--all", "--prune"], root, timeout=60)
|
||||||
|
data["fetch"] = "成功" if fetch_result.ok else f"失败:{one_line(fetch_result.message)}"
|
||||||
|
|
||||||
|
status = git(["status", "-sb"], root)
|
||||||
|
data["status"] = status.stdout.strip() if status.ok else f"读取失败:{one_line(status.message)}"
|
||||||
|
|
||||||
|
upstream = git(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], root)
|
||||||
|
if upstream.ok:
|
||||||
|
data["upstream"] = upstream.stdout.strip()
|
||||||
|
behind = git(["log", "--oneline", "--decorate", f"--max-count={max_commits}", "HEAD..@{u}"], root)
|
||||||
|
ahead = git(["log", "--oneline", "--decorate", f"--max-count={max_commits}", "@{u}..HEAD"], root)
|
||||||
|
data["behind"] = behind.stdout.strip() if behind.ok else f"读取失败:{one_line(behind.message)}"
|
||||||
|
data["ahead"] = ahead.stdout.strip() if ahead.ok else f"读取失败:{one_line(ahead.message)}"
|
||||||
|
else:
|
||||||
|
data["upstream"] = ""
|
||||||
|
data["behind"] = "无 upstream 分支"
|
||||||
|
data["ahead"] = "无 upstream 分支"
|
||||||
|
|
||||||
|
head_hash = git(["rev-parse", "--short", "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_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
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def build_work_entry(now: datetime, event: str, git_data: dict[str, str | bool]) -> str:
|
||||||
|
time_text = now.strftime("%H:%M")
|
||||||
|
head_hash = str(git_data.get("head_hash") or "")
|
||||||
|
head_subject = str(git_data.get("head_subject") or "")
|
||||||
|
behind = str(git_data.get("behind") or "")
|
||||||
|
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(
|
||||||
|
[
|
||||||
|
f"- {time_text}:自动记录 `{head_hash}` 提交后的工作日志。({marker})",
|
||||||
|
f" - Git 提交检查:fetch {git_data.get('fetch')};upstream `{git_data.get('upstream') or '未配置'}`;upstream 新提交:{behind_text};本地 ahead 提交:{ahead_text}。",
|
||||||
|
f" - 修改:最近提交为 `{head_hash} {head_subject}`。",
|
||||||
|
f" - 操作:{event} 触发 `tools/agent-change-log/update_change_log.py`,自动读取 Git 状态并写入当天日志。",
|
||||||
|
" - 验证:自动脚本只记录提交和 Git 状态,不替代业务测试;业务验证仍以本次任务实际运行结果为准。",
|
||||||
|
" - 影响:提交后即使执行代理忘记手动写日志,也会留下最低限度的时间、提交和分支状态记录。",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_issue_entry(now: datetime, git_data: dict[str, str | bool]) -> str | None:
|
||||||
|
time_text = now.strftime("%H:%M")
|
||||||
|
issues: list[str] = []
|
||||||
|
if git_data.get("fetch_failed"):
|
||||||
|
issues.append(f"fetch 未成功:{git_data.get('fetch')}")
|
||||||
|
if not git_data.get("upstream"):
|
||||||
|
issues.append("当前分支没有 upstream,无法判断其他智能体是否已推送新提交")
|
||||||
|
if not issues:
|
||||||
|
return None
|
||||||
|
return f"- {time_text}:自动日志触发时发现 {';'.join(issues)}。建议后续在有 Git 写权限和网络权限的环境里重新执行拉取检查。"
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args() -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
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("--log-path", help="Override log file path.")
|
||||||
|
parser.add_argument("--dry-run", action="store_true", help="Print the entry without writing files.")
|
||||||
|
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("--max-commits", type=int, default=20, help="Maximum commits to summarize per direction.")
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
args = parse_args()
|
||||||
|
root = repo_root()
|
||||||
|
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"
|
||||||
|
|
||||||
|
git_data = collect_git(root, no_fetch=args.no_fetch, max_commits=args.max_commits)
|
||||||
|
entry = build_work_entry(now, args.event, git_data)
|
||||||
|
issue_entry = build_issue_entry(now, git_data)
|
||||||
|
marker = f"auto-log:{git_data.get('head_hash')}"
|
||||||
|
|
||||||
|
if args.dry_run:
|
||||||
|
print(entry)
|
||||||
|
if issue_entry:
|
||||||
|
print()
|
||||||
|
print(issue_entry)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
content = ensure_document(log_path, date_text)
|
||||||
|
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__":
|
||||||
|
raise SystemExit(main())
|
||||||
Reference in New Issue
Block a user