mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-05 16:34:00 +01:00
fix: date a review-log row from its binding start when it has no timestamp
The Step 9.4 template asks the writer to fill `timestamp`; the logger stamps `review_binding.started_at` but never `timestamp`. `gstack-review-scope summary` dropped such a row silently, so this PR's own pass 2 was missing from REVIEW_LOOP (passes=1 for a two-pass loop). A row now dates from `timestamp`, else from its binding start; a row with neither is still not counted. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
5bace0f5ba
commit
107b076ab4
2 files changed
+23
-2
No files matched your search
+11
-2
@@ -224,10 +224,19 @@ export function renderDeferredSection(deferred: readonly DeferredRecord[], noted
|
||||
export interface ReviewLoopSummary { passes: number; blocking_found: number; deferred: number; noted: number; fixed: number; scope: ReviewScopePolicy | 'unknown' }
|
||||
|
||||
/** Review-log rows of one /ship invocation (skill review, via ship, at or after `since`) → the loop summary. */
|
||||
/** When a review-log row was written: its `timestamp`, else the start of the pass the logger bound it to (the row's writer fills `timestamp`; the logger does not). */
|
||||
function rowTime(row: Record<string, any>): number {
|
||||
for (const value of [row.timestamp, row.review_binding?.started_at]) {
|
||||
const ms = typeof value === 'string' ? Date.parse(value) : NaN;
|
||||
if (Number.isFinite(ms)) return ms;
|
||||
}
|
||||
return NaN;
|
||||
}
|
||||
|
||||
export function summarizeReviewLoop(rows: readonly Record<string, any>[], since: string): ReviewLoopSummary {
|
||||
const sinceMs = Date.parse(since);
|
||||
const passes = rows.filter(r => r && r.skill === 'review' && r.via === 'ship' && typeof r.timestamp === 'string'
|
||||
&& Number.isFinite(Date.parse(r.timestamp)) && (!Number.isFinite(sinceMs) || Date.parse(r.timestamp) >= sinceMs));
|
||||
const passes = rows.filter(r => r && r.skill === 'review' && r.via === 'ship' && Number.isFinite(rowTime(r))
|
||||
&& (!Number.isFinite(sinceMs) || rowTime(r) >= sinceMs));
|
||||
const blocking = new Set<string>();
|
||||
const deferred = new Set<string>();
|
||||
const noted = new Set<string>();
|
||||
|
||||
@@ -176,6 +176,18 @@ describe('loop summary', () => {
|
||||
expect(s).toEqual({ passes: 2, blocking_found: 2, deferred: 1, noted: 1, fixed: 1, scope: 'frozen' });
|
||||
expect(reviewLoopLine(s)).toBe('REVIEW_LOOP: passes=2 blocking_found=2 deferred=1 noted=1 fixed=1 scope=frozen');
|
||||
});
|
||||
|
||||
// A pass logged without the template's timestamp (the logger stamps review_binding.started_at, never
|
||||
// timestamp) is still this invocation's pass; dropping it silently under-reported passes on #3481.
|
||||
test('a row with no timestamp dates from its binding start; a row with neither is not counted', () => {
|
||||
const rows = [
|
||||
{ skill: 'review', via: 'ship', timestamp: '2026-10-26T10:00:00Z', review_scope: 'frozen', findings: [{ fingerprint: 'a:1:x', blocking: true, action: 'auto-fixed' }] },
|
||||
{ skill: 'review', via: 'ship', review_scope: 'frozen', review_binding: { state: 'changed', started_at: '2026-10-26T10:30:00.000Z' }, findings: [{ fingerprint: 'c:1:z', action: 'noted' }] },
|
||||
{ skill: 'review', via: 'ship', review_binding: { state: 'changed', started_at: '2026-10-26T09:00:00.000Z' }, findings: [{ fingerprint: 'old:1:q', action: 'noted' }] },
|
||||
{ skill: 'review', via: 'ship', findings: [{ fingerprint: 'undated:1:q', action: 'noted' }] },
|
||||
];
|
||||
expect(summarizeReviewLoop(rows, '2026-10-26T09:30:00Z')).toEqual({ passes: 2, blocking_found: 1, deferred: 0, noted: 1, fixed: 1, scope: 'frozen' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('gstack-review-scope (bin)', () => {
|
||||
|
||||
Reference in new issue
Block a user