Skip to content

Commit 3e204de

Browse files
authored
Format the API change comment as a C# diff for readability (#38084)
1 parent 0de56fb commit 3e204de

5 files changed

Lines changed: 406 additions & 65 deletions

File tree

.agents/skills/run-apichief/SKILL.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ ApiChief can run against either a compiled assembly or a previously emitted base
1717
| `emit baseline` | Emit a JSON API baseline | `-o <file>` |
1818
| `emit summary` | Emit a human-readable API summary | `-o <file>`, `-x` |
1919
| `emit review` | Emit API review files | `-o <dir>`, `-n` |
20-
| `emit delta` | Emit a delta against an existing baseline | `<baseline-path>`, `-o <file>` |
20+
| `emit delta` | Emit a delta against an existing baseline, or markdown diff review files | `<baseline-path>`, `-o <file-or-dir>`, `--diff` |
2121
| `check breaking` | Fail if breaking changes exist vs. a baseline | `<baseline-path>` |
2222

2323
Default to `emit baseline` if the user only asks to "run ApiChief".
@@ -105,6 +105,9 @@ Before running, report the selected TFM if it matters for the task.
105105
106106
# Emit API review artifacts
107107
& $dotnet $apiChief $assemblyPath emit review -o ".\\artifacts\\tmp\\API.$name"
108+
109+
# Emit GitHub-friendly markdown diff files against a baseline
110+
& $dotnet $apiChief $assemblyPath emit delta ".\\src\\$name\\$name.baseline.json" --diff -o ".\\artifacts\\tmp\\API.$name.Diff"
108111
```
109112

110113
`emit delta` also supports passing a `.json` file as the current input instead of a DLL.
@@ -124,4 +127,5 @@ After `emit baseline`:
124127
- show the chosen TFM(s)
125128
- report the output path(s)
126129
- for `check breaking`, state pass/fail
127-
- for `emit delta`, mention that exit code `0` means changes, `2` means no changes, and `-1` means an error
130+
- for `emit delta` and `emit delta --diff`, mention that exit code `0` means changes, `2` means no changes, and `-1` means an error
131+
- prefer `emit delta --diff` output because it generates ready-to-post ```diff fenced markdown split across per-type `.md` files

.github/workflows/api-review-baselines.yml

Lines changed: 71 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ jobs:
101101
shell: bash
102102
run: ./restore.sh
103103

