Align scheduler tables with roster and add drift check

This commit is contained in:
thePR0M3TH3AN committed 2026-02-14 16:09:00 -05:00
1 parent f27929746e
commit 5b0cf980e9
4 files changed
+142 -17

No files matched your search

+3 -2
View File
@@ -27,7 +27,7 @@
"README.md" "README.md"
], ],
"scripts": { "scripts": {
"test": "node --test test/*.test.mjs tests/*.test.js", "test": "npm run validate:scheduler && node --test test/*.test.mjs tests/*.test.js",
"lock:check:daily": "node bin/torch-lock.mjs check --cadence daily", "lock:check:daily": "node bin/torch-lock.mjs check --cadence daily",
"lock:check:weekly": "node bin/torch-lock.mjs check --cadence weekly", "lock:check:weekly": "node bin/torch-lock.mjs check --cadence weekly",
"lock:list": "node bin/torch-lock.mjs list", "lock:list": "node bin/torch-lock.mjs list",
@@ -35,7 +35,8 @@
"dashboard:serve": "node bin/torch-lock.mjs dashboard", "dashboard:serve": "node bin/torch-lock.mjs dashboard",
"build": "node build.mjs", "build": "node build.mjs",
"lint": "eslint .", "lint": "eslint .",
"lock:complete": "node bin/torch-lock.mjs complete" "lock:complete": "node bin/torch-lock.mjs complete",
"validate:scheduler": "node scripts/validate-scheduler-roster.mjs"
}, },
"dependencies": { "dependencies": {
"nostr-tools": "2.19.4", "nostr-tools": "2.19.4",
+122
View File
@@ -0,0 +1,122 @@
import { readdir, readFile } from 'node:fs/promises';
const CONFIG = {
daily: {
schedulerFile: 'src/prompts/daily-scheduler.md',
promptDir: 'src/prompts/daily',
},
weekly: {
schedulerFile: 'src/prompts/weekly-scheduler.md',
promptDir: 'src/prompts/weekly',
},
};
function parseTableRows(markdown, schedulerFile) {
const rows = [];
for (const line of markdown.split('\n')) {
const match = line.match(/^\|\s*(\d+)\s*\|\s*([^|]+?)\s*\|\s*`([^`]+)`\s*\|\s*$/);
if (!match) {
continue;
}
rows.push({
index: Number(match[1]),
agent: match[2].trim(),
promptFile: match[3].trim(),
});
}
if (rows.length === 0) {
throw new Error(`No scheduler table rows found in ${schedulerFile}`);
}
return rows;
}
function diffLists(expected, actual) {
return {
missing: expected.filter((item) => !actual.includes(item)),
extra: actual.filter((item) => !expected.includes(item)),
};
}
function validateScheduler(cadence, roster, rows, promptFiles) {
const errors = [];
if (rows.length !== roster.length) {
errors.push(
`[${cadence}] row count mismatch: roster has ${roster.length}, table has ${rows.length}`,
);
}
for (let i = 0; i < rows.length; i += 1) {
const row = rows[i];
const expectedIndex = i + 1;
if (row.index !== expectedIndex) {
errors.push(`[${cadence}] row ${i + 1} has index ${row.index}, expected ${expectedIndex}`);
}
const expectedAgent = roster[i];
if (expectedAgent && row.agent !== expectedAgent) {
errors.push(
`[${cadence}] row ${i + 1} agent mismatch: table has "${row.agent}", roster expects "${expectedAgent}"`,
);
}
const expectedPromptFile = `${row.agent}.md`;
if (row.promptFile !== expectedPromptFile) {
errors.push(
`[${cadence}] row ${i + 1} prompt file mismatch: table has "${row.promptFile}", expected "${expectedPromptFile}"`,
);
}
}
const tableAgents = rows.map((row) => row.agent);
const rosterVsTable = diffLists(roster, tableAgents);
if (rosterVsTable.missing.length > 0 || rosterVsTable.extra.length > 0) {
errors.push(
`[${cadence}] roster/table mismatch: missing in table [${rosterVsTable.missing.join(', ')}], extra in table [${rosterVsTable.extra.join(', ')}]`,
);
}
const rosterPromptFiles = roster.map((agent) => `${agent}.md`);
const filesVsRoster = diffLists(rosterPromptFiles, promptFiles);
if (filesVsRoster.missing.length > 0 || filesVsRoster.extra.length > 0) {
errors.push(
`[${cadence}] roster/files mismatch: missing files [${filesVsRoster.missing.join(', ')}], extra files [${filesVsRoster.extra.join(', ')}]`,
);
}
const filesVsTable = diffLists(promptFiles, rows.map((row) => row.promptFile));
if (filesVsTable.missing.length > 0 || filesVsTable.extra.length > 0) {
errors.push(
`[${cadence}] table/files mismatch: missing in table [${filesVsTable.missing.join(', ')}], extra in table [${filesVsTable.extra.join(', ')}]`,
);
}
return errors;
}
const roster = JSON.parse(await readFile('src/prompts/roster.json', 'utf8'));
const allErrors = [];
for (const [cadence, cfg] of Object.entries(CONFIG)) {
const schedulerMd = await readFile(cfg.schedulerFile, 'utf8');
const rows = parseTableRows(schedulerMd, cfg.schedulerFile);
const promptFiles = (await readdir(cfg.promptDir))
.filter((filename) => filename.endsWith('.md'))
.sort((a, b) => a.localeCompare(b));
allErrors.push(...validateScheduler(cadence, roster[cadence] ?? [], rows, promptFiles));
}
if (allErrors.length > 0) {
console.error('Scheduler/roster drift detected:');
for (const error of allErrors) {
console.error(`- ${error}`);
}
process.exit(1);
}
console.log('Scheduler tables, roster, and prompt filenames are in sync.');
+3 -3
View File
@@ -28,9 +28,9 @@ Canonical roster source: `src/prompts/roster.json` (`daily` key).
| 11 | innerhtml-migration-agent | `innerhtml-migration-agent.md` | | 11 | innerhtml-migration-agent | `innerhtml-migration-agent.md` |
| 12 | known-issues-agent | `known-issues-agent.md` | | 12 | known-issues-agent | `known-issues-agent.md` |
| 13 | load-test-agent | `load-test-agent.md` | | 13 | load-test-agent | `load-test-agent.md` |
| 14 | onboarding-audit-agent | `onboarding-audit-agent.md` | | 14 | log-fixer-agent | `log-fixer-agent.md` |
| 15 | perf-agent | `perf-agent.md` | | 15 | onboarding-audit-agent | `onboarding-audit-agent.md` |
| 16 | prompt-curator-agent | `prompt-curator-agent.md` | | 16 | perf-agent | `perf-agent.md` |
| 17 | protocol-research-agent | `protocol-research-agent.md` | | 17 | protocol-research-agent | `protocol-research-agent.md` |
| 18 | scheduler-update-agent | `scheduler-update-agent.md` | | 18 | scheduler-update-agent | `scheduler-update-agent.md` |
| 19 | style-agent | `style-agent.md` | | 19 | style-agent | `style-agent.md` |
+14 -12
View File
@@ -18,17 +18,19 @@ Canonical roster source: `src/prompts/roster.json` (`weekly` key).
| 1 | bug-reproducer-agent | `bug-reproducer-agent.md` | | 1 | bug-reproducer-agent | `bug-reproducer-agent.md` |
| 2 | changelog-agent | `changelog-agent.md` | | 2 | changelog-agent | `changelog-agent.md` |
| 3 | dead-code-agent | `dead-code-agent.md` | | 3 | dead-code-agent | `dead-code-agent.md` |
| 4 | event-schema-agent | `event-schema-agent.md` | | 4 | feature-proposer-agent | `feature-proposer-agent.md` |
| 5 | frontend-console-debug-agent | `frontend-console-debug-agent.md` | | 5 | frontend-console-debug-agent | `frontend-console-debug-agent.md` |
| 6 | fuzz-agent | `fuzz-agent.md` | | 6 | fuzz-agent | `fuzz-agent.md` |
| 7 | interop-agent | `interop-agent.md` | | 7 | perf-deepdive-agent | `perf-deepdive-agent.md` |
| 8 | perf-deepdive-agent | `perf-deepdive-agent.md` | | 8 | perf-optimization-agent | `perf-optimization-agent.md` |
| 9 | perf-optimization-agent | `perf-optimization-agent.md` | | 9 | pr-review-agent | `pr-review-agent.md` |
| 10 | pr-review-agent | `pr-review-agent.md` | | 10 | prompt-maintenance-agent | `prompt-maintenance-agent.md` |
| 11 | race-condition-agent | `race-condition-agent.md` | | 11 | prompt-safety-agent | `prompt-safety-agent.md` |
| 12 | refactor-agent | `refactor-agent.md` | | 12 | race-condition-agent | `race-condition-agent.md` |
| 13 | repo-fit-agent | `repo-fit-agent.md` | | 13 | refactor-agent | `refactor-agent.md` |
| 14 | smoke-agent | `smoke-agent.md` | | 14 | repo-fit-agent | `repo-fit-agent.md` |
| 15 | telemetry-agent | `telemetry-agent.md` | | 15 | smoke-agent | `smoke-agent.md` |
| 16 | test-coverage-agent | `test-coverage-agent.md` | | 16 | telemetry-agent | `telemetry-agent.md` |
| 17 | weekly-synthesis-agent | `weekly-synthesis-agent.md` | | 17 | test-coverage-agent | `test-coverage-agent.md` |
| 18 | ui-ux-agent | `ui-ux-agent.md` |
| 19 | weekly-synthesis-agent | `weekly-synthesis-agent.md` |