Spaces:
Running
Running
Commit ·
16ff884
1
Parent(s): 1759d72
feat: add blocked sync notification builder
Browse files
.claude/skills/sync-nowai-leaderboard/lib/blocked-notify.mjs
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const REASON_LABELS = {
|
| 2 |
+
'dirty-worktree': 'Worktree has uncommitted files',
|
| 3 |
+
'wrong-branch': 'Current branch is not main',
|
| 4 |
+
'fetch-failed': 'git fetch origin main failed',
|
| 5 |
+
'behind-or-diverged': 'Local main is behind or diverged from origin/main',
|
| 6 |
+
};
|
| 7 |
+
|
| 8 |
+
export function blockedSubtitle(now = new Date()) {
|
| 9 |
+
return 'NowAI-Bench Sync · ' + now.toISOString().replace('T', ' ').slice(0, 16) + ' UTC';
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
export function buildBlockedNotification({ reason, facts = [], now = new Date() }) {
|
| 13 |
+
const label = REASON_LABELS[reason];
|
| 14 |
+
if (!label) throw new Error(`Unknown blocked reason: ${reason}`);
|
| 15 |
+
|
| 16 |
+
return {
|
| 17 |
+
kind: 'warning',
|
| 18 |
+
title: '⚠️ Leaderboard sync blocked',
|
| 19 |
+
subtitle: blockedSubtitle(now),
|
| 20 |
+
facts: [['Reason', label], ...facts],
|
| 21 |
+
};
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
export function parseBlockedCliArgs(argv) {
|
| 25 |
+
const facts = [];
|
| 26 |
+
let reason;
|
| 27 |
+
|
| 28 |
+
for (let i = 0; i < argv.length; i++) {
|
| 29 |
+
const token = argv[i];
|
| 30 |
+
|
| 31 |
+
if (token === '--reason') {
|
| 32 |
+
reason = argv[++i];
|
| 33 |
+
continue;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
if (token === '--fact') {
|
| 37 |
+
const raw = argv[++i] ?? '';
|
| 38 |
+
const eq = raw.indexOf('=');
|
| 39 |
+
if (eq <= 0) throw new Error(`Invalid --fact, expected key=value: ${raw}`);
|
| 40 |
+
facts.push([raw.slice(0, eq), raw.slice(eq + 1)]);
|
| 41 |
+
continue;
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
throw new Error(`Unknown argument: ${token}`);
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
if (!reason) throw new Error('Missing required --reason');
|
| 48 |
+
|
| 49 |
+
// Reuse payload validation so the CLI parser and notifier stay in lockstep.
|
| 50 |
+
buildBlockedNotification({ reason, facts, now: new Date(0) });
|
| 51 |
+
|
| 52 |
+
return { reason, facts };
|
| 53 |
+
}
|
.claude/skills/sync-nowai-leaderboard/test/blocked-notify.test.mjs
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { test } from 'node:test';
|
| 2 |
+
import assert from 'node:assert/strict';
|
| 3 |
+
import {
|
| 4 |
+
buildBlockedNotification,
|
| 5 |
+
parseBlockedCliArgs,
|
| 6 |
+
} from '../lib/blocked-notify.mjs';
|
| 7 |
+
|
| 8 |
+
test('buildBlockedNotification maps dirty-worktree to a warning card payload', () => {
|
| 9 |
+
const out = buildBlockedNotification({
|
| 10 |
+
reason: 'dirty-worktree',
|
| 11 |
+
facts: [['Branch', 'main'], ['Files', '3']],
|
| 12 |
+
now: new Date('2026-07-01T21:00:00Z'),
|
| 13 |
+
});
|
| 14 |
+
|
| 15 |
+
assert.equal(out.kind, 'warning');
|
| 16 |
+
assert.equal(out.title, '⚠️ Leaderboard sync blocked');
|
| 17 |
+
assert.equal(out.subtitle, 'NowAI-Bench Sync · 2026-07-01 21:00 UTC');
|
| 18 |
+
assert.deepEqual(out.facts, [
|
| 19 |
+
['Reason', 'Worktree has uncommitted files'],
|
| 20 |
+
['Branch', 'main'],
|
| 21 |
+
['Files', '3'],
|
| 22 |
+
]);
|
| 23 |
+
});
|
| 24 |
+
|
| 25 |
+
test('parseBlockedCliArgs accepts repeated --fact pairs', () => {
|
| 26 |
+
const out = parseBlockedCliArgs([
|
| 27 |
+
'--reason', 'behind-or-diverged',
|
| 28 |
+
'--fact', 'Branch=main',
|
| 29 |
+
'--fact', 'RemoteAhead=2',
|
| 30 |
+
'--fact', 'LocalAhead=0',
|
| 31 |
+
]);
|
| 32 |
+
|
| 33 |
+
assert.equal(out.reason, 'behind-or-diverged');
|
| 34 |
+
assert.deepEqual(out.facts, [
|
| 35 |
+
['Branch', 'main'],
|
| 36 |
+
['RemoteAhead', '2'],
|
| 37 |
+
['LocalAhead', '0'],
|
| 38 |
+
]);
|
| 39 |
+
});
|
| 40 |
+
|
| 41 |
+
test('parseBlockedCliArgs rejects unknown blocked reasons', () => {
|
| 42 |
+
assert.throws(
|
| 43 |
+
() => parseBlockedCliArgs(['--reason', 'mystery-reason']),
|
| 44 |
+
/Unknown blocked reason: mystery-reason/,
|
| 45 |
+
);
|
| 46 |
+
});
|
| 47 |
+
|
| 48 |
+
test('parseBlockedCliArgs rejects malformed fact entries', () => {
|
| 49 |
+
assert.throws(
|
| 50 |
+
() => parseBlockedCliArgs(['--reason', 'wrong-branch', '--fact', 'Branch']),
|
| 51 |
+
/Invalid --fact, expected key=value: Branch/,
|
| 52 |
+
);
|
| 53 |
+
});
|