104-
- name: Build ApiChief and compute baseline deltas
104+
- name: Build ApiChief and compute review diffs
105105
if: steps.detect.outputs.has_baselines == 'true'
106106
id: delta
107107
shell: bash
@@ -157,25 +157,36 @@ jobs:
157157
return False
158158
raise
159159
160-
def truncate(text: str, limit: int = 20000) -> str:
161-
if len(text) <= limit:
162-
return text
163-
return text[:limit] + '\n... (truncated)\n'
160+
max_comment_length = 60000
161+
comment_bodies: list[str] = []
162+
current_body = ''
164163
165-
sections: list[str] = []
164+
def append_section(section: str) -> None:
165+
nonlocal current_body
166+
167+
if not current_body:
168+
current_body = section
169+
return
170+
171+
candidate = current_body + '\n\n' + section
172+
if len(candidate) > max_comment_length:
173+
comment_bodies.append(current_body)
174+
current_body = section
175+
else:
176+
current_body = candidate
166177
167178
for index, file in enumerate(files):
168179
filename = file['filename']
169180
old_path = workdir / f'{index}.old.baseline.json'
170181
new_path = workdir / f'{index}.new.baseline.json'
171-
delta_path = workdir / f'{index}.delta.json'
182+
review_dir = workdir / f'{index}.review'
172183
173184
old_exists = download_file(base_sha, filename, old_path)
174185
new_exists = download_file(target_sha, filename, new_path)
175186
176187
if old_exists and new_exists:
177188
result = subprocess.run(
178-
[dotnet, apichief, str(new_path), 'emit', 'delta', str(old_path), '-o', str(delta_path)],
189+
[dotnet, apichief, str(new_path), 'emit', 'delta', str(old_path), '--diff', '-o', str(review_dir)],
179190
capture_output=True,
180191
text=True,
181192
check=False)
@@ -185,54 +196,77 @@ jobs:
185196
186197
if result.returncode != 0:
187198
raise RuntimeError(
188-
f'ApiChief delta failed for {filename} with exit code {result.returncode}:\n'
199+
f'ApiChief delta diff failed for {filename} with exit code {result.returncode}:\n'
189200
f'{result.stdout}\n{result.stderr}')
190201
191-
delta_text = truncate(delta_path.read_text(encoding='utf-8'))
192-
sections.append(
193-
f"### `{filename}`\n\n"
194-
f"<details>\n<summary>Show delta</summary>\n\n```json\n{delta_text}\n```\n</details>")
202+
review_files = sorted(review_dir.rglob('*.md'))
203+
if not review_files:
204+
continue
205+
206+
section = (
207+
f"## API review baseline changes for `{filename}`\n\n"
208+
'The diff below was generated by `ApiChief` between the base and selected PR versions.\n\n')
209+
210+
for review_file in review_files:
211+
section += (
212+
f"{review_file.read_text(encoding='utf-8').rstrip()}\n\n")
213+
214+
append_section(section.rstrip())
195215
elif new_exists:
196-
sections.append(f"### `{filename}`\n\nThis baseline file was **added** in the selected PR.")
216+
append_section(
217+
f"## API review baseline changes for `{filename}`\n\n"
218+
'This baseline file was **added** in the selected PR.')
197219
elif old_exists:
198-
sections.append(f"### `{filename}`\n\nThis baseline file was **removed** in the selected PR.")
220+
append_section(
221+
f"## API review baseline changes for `{filename}`\n\n"
222+
'This baseline file was **removed** in the selected PR.')
199223
200-
if not sections:
201-
sections.append('No API deltas were produced for the modified baseline files.')
224+
if not current_body:
225+
append_section(
226+
'## API review baseline changes\n\n'
227+
'No API deltas were produced for the modified baseline files.')
202228
203-
body = (
204-
'## API review baseline changes\n\n'
205-
'This PR modified one or more `*.baseline.json` files. '
206-
'The deltas below were generated by `ApiChief` between the base and selected PR versions.\n\n'
207-
+ '\n\n'.join(sections)
208-
)
229+
if current_body:
230+
comment_bodies.append(current_body)
209231
210-
comment_path = workdir / 'comment.md'
211-
comment_path.write_text(body[:65000], encoding='utf-8')
232+
comments_dir = workdir / 'comments'
233+
comments_dir.mkdir(parents=True, exist_ok=True)
234+
235+
for index, body in enumerate(comment_bodies, start=1):
236+
comment_path = comments_dir / f'{index:03}.md'
237+
comment_path.write_text(body.rstrip() + '\n', encoding='utf-8')
212238
213239
with open(os.environ['GITHUB_OUTPUT'], 'a', encoding='utf-8') as output:
214-
output.write(f'comment_path={comment_path}\n')
240+
output.write(f'comments_dir={comments_dir}\n')
215241
PY
216242
217-
- name: Create PR comment with delta
243+
- name: Create PR comment with diffs
218244
if: steps.detect.outputs.has_baselines == 'true'
219245
uses: actions/github-script@v9
220246
env:
221-
COMMENT_PATH: ${{ steps.delta.outputs.comment_path }}
247+
COMMENTS_DIR: ${{ steps.delta.outputs.comments_dir }}
222248
PR_NUMBER: ${{ steps.detect.outputs.pr_number }}
223249
with:
224250
script: |
225251
const fs = require('fs');
252+
const path = require('path');
226253
const owner = context.repo.owner;
227254
const repo = context.repo.repo;
228255
const issue_number = Number(process.env.PR_NUMBER);
229-
const body = fs.readFileSync(process.env.COMMENT_PATH, 'utf8');
230-
231-
await github.rest.issues.createComment({
232-
owner,
233-
repo,
234-
issue_number,
235-
body
236-
});
256+
const commentsDir = process.env.COMMENTS_DIR;
257+
258+
const commentFiles = fs.readdirSync(commentsDir)
259+
.filter(file => file.endsWith('.md'))
260+
.sort((a, b) => a.localeCompare(b));
261+
262+
for (const file of commentFiles) {
263+
const body = fs.readFileSync(path.join(commentsDir, file), 'utf8');
264+
await github.rest.issues.createComment({
265+
owner,
266+
repo,
267+
issue_number,
268+
body
269+
});
270+
}
237271
238-
console.log(`Created new API review comment for PR #${issue_number}.`);
272+
console.log(`Created ${commentFiles.length} API review comment(s) for PR #${issue_number}.`);

0 commit comments

Comments
 (0)