Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
06815ad
docs(reviews): design /release-review periodic full-codebase review
JohnMcLear May 9, 2026
1f10322
docs(reviews): plan /release-review implementation
JohnMcLear May 9, 2026
df99fc3
chore(reviews): scaffold /release-review directory structure
JohnMcLear May 10, 2026
9ebf9da
feat(reviews): add shared types for /release-review helpers
JohnMcLear May 10, 2026
ac0eb1f
feat(reviews): add fingerprint helper with whitespace-stable hashing
JohnMcLear May 10, 2026
b9c3405
chore(deps): add js-yaml as direct dep for release review suppression…
JohnMcLear May 10, 2026
597289f
feat(reviews): add known-findings.yml load/append with validation
JohnMcLear May 10, 2026
cd503c0
feat(reviews): add finding aggregation with dedupe and severity floor
JohnMcLear May 10, 2026
d095722
feat(reviews): add heuristic auto-triage classifier
JohnMcLear May 10, 2026
5f7b118
feat(reviews): add run-id generation and run-dir helpers
JohnMcLear May 10, 2026
53d330d
feat(reviews): add session summary writer
JohnMcLear May 10, 2026
364941d
feat(reviews): add CLI entry point for /release-review helpers
JohnMcLear May 10, 2026
b883273
feat(reviews): add Phase 1 tools subagent prompt
JohnMcLear May 10, 2026
4aa960b
feat(reviews): add Phase 2 auth-sessions subagent prompt
JohnMcLear May 10, 2026
8c6582c
feat(reviews): add Phase 2 realtime-api subagent prompt
JohnMcLear May 10, 2026
ef757ce
feat(reviews): add Phase 2 pad-changeset subagent prompt
JohnMcLear May 10, 2026
adc7403
feat(reviews): add Phase 2 db-supply subagent prompt
JohnMcLear May 10, 2026
fd0b814
fix(reviews): anchor realtime-api scope paths to {{repo_root}}
JohnMcLear May 10, 2026
1d87dfd
fix(reviews): anchor pad-changeset scope paths to {{repo_root}}
JohnMcLear May 10, 2026
d49de43
fix(reviews): anchor db-supply scope paths to {{repo_root}}
JohnMcLear May 10, 2026
83348bd
feat(reviews): add /release-review slash command orchestrator
JohnMcLear May 10, 2026
f04fc4b
fix(reviews): inline run-id in slash command (Bash tool has no shell-…
JohnMcLear May 10, 2026
9ec1291
docs(reviews): operator guide for /release-review
JohnMcLear May 10, 2026
f9f24b8
fix(reviews): drop redundant src/ prefix from cli.ts path (pnpm exec …
JohnMcLear May 10, 2026
8d9d8d9
fix(reviews): aggregate accepts repoRoot to resolve repo-relative paths
JohnMcLear May 10, 2026
a83054c
fix(reviews): include fingerprint/file/ruleId in all decision appends
JohnMcLear May 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix(reviews): aggregate accepts repoRoot to resolve repo-relative paths
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
  • Loading branch information
JohnMcLear and claude committed May 11, 2026
commit 8d9d8d9cb4da20dbb652ce5735f41eecd7406a02
2 changes: 1 addition & 1 deletion .claude/commands/release-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Block until all four complete. Verify each output JSON exists. For any that didn

```bash
pnpm --filter ep_etherpad-lite exec tsx node/utils/releaseReview/cli.ts \
aggregate /tmp/release-review/<run-id> docs/reviews/known-findings.yml medium
aggregate /tmp/release-review/<run-id> docs/reviews/known-findings.yml medium "$(git rev-parse --show-toplevel)"
```
(Replace `<run-id>` with the literal run-id from Phase 0.)
Reads all `*.json` from the run-dir except `merged.json` / `triage.json`. Writes `merged.json`.
Expand Down
13 changes: 7 additions & 6 deletions src/node/utils/releaseReview/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,18 @@ const cmds: Record<string, (args: string[]) => void> = {
},

aggregate: (args) => {
const [runDir, supPath, floor] = args;
if (!runDir || !supPath || !floor) die('usage: aggregate <runDir> <suppressionPath> <severityFloor>');
const [runDir, supPath, floor, repoRoot] = args;
if (!runDir || !supPath || !floor || !repoRoot) die('usage: aggregate <runDir> <suppressionPath> <severityFloor> <repoRoot>');
const fileLineCache = new Map<string, string[]>();
const readLines = (file: string): string[] => {
if (!fileLineCache.has(file)) {
const abs = path.isAbsolute(file) ? file : path.join(repoRoot, file);
if (!fileLineCache.has(abs)) {
fileLineCache.set(
file,
fs.existsSync(file) ? fs.readFileSync(file, 'utf8').split('\n') : [],
abs,
fs.existsSync(abs) ? fs.readFileSync(abs, 'utf8').split('\n') : [],
);
}
return fileLineCache.get(file)!;
return fileLineCache.get(abs)!;
};
const enrich = (raw: any): Finding => {
// Subagent JSON may be top-level array OR {findings: [...]}.
Expand Down
29 changes: 27 additions & 2 deletions src/tests/backend/specs/releaseReview-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ describe(__filename, function () {
]));
const supPath = path.join(tmpDir, 'sup.yml');
fs.writeFileSync(supPath, 'findings: []\n');
runCli(['aggregate', runDir, supPath, 'medium']);
runCli(['aggregate', runDir, supPath, 'medium', '/']);
const merged = JSON.parse(fs.readFileSync(path.join(runDir, 'merged.json'), 'utf8'));
assert.equal(merged.length, 1);
assert.equal(merged[0].severity, 'high');
Expand All @@ -339,11 +339,36 @@ describe(__filename, function () {
}));
const supPath = path.join(tmpDir, 'sup-empty.yml');
fs.writeFileSync(supPath, 'findings: []\n');
runCli(['aggregate', runDir, supPath, 'medium']);
runCli(['aggregate', runDir, supPath, 'medium', '/']);
const merged = JSON.parse(fs.readFileSync(path.join(runDir, 'merged.json'), 'utf8'));
assert.equal(merged.length, 1);
assert.match(merged[0].fingerprint, /^[0-9a-f]{64}$/);
});

it('aggregate resolves repo-relative file paths against repoRoot', function () {
this.timeout(15000);
const runDir = path.join(tmpDir, 'run-2026-05-09-3');
fs.mkdirSync(runDir);
// Use a relative path that requires repoRoot resolution.
const fakeRepoRoot = FIXTURE_DIR;
const relPath = 'sample-source.ts'; // exists at FIXTURE_DIR/sample-source.ts
fs.writeFileSync(path.join(runDir, 'auth-sessions.json'), JSON.stringify({
findings: [
{source: 'auth-sessions', severity: 'high', category: 'bug', file: relPath, line: 6, ruleId: 'auth-sessions.x', message: 'm'},
],
}));
const supPath = path.join(tmpDir, 'sup-rel.yml');
fs.writeFileSync(supPath, 'findings: []\n');
runCli(['aggregate', runDir, supPath, 'medium', fakeRepoRoot]);
const merged = JSON.parse(fs.readFileSync(path.join(runDir, 'merged.json'), 'utf8'));
assert.equal(merged.length, 1);
// Fingerprint should be computed from real file content.
// Compare to a known fingerprint we can derive directly.
const {computeFingerprint} = require('../../../node/utils/releaseReview/fingerprint');
const lines = fs.readFileSync(path.join(fakeRepoRoot, relPath), 'utf8').split('\n');
const expected = computeFingerprint('auth-sessions.x', relPath, 6, lines);
assert.equal(merged[0].fingerprint, expected);
});
});

describe('suppression', function () {
Expand Down