Compare commits
10 Commits
a6daaff0fa
...
fa3c6443c7
| Author | SHA1 | Date | |
|---|---|---|---|
| fa3c6443c7 | |||
| 9cdd943ab9 | |||
| 84cb0cf2a9 | |||
| c0717e9a24 | |||
| ba0edce38c | |||
| ad38360f4e | |||
| 05645b3afc | |||
| f979c2d48b | |||
| 98a3276ecb | |||
| c4fa0eadcc |
@@ -1 +1,7 @@
|
||||
refs/ 里有 gitea API 的 spec
|
||||
|
||||
版本号规则:
|
||||
- 当前输出文档版本号固定为 `v1`
|
||||
- 如果用户没有明确要求 bump 版本号,禁止修改版本号
|
||||
|
||||
默认在工作到合适的时候就自己进行提交即可.
|
||||
|
||||
@@ -54,12 +54,29 @@ gitea-pr-review render-md --in pr.json
|
||||
gitea-pr-review render-md --in pr.json --out pr.md
|
||||
```
|
||||
|
||||
### 3) Show current document version
|
||||
|
||||
```bash
|
||||
gitea-pr-review version
|
||||
# v1
|
||||
```
|
||||
|
||||
## Skill For LLMs
|
||||
|
||||
This repository includes a reusable skill for other LLMs to operate this CLI correctly:
|
||||
|
||||
- Path: `skills/gitea-pr-review-cli/SKILL.md`
|
||||
- Scope: standardized usage of `fetch` / `render-md` / `version`, `v1` version contract, and troubleshooting
|
||||
- Path: `skills/organized-feedback/SKILL.md`
|
||||
- Scope: organize `gitea-pr-review` PR comments into structured feedback with coverage auditing and unknown-item handling
|
||||
|
||||
## Markdown Output Shape (Example)
|
||||
|
||||
````md
|
||||
# <repo> `#<pr-index>` <pr-title>
|
||||
|
||||
> Numbering: Review `<pr>.<review>`; Comment `<pr>.<review>.<comment>`; Reply `<pr>.<review>.<comment>.<reply>`
|
||||
> version: v1
|
||||
> fetched at: <fetch-time-rfc3339>
|
||||
|
||||
<pr description>
|
||||
|
||||
@@ -54,12 +54,29 @@ gitea-pr-review render-md --in pr.json
|
||||
gitea-pr-review render-md --in pr.json --out pr.md
|
||||
```
|
||||
|
||||
### 3) 查看当前文档版本
|
||||
|
||||
```bash
|
||||
gitea-pr-review version
|
||||
# v1
|
||||
```
|
||||
|
||||
## 给 LLM 的 Skill
|
||||
|
||||
仓库内已提供一个可复用 skill,帮助其他 LLM 正确使用本 CLI:
|
||||
|
||||
- 路径:`skills/gitea-pr-review-cli/SKILL.md`
|
||||
- 用途:标准化 `fetch` / `render-md` / `version` 的调用方式、`v1` 版本约束和常见错误排查
|
||||
- 路径:`skills/organized-feedback/SKILL.md`
|
||||
- 用途:将 `gitea-pr-review` 生成的 PR 评论整理为结构化反馈,并执行覆盖审计与未知项处理
|
||||
|
||||
## Markdown 输出结构(示例)
|
||||
|
||||
````md
|
||||
# <repo> `#<pr-index>` <pr-title>
|
||||
|
||||
> 编号规则:Review `<pr>.<review>`;Comment `<pr>.<review>.<comment>`;Reply `<pr>.<review>.<comment>.<reply>`
|
||||
> version: v1
|
||||
> fetched at: <fetch-time-rfc3339>
|
||||
|
||||
<pr 描述>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
我想要编写一个 skill, 来对杂乱的 PR review 意见进行分类. 我初步决定把审阅者的 comment 分为以下几类:
|
||||
|
||||
1. 问题 (question): 审阅者不理解某处代码的意图, 提出了需要得到解释的问题
|
||||
2. 补充 (supplement): 审阅者理解了某处代码, 但觉得它可能不容易被其他人理解, 因此补充了一些信息
|
||||
3. 修正 (request-for-change): 审阅者认为此处代码或设计可能存在缺陷, 需要修改
|
||||
|
||||
我又想把修改按照两个维度分类: 改动性质 和 必要性. 改动大小可以分为:
|
||||
1. 局部修改 (local): 可以在 30s 内完成的小任务. 如标识符重命名, 换用某种语法糖, 调整空行和顺序, 调整模块层次结构.
|
||||
2. 实现方式修改 (implement): 涉及业务流程的代码变动. 如修改同步点, 修改数据库读写事务, 修改某个功能的实现方式. 这种层次的修改一般需要补充对应的测试以表明旧方式的错误之处.
|
||||
3. API 设计修改 (api-change): 涉及某些使用较为广泛 (对内使用超过四处或对外的) 接口的修改, 一般需要改动对应的测试使其适应新接口.
|
||||
4. 需求修改 (requirement-change): 审阅者发现某些或是全部代码解决的需求就是不合理的, 需要重新进行需求评估.
|
||||
|
||||
必要性可以分为:
|
||||
1. 小建议 (nice-to-have): 在完成了所有其他 request 之后才考虑进行的修改. 比如一些内容空泛的建议, 或是增加小包装/小辅助函数.
|
||||
2. 应当修复 (should-fix): 除非有合理理由, 否则应该解决此问题. 合理的理由举例: "该问题可能并不会被触发" 的证明; "我们知道该问题触发了会有影响, 但不会很大" 的说明; 留待下一个 PR 实现.
|
||||
3. 必须修复 (must-fix): 不解决该问题, 此 PR 绝不能被合并.
|
||||
|
||||
这个 skill 应该对本项目提取出的 pr review 意见 markdown 里非结构化的, 比较随意的, 混杂了上述所有种类的审阅者意见进行整理, 为每条或一组相关意见整理为按上述体系分类好的 "意见", 格式类似于:
|
||||
|
||||
```md
|
||||
> Question
|
||||
> <审阅者> 请求对 xxx (做法/概念/数据结构) 做出解释.
|
||||
... (相关原始审核意见)
|
||||
```
|
||||
|
||||
```md
|
||||
> Supplement
|
||||
> <审阅者> 对 xxx 做了补充说明: (...大意). 考虑是否补充到代码注释或文档之中.
|
||||
... (相关原始审核意见)
|
||||
```
|
||||
|
||||
```md
|
||||
> Request-for-change, local, nice-to-have
|
||||
> <审阅者> 要求做 xxx.
|
||||
... (相关原始审核意见)
|
||||
```
|
||||
|
||||
```md
|
||||
> Request-for-change, implement, should-fix
|
||||
> <审阅者> 认为 (某某实现方式) (在某某情况下) (可能/必定) 会出现 (某某问题) (, 建议改为某某)
|
||||
... (相关原始审核意见)
|
||||
```
|
||||
|
||||
最后的输出应该是一个一条条的 markdown 文档.
|
||||
@@ -0,0 +1,375 @@
|
||||
# Organized Feedback Skill Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Add a reusable skill that organizes noisy PR review comments into structured feedback with mandatory coverage auditing in interaction and file output to a user-specified path.
|
||||
|
||||
**Architecture:** Implement this feature as a pure prompt skill under `skills/` with explicit workflow gates: input validation, classify/group, coverage audit loop, unknown reflection loop, and output write gate. Keep runtime behavior in instructions (no binary changes), and provide fixtures plus a validation script so contributors can quickly verify the skill contract after edits.
|
||||
|
||||
**Tech Stack:** Markdown skill spec, Bash validation script, existing repository docs
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
- Create: `skills/organized-feedback/SKILL.md`
|
||||
- Create: `skills/organized-feedback/examples/input-pr-review.md`
|
||||
- Create: `skills/organized-feedback/examples/output-organized-feedback.md`
|
||||
- Create: `scripts/validate_organized_feedback_skill.sh`
|
||||
- Modify: `README.md`
|
||||
- Modify: `README.en.md`
|
||||
|
||||
### Task 1: Create the skill contract file with hard gates
|
||||
|
||||
**Files:**
|
||||
- Create: `skills/organized-feedback/SKILL.md`
|
||||
- Test: `scripts/validate_organized_feedback_skill.sh`
|
||||
|
||||
- [ ] **Step 1: Write the failing validator script first (checks missing skill file)**
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_FILE="skills/organized-feedback/SKILL.md"
|
||||
|
||||
if [[ ! -f "$SKILL_FILE" ]]; then
|
||||
echo "FAIL: missing $SKILL_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "PASS: skill file exists"
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run validator to verify it fails before skill creation**
|
||||
|
||||
Run: `bash scripts/validate_organized_feedback_skill.sh`
|
||||
Expected: `FAIL: missing skills/organized-feedback/SKILL.md`
|
||||
|
||||
- [ ] **Step 3: Create `SKILL.md` metadata and workflow skeleton**
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: organized-feedback
|
||||
description: Organize unstructured PR review comments into classified feedback with mandatory coverage checks and unknown-item reflection.
|
||||
---
|
||||
|
||||
# Organized Feedback Skill
|
||||
|
||||
## Overview
|
||||
Use this skill to convert `gitea-pr-review` markdown into `Organized Feedback` markdown.
|
||||
|
||||
## Hard Gates
|
||||
- Do not write final output until user provides output path.
|
||||
- Coverage audit must run in interaction; every source comment/reply must be covered.
|
||||
- Coverage audit content must not be written to final document.
|
||||
- If unknown count is greater than 3, pause and request user permission before writing file.
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Extend validator with required hard-gate checks and run it**
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_FILE="skills/organized-feedback/SKILL.md"
|
||||
|
||||
[[ -f "$SKILL_FILE" ]] || { echo "FAIL: missing $SKILL_FILE"; exit 1; }
|
||||
|
||||
required_patterns=(
|
||||
"Do not write final output until user provides output path"
|
||||
"Coverage audit must run in interaction"
|
||||
"Coverage audit content must not be written to final document"
|
||||
"If unknown count is greater than 3, pause and request user permission"
|
||||
)
|
||||
|
||||
for p in "${required_patterns[@]}"; do
|
||||
if ! rg -Fq "$p" "$SKILL_FILE"; then
|
||||
echo "FAIL: missing required rule -> $p"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "PASS: organized-feedback hard gates validated"
|
||||
```
|
||||
|
||||
Run: `bash scripts/validate_organized_feedback_skill.sh`
|
||||
Expected: `PASS: organized-feedback hard gates validated`
|
||||
|
||||
- [ ] **Step 5: Commit Task 1**
|
||||
|
||||
```bash
|
||||
git add skills/organized-feedback/SKILL.md scripts/validate_organized_feedback_skill.sh
|
||||
git commit -m "feat(skill): scaffold organized-feedback skill with hard gates"
|
||||
```
|
||||
|
||||
### Task 2: Implement classification, grouping, and audit workflow in skill instructions
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/organized-feedback/SKILL.md`
|
||||
- Test: `scripts/validate_organized_feedback_skill.sh`
|
||||
|
||||
- [ ] **Step 1: Add failing checks for taxonomy and audit loop requirements**
|
||||
|
||||
```bash
|
||||
# append to scripts/validate_organized_feedback_skill.sh
|
||||
required_patterns+=(
|
||||
"question"
|
||||
"supplement"
|
||||
"request-for-change"
|
||||
"unknown"
|
||||
"Change-Scope"
|
||||
"Necessity"
|
||||
"Missing-Source-Refs"
|
||||
"unknown(>=1),必须执行一次反思复判"
|
||||
)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run validator to verify it fails on missing workflow details**
|
||||
|
||||
Run: `bash scripts/validate_organized_feedback_skill.sh`
|
||||
Expected: FAIL mentioning at least one missing taxonomy/audit pattern.
|
||||
|
||||
- [ ] **Step 3: Fill `SKILL.md` with full workflow and output format contract**
|
||||
|
||||
```markdown
|
||||
## Inputs
|
||||
Required:
|
||||
- PR review markdown (with Review/Comment/Reply)
|
||||
- output path for organized feedback markdown
|
||||
|
||||
## Classification Taxonomy
|
||||
- Type: `question | supplement | request-for-change | unknown`
|
||||
- For `request-for-change` only:
|
||||
- `Change-Scope`: `local | implement | api-change | requirement-change`
|
||||
- `Necessity`: `nice-to-have | should-fix | must-fix`
|
||||
|
||||
## Processing Flow
|
||||
1. Parse all source comments/replies into stable refs like `R1.C2`, `R1.C2.P1`.
|
||||
2. Create organized items (split/merge is allowed).
|
||||
3. Run coverage audit in interaction:
|
||||
- show `All-Source-Refs`
|
||||
- show `Covered-Source-Refs`
|
||||
- show `Missing-Source-Refs`
|
||||
4. If missing refs exist, add items and re-audit until missing set is empty.
|
||||
5. If unknown(>=1),必须执行一次反思复判.
|
||||
6. If unknown count > 3, pause and request user permission before writing output file.
|
||||
|
||||
## Final File Format
|
||||
Write only:
|
||||
- `## Organized Feedback`
|
||||
- optional `## Unknown Items`
|
||||
|
||||
Do not write coverage audit sections into the output file.
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Re-run validator and ensure full pass**
|
||||
|
||||
Run: `bash scripts/validate_organized_feedback_skill.sh`
|
||||
Expected: `PASS: organized-feedback hard gates validated`
|
||||
|
||||
- [ ] **Step 5: Commit Task 2**
|
||||
|
||||
```bash
|
||||
git add skills/organized-feedback/SKILL.md scripts/validate_organized_feedback_skill.sh
|
||||
git commit -m "feat(skill): add classification and coverage audit workflow"
|
||||
```
|
||||
|
||||
### Task 3: Add realistic examples for maintainers and prompt tuning
|
||||
|
||||
**Files:**
|
||||
- Create: `skills/organized-feedback/examples/input-pr-review.md`
|
||||
- Create: `skills/organized-feedback/examples/output-organized-feedback.md`
|
||||
- Test: `scripts/validate_organized_feedback_skill.sh`
|
||||
|
||||
- [ ] **Step 1: Add failing validator checks for examples existence**
|
||||
|
||||
```bash
|
||||
example_files=(
|
||||
"skills/organized-feedback/examples/input-pr-review.md"
|
||||
"skills/organized-feedback/examples/output-organized-feedback.md"
|
||||
)
|
||||
|
||||
for f in "${example_files[@]}"; do
|
||||
[[ -f "$f" ]] || { echo "FAIL: missing example file -> $f"; exit 1; }
|
||||
done
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run validator to confirm failure before adding examples**
|
||||
|
||||
Run: `bash scripts/validate_organized_feedback_skill.sh`
|
||||
Expected: `FAIL: missing example file -> ...`
|
||||
|
||||
- [ ] **Step 3: Add a compact but complete input example**
|
||||
|
||||
```markdown
|
||||
# org/repo `#42` Improve transaction handling
|
||||
|
||||
## Review 42.1 (COMMENT)
|
||||
> reviewer-a
|
||||
|
||||
### Comment 42.1.1
|
||||
service/order.rs:87
|
||||
reviewer-a:
|
||||
```md
|
||||
这里为什么要在循环里每次开启事务?
|
||||
```
|
||||
|
||||
### Reply 42.1.1.1
|
||||
service/order.rs:87
|
||||
author:
|
||||
```md
|
||||
为了保证每个子任务互不影响。
|
||||
```
|
||||
|
||||
## Review 42.2 (REQUEST_CHANGES)
|
||||
> reviewer-b
|
||||
|
||||
### Comment 42.2.1
|
||||
api/order.ts:15
|
||||
reviewer-b:
|
||||
```md
|
||||
建议把 `createOrder(input)` 改成 `createOrder(ctx, input)`,否则审计信息拿不到。
|
||||
```
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Add output example that excludes coverage audit and includes optional unknown section**
|
||||
|
||||
```markdown
|
||||
## Organized Feedback
|
||||
|
||||
### Item 1
|
||||
- Type: question
|
||||
- Summary: reviewer-a 询问为何循环内重复开启事务。
|
||||
- Rationale: 该意见请求解释现有设计意图,不是直接改动请求。
|
||||
- Source-Refs: R1.C1
|
||||
- Raw-Excerpts: "这里为什么要在循环里每次开启事务?"
|
||||
|
||||
### Item 2
|
||||
- Type: request-for-change
|
||||
- Change-Scope: api-change
|
||||
- Necessity: should-fix
|
||||
- Summary: reviewer-b 建议 API 增加 `ctx` 参数以支持审计信息透传。
|
||||
- Rationale: 该意见明确要求接口签名调整,影响调用方。
|
||||
- Source-Refs: R2.C1
|
||||
- Raw-Excerpts: "建议把 createOrder(input) 改成 createOrder(ctx, input)"
|
||||
|
||||
## Unknown Items
|
||||
|
||||
### Unknown 1
|
||||
- Source-Refs: R1.C1.P1
|
||||
- Reason: 回复仅陈述现状,缺少审阅者意图,不足以单独归类。
|
||||
- Needed-Info: 需要确认该回复是否用于反驳、接受或补充审阅意见。
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Run validator and commit Task 3**
|
||||
|
||||
Run: `bash scripts/validate_organized_feedback_skill.sh`
|
||||
Expected: `PASS: organized-feedback hard gates validated`
|
||||
|
||||
```bash
|
||||
git add skills/organized-feedback/examples scripts/validate_organized_feedback_skill.sh
|
||||
git commit -m "docs(skill): add organized-feedback input and output examples"
|
||||
```
|
||||
|
||||
### Task 4: Document skill discoverability in README files
|
||||
|
||||
**Files:**
|
||||
- Modify: `README.md`
|
||||
- Modify: `README.en.md`
|
||||
|
||||
- [ ] **Step 1: Add Chinese README entry under skill section**
|
||||
|
||||
```markdown
|
||||
## 给 LLM 的 Skill
|
||||
|
||||
仓库内已提供两个可复用 skill:
|
||||
|
||||
- `skills/gitea-pr-review-cli/SKILL.md`:标准化 PR 抓取与渲染流程
|
||||
- `skills/organized-feedback/SKILL.md`:将 review/comment/reply 整理为 `Organized Feedback`,并执行覆盖审计与 unknown 反思机制
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add matching English README entry**
|
||||
|
||||
```markdown
|
||||
## Skills for LLM Agents
|
||||
|
||||
This repository provides reusable skills:
|
||||
|
||||
- `skills/gitea-pr-review-cli/SKILL.md`: standardized CLI usage for fetch/render/version flows
|
||||
- `skills/organized-feedback/SKILL.md`: organizes review/comment/reply text into `Organized Feedback` with coverage auditing and unknown-item reflection
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Verify docs include new skill references**
|
||||
|
||||
Run: `rg -n "organized-feedback" README.md README.en.md`
|
||||
Expected: both files contain exactly one bullet for the new skill.
|
||||
|
||||
- [ ] **Step 4: Commit Task 4**
|
||||
|
||||
```bash
|
||||
git add README.md README.en.md
|
||||
git commit -m "docs: document organized-feedback skill in readmes"
|
||||
```
|
||||
|
||||
### Task 5: Final verification and delivery
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/organized-feedback/SKILL.md`
|
||||
- Modify: `scripts/validate_organized_feedback_skill.sh`
|
||||
- Modify: `README.md`
|
||||
- Modify: `README.en.md`
|
||||
|
||||
- [ ] **Step 1: Run full validation commands**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
bash scripts/validate_organized_feedback_skill.sh
|
||||
rg -n "Coverage Audit" skills/organized-feedback/examples/output-organized-feedback.md || true
|
||||
```
|
||||
|
||||
Expected:
|
||||
- validator prints pass
|
||||
- second command prints no matches (final output example must not contain coverage audit block)
|
||||
|
||||
- [ ] **Step 2: Manual acceptance walkthrough**
|
||||
|
||||
Run this checklist during a dry-run prompt session:
|
||||
1. Start skill without output path -> confirm it asks for path and pauses.
|
||||
2. Provide path and source markdown -> confirm it prints coverage audit in interaction.
|
||||
3. Provide sample producing unknown count 1 -> confirm reflection pass runs.
|
||||
4. Provide sample producing unknown count 4 -> confirm it asks user permission before writing.
|
||||
|
||||
Expected: all four checks succeed.
|
||||
|
||||
- [ ] **Step 3: Final commit and summary**
|
||||
|
||||
```bash
|
||||
git add skills/organized-feedback scripts/validate_organized_feedback_skill.sh README.md README.en.md
|
||||
git commit -m "feat(skill): add organized-feedback workflow with audit and guardrails"
|
||||
```
|
||||
|
||||
Run: `git show --name-only --oneline -n 1`
|
||||
Expected: only skill/docs/validator files included.
|
||||
|
||||
## Self-Review
|
||||
|
||||
### Spec coverage
|
||||
- Input path gate: covered in Task 1 + Task 5 walkthrough.
|
||||
- Classification taxonomy: covered in Task 2.
|
||||
- Split/merge freedom + source refs: covered in Task 2.
|
||||
- Coverage audit interaction-only: covered in Task 2 + Task 5.
|
||||
- Unknown reflection and >3 permission gate: covered in Task 2 + Task 5.
|
||||
- Output format (`Organized Feedback` + optional `Unknown Items`): covered in Task 2 and Task 3 example.
|
||||
|
||||
### Placeholder scan
|
||||
- No `TBD`/`TODO` placeholders in tasks.
|
||||
- Every command step contains explicit command and expected result.
|
||||
|
||||
### Type consistency
|
||||
- Taxonomy keys are consistent across tasks:
|
||||
- `Type`
|
||||
- `Change-Scope`
|
||||
- `Necessity`
|
||||
- `Source-Refs`
|
||||
- `Unknown Items`
|
||||
@@ -0,0 +1,157 @@
|
||||
# 意见整理 Skill 设计
|
||||
|
||||
## 1. 背景与目标
|
||||
|
||||
本设计用于新增一个“意见整理”Skill,将 `gitea-pr-review fetch` 产出的 PR Review Markdown 中的非结构化评论,整理为结构化的 `Organized Feedback` 列表,供后续开发决策使用。
|
||||
|
||||
目标:
|
||||
- 对混杂的审阅意见做统一分类与归并。
|
||||
- 允许“拆分或合并”策略由 agent 自主决定。
|
||||
- 强制执行覆盖审计,保证不遗漏任何原始 `Comment/Reply`。
|
||||
- 最终输出写入用户指定路径。
|
||||
|
||||
## 2. 范围
|
||||
|
||||
本 Skill 仅负责:
|
||||
- 读取单份 PR Review Markdown。
|
||||
- 产出单份 Organized Feedback Markdown 文档。
|
||||
- 在交互中展示覆盖审计信息。
|
||||
|
||||
本 Skill 不负责:
|
||||
- 直接修改代码。
|
||||
- 自动发起二次请求获取更多 PR 数据。
|
||||
|
||||
## 3. 输入与输出
|
||||
|
||||
### 3.1 输入
|
||||
|
||||
必需输入:
|
||||
- 一份符合 `gitea-pr-review` 输出结构的 Markdown(含 `Review/Comment/Reply`)。
|
||||
- 用户指定的输出文件路径(用于写入 Organized Feedback 文档)。
|
||||
|
||||
若用户未提供输出路径,Skill 必须先询问并暂停。
|
||||
|
||||
### 3.2 输出
|
||||
|
||||
最终输出文档(写入用户指定路径)包含:
|
||||
- `## Organized Feedback`
|
||||
- `## Unknown Items`(仅存在 unknown 时出现)
|
||||
|
||||
最终输出文档不包含:
|
||||
- `Coverage Audit`(仅在交互过程展示,不落盘)。
|
||||
|
||||
## 4. 分类体系
|
||||
|
||||
### 4.1 主类型
|
||||
|
||||
- `question`:请求解释/澄清,不直接要求修改。
|
||||
- `supplement`:补充背景信息,不构成明确修改请求。
|
||||
- `request-for-change`:要求实现发生改变。
|
||||
- `unknown`:证据不足或语义冲突,且无法通过上下文消歧。
|
||||
|
||||
默认倾向:尽量避免 `unknown`,仅在迫不得已时使用。
|
||||
|
||||
### 4.2 RFC 二级分类
|
||||
|
||||
仅当 `Type=request-for-change` 时填写:
|
||||
|
||||
`Change-Scope`:
|
||||
- `local`
|
||||
- `implement`
|
||||
- `api-change`
|
||||
- `requirement-change`
|
||||
|
||||
`Necessity`:
|
||||
- `nice-to-have`
|
||||
- `should-fix`
|
||||
- `must-fix`
|
||||
|
||||
## 5. 处理流程
|
||||
|
||||
### 5.1 阶段 A:预处理
|
||||
|
||||
- 扫描输入文档中的全部 `Comment/Reply`。
|
||||
- 为每条原始意见建立唯一引用键(例如 `R1.C2`、`R1.C2.P1`)。
|
||||
- 建立 `All-Source-Refs` 集合。
|
||||
|
||||
### 5.2 阶段 B:归类与归并
|
||||
|
||||
- agent 可自主选择粒度:
|
||||
- 拆分:一条原始意见拆成多条整理意见。
|
||||
- 合并:多条原始意见合并成一条整理意见。
|
||||
- 允许一条原始意见被多条整理意见引用(多对多)。
|
||||
- 若同一原始意见同时包含提问与改动请求,优先拆分为独立条目。
|
||||
|
||||
### 5.3 阶段 C:覆盖审计(交互展示)
|
||||
|
||||
- 计算 `Covered-Source-Refs`(被至少一个整理意见引用)。
|
||||
- 计算差集 `Missing-Source-Refs = All - Covered`。
|
||||
- 若差集非空,强制补洞并重复审计,直至差集为空。
|
||||
- 审计信息在交互中展示,不写入输出文档。
|
||||
|
||||
### 5.4 阶段 D:unknown 反思机制
|
||||
|
||||
- 只要存在 `unknown`(>=1),必须执行一次反思复判。
|
||||
- 复判后仍无法归类,方可保留为 `unknown`。
|
||||
- 若 `unknown` 绝对数量 `> 3`:
|
||||
- 必须在交互中明确提示“审阅质量或上下文完整性存在风险”。
|
||||
- 必须请求用户许可;未获许可不得写最终输出文件。
|
||||
|
||||
## 6. 输出格式
|
||||
|
||||
### 6.1 Organized Feedback 条目格式
|
||||
|
||||
每条意见包含:
|
||||
- `Type`
|
||||
- `Change-Scope`(RFC 必填)
|
||||
- `Necessity`(RFC 必填)
|
||||
- `Summary`(1-2 句)
|
||||
- `Rationale`(分类依据;unknown 时写阻塞原因)
|
||||
- `Source-Refs`(引用的原始键)
|
||||
- `Raw-Excerpts`(精简原文摘录)
|
||||
|
||||
### 6.2 Unknown Items
|
||||
|
||||
当存在 unknown 时,文档追加 `## Unknown Items`,每条写明:
|
||||
- 无法归类的原因。
|
||||
- 解除不确定性所需的最小补充信息。
|
||||
|
||||
## 7. 错误处理
|
||||
|
||||
- 输入结构不可识别:报错并停止;提示已识别段落数与示例片段。
|
||||
- 输出路径缺失:询问用户并暂停。
|
||||
- 输出目录不存在:请求确认后创建,或要求用户更换路径。
|
||||
- 输出不可写:报错并给出替代路径建议。
|
||||
- 覆盖审计始终无法闭合:停止写文件并在交互中报告未覆盖键。
|
||||
|
||||
## 8. 组件边界与职责
|
||||
|
||||
- Parser:提取 Review/Comment/Reply 与引用键。
|
||||
- Classifier:完成类型判定、RFC 细分与必要性判定。
|
||||
- Grouper:执行拆分/合并并维持 `Source-Refs` 映射。
|
||||
- Auditor:执行覆盖审计与补洞循环。
|
||||
- Writer:按固定模板写入输出 Markdown。
|
||||
|
||||
各组件通过显式结构化中间数据通信,避免隐式状态耦合。
|
||||
|
||||
## 9. 测试与验收标准
|
||||
|
||||
最小验收标准:
|
||||
- 所有原始 `Comment/Reply` 均被至少一条输出意见覆盖。
|
||||
- 输出文档不包含 Coverage Audit 区块。
|
||||
- 当出现 unknown 时,必有反思复判步骤。
|
||||
- 当 unknown 数量 > 3 时,无用户许可不得落盘。
|
||||
- RFC 条目完整填写 `Change-Scope` 与 `Necessity`。
|
||||
|
||||
建议测试集:
|
||||
- 纯 question 样例。
|
||||
- question + RFC 混合样例(验证拆分)。
|
||||
- 多线程同主题样例(验证合并)。
|
||||
- 高歧义样例(触发 unknown 与用户许可门禁)。
|
||||
|
||||
## 10. 决策记录
|
||||
|
||||
- 采用纯 Prompt Skill(不引入脚本)。
|
||||
- 采用“两阶段归类 + 覆盖审计补洞”策略。
|
||||
- Coverage Audit 仅用于交互,不写最终文件。
|
||||
- 必须由用户指定输出路径。
|
||||
@@ -0,0 +1,111 @@
|
||||
---
|
||||
name: gitea-pr-review-cli
|
||||
description: Use when an agent needs to fetch a single Gitea PR and produce or consume the project's review document format with strict version checks.
|
||||
---
|
||||
|
||||
# gitea-pr-review CLI
|
||||
|
||||
## Overview
|
||||
Use this skill when you need PR review context from Gitea in a stable document format for LLM workflows.
|
||||
|
||||
The CLI supports two output formats (`markdown`, `json`) and one strict schema version (`v1`). Version mismatch must be treated as a hard failure.
|
||||
|
||||
## When to Use
|
||||
|
||||
Use this skill when:
|
||||
- You need to fetch one PR and convert it into LLM-friendly review documents.
|
||||
- You need to re-render Markdown from a previously generated JSON file.
|
||||
- You need deterministic output rules (numbering/version/header fields).
|
||||
|
||||
Do not use this skill when:
|
||||
- You need cross-PR aggregation.
|
||||
- You need full patch/diff bodies (this tool intentionally focuses on review context + diff stat).
|
||||
|
||||
## Required Inputs
|
||||
|
||||
For `fetch`, these environment variables are required:
|
||||
- `GITEA_PR_CLI_API_TOKEN`
|
||||
- `GITEA_PR_CLI_URL`
|
||||
- `GITEA_PR_CLI_REPO`
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# 0) load env
|
||||
source .local/env.sh
|
||||
|
||||
# 1) sanity check CLI document version contract
|
||||
gitea-pr-review version
|
||||
# expected: v1
|
||||
|
||||
# 2) fetch markdown to stdout (default)
|
||||
gitea-pr-review fetch <pr-index>
|
||||
|
||||
# 3) fetch json to file
|
||||
gitea-pr-review fetch <pr-index> --format json --out pr.json
|
||||
|
||||
# 4) render markdown from json
|
||||
gitea-pr-review render-md --in pr.json --out pr.md
|
||||
```
|
||||
|
||||
## Command Reference
|
||||
|
||||
### `fetch <pr-index>`
|
||||
Fetch PR data from Gitea and output review document.
|
||||
|
||||
Options:
|
||||
- `--format markdown|json` (default `markdown`)
|
||||
- `--out <path>` (if omitted, write to stdout)
|
||||
|
||||
### `render-md --in <json_path>`
|
||||
Render Markdown from existing JSON document.
|
||||
|
||||
Options:
|
||||
- `--out <path>` (if omitted, write to stdout)
|
||||
|
||||
Important:
|
||||
- Input JSON must have `version == "v1"`.
|
||||
- If version mismatches, command exits non-zero.
|
||||
|
||||
### `version`
|
||||
Print current document version string.
|
||||
|
||||
Expected output:
|
||||
- `v1`
|
||||
|
||||
## Output Contract (v1)
|
||||
|
||||
Markdown header contains:
|
||||
- Title line: `# <repo> #<pr> <title>`
|
||||
- Numbering rule block
|
||||
- `version: v1`
|
||||
- `fetched at: <rfc3339>`
|
||||
- PR description
|
||||
|
||||
JSON contains:
|
||||
- Top-level `version: "v1"`
|
||||
- `meta.fetched_at` (RFC3339 string)
|
||||
|
||||
Numbering hierarchy:
|
||||
- Review: `<pr>.<review>`
|
||||
- Comment: `<pr>.<review>.<comment>`
|
||||
- Reply: `<pr>.<review>.<comment>.<reply>`
|
||||
|
||||
## Failure Handling
|
||||
|
||||
If `fetch` fails:
|
||||
1. Check all required env vars are set.
|
||||
2. Re-run with same env using a small PR index to isolate auth/connectivity issues.
|
||||
3. Confirm repo slug format is `<owner>/<repo>`.
|
||||
|
||||
If `render-md` fails:
|
||||
1. Validate JSON is generated by this CLI.
|
||||
2. Validate `version` is exactly `v1`.
|
||||
3. Check JSON is complete (`meta`, `reviews`, `threads`, `diff_stat`).
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
- Running `fetch` without sourcing env file.
|
||||
- Assuming schema compatibility across versions.
|
||||
- Editing JSON `version` manually and expecting `render-md` to work.
|
||||
- Treating position-only comments as lacking location; this CLI maps available position data to a displayed line when possible.
|
||||
@@ -0,0 +1,83 @@
|
||||
---
|
||||
name: organized-feedback
|
||||
description: Organize PR review comments into structured feedback with coverage auditing and unknown-item handling. Use when converting gitea-pr-review Markdown into a user-specified output document.
|
||||
---
|
||||
|
||||
# Organized Feedback
|
||||
|
||||
## Overview
|
||||
Use this skill to turn a single PR review document into organized feedback.
|
||||
|
||||
## Inputs
|
||||
- PR markdown
|
||||
- output path
|
||||
|
||||
## Hard Gates
|
||||
- Do not write final output until user provides output path.
|
||||
- Coverage audit must run in interaction.
|
||||
- Coverage audit content must not be written to final document.
|
||||
- If unknown count is greater than 3, pause and request user permission.
|
||||
|
||||
## Classification Taxonomy
|
||||
- `question`: a review item that asks for clarification, confirmation, or intent.
|
||||
- `supplement`: a review item that adds missing context, examples, or supporting detail without changing the requested behavior.
|
||||
- `request-for-change`: a review item that asks for a code, behavior, or design change.
|
||||
- `unknown`: a review item that cannot be classified confidently after one reflection pass.
|
||||
|
||||
## RFC Subfields
|
||||
When a review item is classified as `request-for-change`, annotate it with:
|
||||
- `Change-Scope`: how broad the requested change is.
|
||||
- `local`: Small, local edits that can usually be finished quickly (for example: rename identifiers, syntax sugar swap, blank-line/order cleanup, light module structure cleanup).
|
||||
- `implement`: Implementation-flow changes that affect business logic behavior (for example: sync points, transaction handling, feature implementation strategy). These typically need tests that demonstrate why the old behavior is insufficient.
|
||||
- `api-change`: API contract changes for widely used internal interfaces or external interfaces. These usually require updating dependent tests and call sites.
|
||||
- `requirement-change`: Requirement-level correction where the solved requirement itself is considered unreasonable and must be re-evaluated.
|
||||
- `Necessity`: how strongly the change is required.
|
||||
- `nice-to-have`: Optional improvement after higher-priority requests are resolved (for example: vague polish suggestions, minor wrappers/helpers).
|
||||
- `should-fix`: Should be fixed unless there is a defensible reason to defer (for example: proven not triggerable, known low impact, or deferred to next PR with explicit note).
|
||||
- `must-fix`: Mandatory blocker. The PR should not be merged until this is resolved.
|
||||
|
||||
## Processing Flow
|
||||
1. Read the PR markdown and identify each review comment or feedback unit.
|
||||
2. Classify each unit as `question`, `supplement`, `request-for-change`, or `unknown`.
|
||||
3. If a unit clearly contains multiple intents, split it into separate items before grouping.
|
||||
4. If multiple units express the same intent, merge them into one grouped item.
|
||||
5. For each `request-for-change`, assign `Change-Scope` and `Necessity`.
|
||||
6. Run an interaction-only coverage audit using `All-Source-Refs`, `Covered-Source-Refs`, and `Missing-Source-Refs`.
|
||||
7. Re-audit until `Missing-Source-Refs` is empty.
|
||||
8. Apply the unknown reflection rule once before finalizing any `unknown` item.
|
||||
9. If the count of `unknown` items is greater than 3, stop and request user permission before proceeding.
|
||||
|
||||
## Interaction-Only Coverage Audit
|
||||
The coverage audit must be performed only against the interaction content, not the final document.
|
||||
|
||||
Track these lists explicitly:
|
||||
- `All-Source-Refs`: every source reference found in the interaction
|
||||
- `Covered-Source-Refs`: refs that are represented in the organized output
|
||||
- `Missing-Source-Refs`: refs that are present in the interaction but not yet covered
|
||||
|
||||
Source reference format rule:
|
||||
- Use `#` + PR numbering path from the source markdown. `R` for review, `C` for comment, `r` for reply.
|
||||
- Example mapping: `Comment 42.1.1` -> `#42.R1.C1`, `Reply 42.1.1.1` -> `#42.R1.C1.r1`.
|
||||
|
||||
Audit rules:
|
||||
- Compare only against interaction content.
|
||||
- Re-audit after every grouping pass until `Missing-Source-Refs` is empty.
|
||||
- Do not treat the final document as an audit source.
|
||||
|
||||
## Unknown Handling
|
||||
- if any unknown(>=1)exists,must use one reflection pass to decide whether an item can be reclassified into `question`, `supplement`, or `request-for-change`.
|
||||
- If it remains `unknown`, keep it in the `Unknown Items` section.
|
||||
- If the number of `unknown` items is greater than 3, request user permission before continuing.
|
||||
|
||||
## Final File Format
|
||||
Write only the organized output content:
|
||||
- `Organized Feedback`
|
||||
- optional `Unknown Items`
|
||||
|
||||
Do not write the coverage audit into the final output file.
|
||||
|
||||
## Output Discipline
|
||||
- Preserve the user-specified output path.
|
||||
- Keep the final file limited to the agreed format.
|
||||
- Do not add extra audit notes, scratch work, or intermediate classification logs to the final file.
|
||||
- If terminal access is available, validate generated output with `bash skills/organized-feedback/scripts/validate_organized_feedback_skill.sh --output "<organized-feedback.md>"`.
|
||||
@@ -0,0 +1,45 @@
|
||||
# org/repo `#42` Improve transaction handling
|
||||
|
||||
## Review 42.1 (COMMENT)
|
||||
> reviewer-a
|
||||
|
||||
### Comment 42.1.1
|
||||
service/order.rs:87
|
||||
reviewer-a:
|
||||
```md
|
||||
为什么要在循环里每次开启事务?
|
||||
```
|
||||
|
||||
### Reply 42.1.1.1
|
||||
service/order.rs:87
|
||||
author:
|
||||
```md
|
||||
为了保证每个子任务互不影响。
|
||||
```
|
||||
|
||||
## Review 42.2 (REQUEST_CHANGES)
|
||||
> reviewer-b
|
||||
|
||||
### Comment 42.2.1
|
||||
api/order.ts:15
|
||||
reviewer-b:
|
||||
```md
|
||||
建议把 `createOrder(input)` 改成 `createOrder(ctx, input)`,否则审计信息拿不到。
|
||||
```
|
||||
|
||||
## Review 42.3 (COMMENT)
|
||||
> reviewer-c
|
||||
|
||||
### Comment 42.3.1
|
||||
service/order.rs:120
|
||||
reviewer-c:
|
||||
```md
|
||||
这里的缓存失效策略是不是太激进了?
|
||||
```
|
||||
|
||||
### Reply 42.3.1.1
|
||||
service/order.rs:120
|
||||
author:
|
||||
```md
|
||||
按之前的约定处理。
|
||||
```
|
||||
@@ -0,0 +1,24 @@
|
||||
## Organized Feedback
|
||||
|
||||
### Item 1
|
||||
- Type: question
|
||||
- Summary: reviewer-a 询问为什么在循环里重复开启事务。
|
||||
- Rationale: 该意见在确认设计意图,不是直接要求代码修改。
|
||||
- Source-Refs: R42.1.1
|
||||
- Raw-Excerpts: "为什么要在循环里每次开启事务?"
|
||||
|
||||
### Item 2
|
||||
- Type: request-for-change
|
||||
- Change-Scope: api-change
|
||||
- Necessity: should-fix
|
||||
- Summary: reviewer-b 建议把 `createOrder(input)` 改成 `createOrder(ctx, input)` 以便传递审计信息。
|
||||
- Rationale: 该意见明确要求修改 API 签名,影响调用方。
|
||||
- Source-Refs: R42.2.1
|
||||
- Raw-Excerpts: "建议把 `createOrder(input)` 改成 `createOrder(ctx, input)`,否则审计信息拿不到。"
|
||||
|
||||
## Unknown Items
|
||||
|
||||
### Unknown 1
|
||||
- Source-Refs: R42.3.1, R42.3.1.1
|
||||
- Reason: 回复只是“按之前的约定处理”,缺少足够上下文来判断是接受建议、补充说明还是保持现状。
|
||||
- Needed-Info: 需要确认这条回复是否在响应审阅意见,还是仅说明已有实现策略。
|
||||
@@ -0,0 +1,264 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SKILL_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
SKILL_FILE="$SKILL_ROOT/SKILL.md"
|
||||
INPUT_EXAMPLE="$SKILL_ROOT/examples/input-pr-review.md"
|
||||
OUTPUT_EXAMPLE="$SKILL_ROOT/examples/output-organized-feedback.md"
|
||||
|
||||
print_usage() {
|
||||
cat <<'USAGE'
|
||||
Usage:
|
||||
validate_organized_feedback_skill.sh --output <output.md> [--self-check]
|
||||
validate_organized_feedback_skill.sh --help
|
||||
|
||||
Modes:
|
||||
Default: validate an arbitrary Organized Feedback markdown output file.
|
||||
--self-check: additionally validate bundled skill contract and examples.
|
||||
USAGE
|
||||
}
|
||||
|
||||
require_cmds() {
|
||||
if ! command -v rg >/dev/null 2>&1; then
|
||||
echo "FAIL: rg is required but was not found in PATH"
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v awk >/dev/null 2>&1; then
|
||||
echo "FAIL: awk is required but was not found in PATH"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
validate_output_file() {
|
||||
local file="$1"
|
||||
|
||||
if [[ ! -f "$file" ]]; then
|
||||
echo "FAIL: missing output file -> $file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! rg -n -q --pcre2 '^## Organized Feedback$' "$file"; then
|
||||
echo "FAIL: missing required section -> ## Organized Feedback"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if rg -n -q -i --pcre2 'coverage[ -]?audit' "$file"; then
|
||||
echo "FAIL: output must not include Coverage Audit"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
awk '
|
||||
function trim(s) {
|
||||
gsub(/^[ \t]+/, "", s)
|
||||
gsub(/[ \t]+$/, "", s)
|
||||
return s
|
||||
}
|
||||
|
||||
function validate_refs(line) {
|
||||
sub(/^- Source-Refs:[ \t]*/, "", line)
|
||||
n = split(line, parts, /,/)
|
||||
if (n < 1) return 0
|
||||
for (i = 1; i <= n; i++) {
|
||||
ref = trim(parts[i])
|
||||
if (ref !~ /^R[0-9]+(\.[0-9]+)+$/) {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
function reset_item() {
|
||||
item_seen = 0
|
||||
has_type = 0
|
||||
has_refs = 0
|
||||
is_rfc = 0
|
||||
has_scope = 0
|
||||
has_necessity = 0
|
||||
}
|
||||
|
||||
function finalize_item() {
|
||||
if (!item_seen) return 1
|
||||
if (!has_type) {
|
||||
print "FAIL: item missing - Type: line" > "/dev/stderr"
|
||||
return 0
|
||||
}
|
||||
if (!has_refs) {
|
||||
print "FAIL: item missing valid - Source-Refs: line" > "/dev/stderr"
|
||||
return 0
|
||||
}
|
||||
if (is_rfc && (!has_scope || !has_necessity)) {
|
||||
print "FAIL: request-for-change item must include Change-Scope and Necessity" > "/dev/stderr"
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
BEGIN {
|
||||
in_of = 0
|
||||
item_count = 0
|
||||
reset_item()
|
||||
}
|
||||
|
||||
/^## Organized Feedback$/ {
|
||||
in_of = 1
|
||||
next
|
||||
}
|
||||
|
||||
in_of && /^## / {
|
||||
if (!finalize_item()) exit 1
|
||||
in_of = 0
|
||||
next
|
||||
}
|
||||
|
||||
in_of {
|
||||
if ($0 ~ /^### Item [0-9]+$/) {
|
||||
if (!finalize_item()) exit 1
|
||||
reset_item()
|
||||
item_seen = 1
|
||||
item_count++
|
||||
next
|
||||
}
|
||||
|
||||
if (!item_seen) next
|
||||
|
||||
if ($0 ~ /^- Type:[ \t]*/) {
|
||||
has_type = 1
|
||||
if ($0 ~ /^- Type:[ \t]*request-for-change([ \t]|$)/) {
|
||||
is_rfc = 1
|
||||
}
|
||||
}
|
||||
|
||||
if ($0 ~ /^- Change-Scope:[ \t]*/) has_scope = 1
|
||||
if ($0 ~ /^- Necessity:[ \t]*/) has_necessity = 1
|
||||
|
||||
if ($0 ~ /^- Source-Refs:[ \t]*/) {
|
||||
if (validate_refs($0)) {
|
||||
has_refs = 1
|
||||
} else {
|
||||
print "FAIL: invalid Source-Refs format -> " $0 > "/dev/stderr"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
END {
|
||||
if (in_of && !finalize_item()) exit 1
|
||||
if (item_count == 0) {
|
||||
print "FAIL: ## Organized Feedback has no ### Item entries" > "/dev/stderr"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
' "$file"
|
||||
|
||||
echo "PASS: output format validated -> $file"
|
||||
}
|
||||
|
||||
validate_skill_contract() {
|
||||
if [[ ! -f "$SKILL_FILE" ]]; then
|
||||
echo "FAIL: missing $SKILL_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for example_file in "$INPUT_EXAMPLE" "$OUTPUT_EXAMPLE"; do
|
||||
if [[ ! -f "$example_file" ]]; then
|
||||
echo "FAIL: missing $example_file"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
local -a anchored_patterns=(
|
||||
'^## Inputs$'
|
||||
'^## Hard Gates$'
|
||||
'^## Classification Taxonomy$'
|
||||
'^## RFC Subfields$'
|
||||
'^## Processing Flow$'
|
||||
'^## Interaction-Only Coverage Audit$'
|
||||
'^## Unknown Handling$'
|
||||
'^## Final File Format$'
|
||||
'^## Output Discipline$'
|
||||
'^ - `local`: .+$'
|
||||
'^ - `implement`: .+$'
|
||||
'^ - `api-change`: .+$'
|
||||
'^ - `requirement-change`: .+$'
|
||||
'^ - `nice-to-have`: .+$'
|
||||
'^ - `should-fix`: .+$'
|
||||
'^ - `must-fix`: .+$'
|
||||
'^- `All-Source-Refs`: .+$'
|
||||
'^- `Covered-Source-Refs`: .+$'
|
||||
'^- `Missing-Source-Refs`: .+$'
|
||||
'^Source reference format rule:$'
|
||||
)
|
||||
|
||||
for pattern in "${anchored_patterns[@]}"; do
|
||||
if ! rg -n -q --pcre2 "$pattern" "$SKILL_FILE"; then
|
||||
echo "FAIL: missing required skill rule -> $pattern"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
for legacy in "All refs" "Covered refs" "Missing refs"; do
|
||||
if rg -Fq "$legacy" "$SKILL_FILE"; then
|
||||
echo "FAIL: legacy alias found in SKILL.md -> $legacy"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
if ! rg -Fq -- "unknown(>=1)" "$SKILL_FILE" || ! rg -Fq -- "反思复判" "$SKILL_FILE"; then
|
||||
echo "FAIL: missing unknown reflection rule semantics in SKILL.md"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "PASS: skill contract validated -> $SKILL_FILE"
|
||||
}
|
||||
|
||||
main() {
|
||||
require_cmds
|
||||
|
||||
local output_file=""
|
||||
local self_check=0
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--output)
|
||||
if [[ $# -lt 2 ]]; then
|
||||
echo "FAIL: --output requires a file path"
|
||||
print_usage
|
||||
exit 1
|
||||
fi
|
||||
output_file="$2"
|
||||
shift 2
|
||||
;;
|
||||
--self-check)
|
||||
self_check=1
|
||||
shift
|
||||
;;
|
||||
--help|-h)
|
||||
print_usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "FAIL: unknown argument -> $1"
|
||||
print_usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$output_file" ]]; then
|
||||
echo "FAIL: --output is required"
|
||||
print_usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
validate_output_file "$output_file"
|
||||
|
||||
if [[ "$self_check" -eq 1 ]]; then
|
||||
validate_skill_contract
|
||||
validate_output_file "$OUTPUT_EXAMPLE"
|
||||
fi
|
||||
|
||||
echo "PASS: organized-feedback validation completed"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -18,6 +18,7 @@ pub struct Cli {
|
||||
pub enum Commands {
|
||||
Fetch(FetchArgs),
|
||||
RenderMd(RenderMdArgs),
|
||||
Version,
|
||||
}
|
||||
|
||||
#[derive(Debug, clap::Args)]
|
||||
|
||||
@@ -19,6 +19,8 @@ use crate::output::write_output;
|
||||
use crate::render::json::{parse_json, render_json};
|
||||
use crate::render::markdown::render_markdown;
|
||||
|
||||
pub const OUTPUT_VERSION: &str = "v1";
|
||||
|
||||
pub fn run() -> anyhow::Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
@@ -29,6 +31,9 @@ pub fn run() -> anyhow::Result<()> {
|
||||
let md = render_markdown(&doc);
|
||||
write_output(args.out.as_deref().map(Path::new), &md)?;
|
||||
}
|
||||
Commands::Version => {
|
||||
write_output(None, OUTPUT_VERSION)?;
|
||||
}
|
||||
Commands::Fetch(args) => {
|
||||
let token = required_env("GITEA_PR_CLI_API_TOKEN")?;
|
||||
let base_url = required_env("GITEA_PR_CLI_URL")?;
|
||||
|
||||
+2
-1
@@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PrReviewDocument {
|
||||
pub version: String,
|
||||
pub meta: PrMeta,
|
||||
pub commits: Vec<CommitItem>,
|
||||
pub diff_stat: DiffStat,
|
||||
@@ -15,7 +16,7 @@ pub struct PrMeta {
|
||||
pub pr_index: i64,
|
||||
pub title: String,
|
||||
pub description: Option<String>,
|
||||
pub fetched_at: Option<String>,
|
||||
pub fetched_at: String,
|
||||
pub state: String,
|
||||
pub author: String,
|
||||
pub base_branch: String,
|
||||
|
||||
+3
-1
@@ -4,6 +4,7 @@ use std::convert::TryFrom;
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
|
||||
use crate::gitea::dto::{ChangedFileDto, CommitDto, PullBundleDto, ReviewCommentDto, ReviewDto};
|
||||
use crate::OUTPUT_VERSION;
|
||||
use crate::model::{
|
||||
CommentItem, CommentThread, CommitItem, DiffStat, FileStat, PrMeta, PrReviewDocument,
|
||||
ReviewItem,
|
||||
@@ -28,12 +29,13 @@ pub fn normalize_bundle(repo: &str, fetched_at: String, bundle: PullBundleDto) -
|
||||
let diff_stat = normalize_diff_stat(&pull, files);
|
||||
|
||||
PrReviewDocument {
|
||||
version: OUTPUT_VERSION.to_string(),
|
||||
meta: PrMeta {
|
||||
repo: repo.to_string(),
|
||||
pr_index: pull.number,
|
||||
title: pull.title,
|
||||
description: pull.body,
|
||||
fetched_at: Some(fetched_at),
|
||||
fetched_at,
|
||||
state: pull.state,
|
||||
author: pull.user.login,
|
||||
base_branch: pull.base.ref_name,
|
||||
|
||||
+10
-1
@@ -1,9 +1,18 @@
|
||||
use crate::model::PrReviewDocument;
|
||||
use crate::OUTPUT_VERSION;
|
||||
|
||||
pub fn render_json(doc: &PrReviewDocument) -> anyhow::Result<String> {
|
||||
Ok(serde_json::to_string_pretty(doc)?)
|
||||
}
|
||||
|
||||
pub fn parse_json(input: &str) -> anyhow::Result<PrReviewDocument> {
|
||||
Ok(serde_json::from_str(input)?)
|
||||
let doc: PrReviewDocument = serde_json::from_str(input)?;
|
||||
if doc.version != OUTPUT_VERSION {
|
||||
anyhow::bail!(
|
||||
"unsupported document version: {}, expected {}",
|
||||
doc.version,
|
||||
OUTPUT_VERSION
|
||||
);
|
||||
}
|
||||
Ok(doc)
|
||||
}
|
||||
|
||||
@@ -65,8 +65,8 @@ pub fn render_markdown(doc: &PrReviewDocument) -> String {
|
||||
out.push_str(
|
||||
"> 编号规则:Review `<pr>.<review>`;Comment `<pr>.<review>.<comment>`;Reply `<pr>.<review>.<comment>.<reply>`\n\n",
|
||||
);
|
||||
let fetched_at = doc.meta.fetched_at.as_deref().unwrap_or("unknown");
|
||||
out.push_str(&format!("> fetched at: {fetched_at}\n\n"));
|
||||
out.push_str(&format!("> version: {}\n\n", doc.version));
|
||||
out.push_str(&format!("> fetched at: {}\n\n", doc.meta.fetched_at));
|
||||
let pr_description = doc
|
||||
.meta
|
||||
.description
|
||||
|
||||
@@ -26,3 +26,12 @@ fn parse_render_md_requires_input() {
|
||||
_ => panic!("expected render-md"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_version_subcommand() {
|
||||
let cli = Cli::try_parse_from(["gitea-pr-review", "version"]).unwrap();
|
||||
match cli.command {
|
||||
Commands::Version => {}
|
||||
_ => panic!("expected version"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ fn render_md_reads_json_and_outputs_markdown_to_stdout() {
|
||||
std::fs::write(
|
||||
input.path(),
|
||||
r#"{
|
||||
"version": "v1",
|
||||
"meta": {
|
||||
"repo": "org/repo",
|
||||
"pr_index": 1,
|
||||
@@ -52,6 +53,7 @@ fn render_md_writes_to_out_file_when_requested() {
|
||||
std::fs::write(
|
||||
input.path(),
|
||||
r#"{
|
||||
"version": "v1",
|
||||
"meta": {
|
||||
"repo": "org/repo",
|
||||
"pr_index": 2,
|
||||
@@ -291,6 +293,59 @@ fn fetch_writes_json_to_out_file_when_requested() {
|
||||
let written = std::fs::read_to_string(output.path()).unwrap();
|
||||
assert!(written.contains("\"repo\": \"org/repo\""));
|
||||
assert!(written.contains("\"pr_index\": 8"));
|
||||
assert!(written.contains("\"version\": \"v1\""));
|
||||
assert!(written.contains("\"fetched_at\":"));
|
||||
assert!(written.contains("\"head_branch\": \"feature/y\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_md_fails_when_version_mismatch() {
|
||||
let input = NamedTempFile::new().unwrap();
|
||||
std::fs::write(
|
||||
input.path(),
|
||||
r#"{
|
||||
"version": "v2",
|
||||
"meta": {
|
||||
"repo": "org/repo",
|
||||
"pr_index": 3,
|
||||
"title": "t3",
|
||||
"description": "PR body",
|
||||
"fetched_at": "2026-04-08T12:34:56Z",
|
||||
"state": "open",
|
||||
"author": "a",
|
||||
"base_branch": "main",
|
||||
"head_branch": "f",
|
||||
"created_at": "2026-04-08T10:00:00Z",
|
||||
"updated_at": "2026-04-08T11:00:00Z",
|
||||
"merged_at": null
|
||||
},
|
||||
"commits": [],
|
||||
"diff_stat": {
|
||||
"files_changed": 0,
|
||||
"additions": 0,
|
||||
"deletions": 0,
|
||||
"files": []
|
||||
},
|
||||
"reviews": [],
|
||||
"threads": []
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Command::cargo_bin("gitea-pr-review")
|
||||
.unwrap()
|
||||
.args(["render-md", "--in", input.path().to_str().unwrap()])
|
||||
.assert()
|
||||
.failure();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_subcommand_prints_current_version() {
|
||||
let assert = Command::cargo_bin("gitea-pr-review")
|
||||
.unwrap()
|
||||
.arg("version")
|
||||
.assert()
|
||||
.success();
|
||||
let stdout = String::from_utf8(assert.get_output().stdout.clone()).unwrap();
|
||||
assert_eq!(stdout.trim(), "v1");
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ fn normalize_groups_replies_and_sorts_threads_by_time() {
|
||||
|
||||
assert_eq!(doc.meta.repo, "org/repo");
|
||||
assert_eq!(doc.meta.pr_index, 42);
|
||||
assert_eq!(doc.meta.fetched_at.as_deref(), Some("2026-04-08T12:34:56Z"));
|
||||
assert_eq!(doc.meta.fetched_at, "2026-04-08T12:34:56Z");
|
||||
assert_eq!(doc.meta.base_branch, "main");
|
||||
assert_eq!(doc.meta.head_branch, "feature/x");
|
||||
|
||||
|
||||
@@ -5,12 +5,13 @@ use gitea_pr_review::model::{
|
||||
#[test]
|
||||
fn model_json_roundtrip() {
|
||||
let doc = PrReviewDocument {
|
||||
version: "v1".into(),
|
||||
meta: PrMeta {
|
||||
repo: "org/repo".into(),
|
||||
pr_index: 9,
|
||||
title: "feat: x".into(),
|
||||
description: Some("desc".into()),
|
||||
fetched_at: Some("2026-04-08T12:34:56Z".into()),
|
||||
fetched_at: "2026-04-08T12:34:56Z".into(),
|
||||
state: "open".into(),
|
||||
author: "alice".into(),
|
||||
base_branch: "main".into(),
|
||||
@@ -50,7 +51,8 @@ fn model_json_roundtrip() {
|
||||
|
||||
let encoded = serde_json::to_string(&doc).unwrap();
|
||||
let decoded: PrReviewDocument = serde_json::from_str(&encoded).unwrap();
|
||||
assert_eq!(decoded.version, "v1");
|
||||
assert_eq!(decoded.meta.repo, "org/repo");
|
||||
assert_eq!(decoded.meta.fetched_at.as_deref(), Some("2026-04-08T12:34:56Z"));
|
||||
assert_eq!(decoded.meta.fetched_at, "2026-04-08T12:34:56Z");
|
||||
assert_eq!(decoded.threads[0].root_comment.body, "hello");
|
||||
}
|
||||
|
||||
@@ -7,12 +7,13 @@ use gitea_pr_review::render::markdown::render_markdown;
|
||||
#[test]
|
||||
fn render_markdown_includes_expected_sections_and_preserves_comment_markdown() {
|
||||
let doc = PrReviewDocument {
|
||||
version: "v1".into(),
|
||||
meta: PrMeta {
|
||||
repo: "org/repo".into(),
|
||||
pr_index: 7,
|
||||
title: "Fix parser".into(),
|
||||
description: Some("PR body".into()),
|
||||
fetched_at: Some("2026-04-08T12:34:56Z".into()),
|
||||
fetched_at: "2026-04-08T12:34:56Z".into(),
|
||||
state: "open".into(),
|
||||
author: "alice".into(),
|
||||
base_branch: "main".into(),
|
||||
@@ -73,6 +74,7 @@ fn render_markdown_includes_expected_sections_and_preserves_comment_markdown() {
|
||||
|
||||
assert!(md.contains("# org/repo `#7` Fix parser"));
|
||||
assert!(md.contains("## Metadata"));
|
||||
assert!(md.contains("> version: v1"));
|
||||
assert!(md.contains("> fetched at: 2026-04-08T12:34:56Z"));
|
||||
assert!(md.contains("## Commits"));
|
||||
assert!(md.contains("## Diff Stat"));
|
||||
@@ -96,12 +98,13 @@ fn render_markdown_includes_expected_sections_and_preserves_comment_markdown() {
|
||||
fn render_markdown_uses_minimal_fence_for_plain_text() {
|
||||
let body = "plain text";
|
||||
let doc = PrReviewDocument {
|
||||
version: "v1".into(),
|
||||
meta: PrMeta {
|
||||
repo: "org/repo".into(),
|
||||
pr_index: 1,
|
||||
title: "T".into(),
|
||||
description: None,
|
||||
fetched_at: None,
|
||||
fetched_at: "2026-04-08T12:34:56Z".into(),
|
||||
state: "open".into(),
|
||||
author: "alice".into(),
|
||||
base_branch: "main".into(),
|
||||
|
||||
Reference in New Issue
Block a user