Update report generation to dynamically discover and display author commit data
Refactor `timmy-report.ts` to dynamically collect and display author commit samples from git log, update `context.md` to reflect dynamic author data, and adjust `timmy-report.md` to use the new dynamic contributor summary. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 90c7a60b-2c61-4699-b5c6-6a1ac7469a4d Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: cf2341e4-4927-4087-a7c9-a93340626de0 Replit-Helium-Checkpoint-Created: true
This commit is contained in:
@@ -61,9 +61,6 @@ process.stdout.write("Collecting git data…\n");
|
||||
const shortlog = git("shortlog -sn HEAD");
|
||||
const logOneline = git("log --oneline HEAD");
|
||||
|
||||
const alexSample = git(`log HEAD --author="alexpaynex" --pretty=format:"%h %s" --stat -10`);
|
||||
const replitAgentSample = git(`log HEAD --author="Replit Agent" --pretty=format:"%h %s" --stat -10`);
|
||||
|
||||
// Validate that git data is non-empty — fail loudly rather than commit blank sections
|
||||
if (!shortlog || shortlog === "(git command failed)") {
|
||||
throw new Error(`git shortlog returned empty output. ROOT=${ROOT}`);
|
||||
@@ -72,7 +69,24 @@ if (!logOneline || logOneline === "(git command failed)") {
|
||||
throw new Error(`git log returned empty output. ROOT=${ROOT}`);
|
||||
}
|
||||
|
||||
process.stdout.write(` ✓ git data collected (${shortlog.split("\n").length} contributors, ${logOneline.split("\n").length} commits)\n`);
|
||||
// Derive author list dynamically from shortlog output
|
||||
// Each line looks like: " 127 Author Name"
|
||||
const authors: string[] = shortlog
|
||||
.split("\n")
|
||||
.map((line) => line.trim().replace(/^\d+\s+/, ""))
|
||||
.filter((name) => name.length > 0 && name !== "(git command failed)");
|
||||
|
||||
// Exclude Replit system identities (no meaningful code to sample)
|
||||
const SYSTEM_IDENTITIES = new Set(["replit", "agent"]);
|
||||
const codeAuthors = authors.filter((a) => !SYSTEM_IDENTITIES.has(a.toLowerCase()));
|
||||
|
||||
// Collect per-author stat samples for all code contributors
|
||||
const authorSamples: Record<string, string> = {};
|
||||
for (const author of codeAuthors) {
|
||||
authorSamples[author] = git(`log HEAD --author="${author}" --pretty=format:"%h %s" --stat -10`);
|
||||
}
|
||||
|
||||
process.stdout.write(` ✓ git data collected (${authors.length} contributors, ${logOneline.split("\n").length} commits)\n`);
|
||||
|
||||
// ── Collect source file excerpts ──────────────────────────────────────────────
|
||||
|
||||
@@ -154,21 +168,13 @@ ${logOneline}
|
||||
|
||||
---
|
||||
|
||||
## alexpaynex — Sample commits with diff stats (last 10)
|
||||
${Object.entries(authorSamples).map(([author, sample]) => `## ${author} — Sample commits with diff stats (last 10)
|
||||
|
||||
\`\`\`
|
||||
${alexSample}
|
||||
${sample}
|
||||
\`\`\`
|
||||
|
||||
---
|
||||
|
||||
## Replit Agent — Sample commits with diff stats (last 10)
|
||||
|
||||
\`\`\`
|
||||
${replitAgentSample}
|
||||
\`\`\`
|
||||
|
||||
---
|
||||
---`).join("\n\n")}
|
||||
|
||||
## Key Source File Excerpts
|
||||
|
||||
@@ -221,11 +227,9 @@ ${shortlog}
|
||||
FULL COMMIT LOG:
|
||||
${logOneline}
|
||||
|
||||
ALEXPAYNEX — LAST 10 COMMITS WITH STATS:
|
||||
${alexSample}
|
||||
|
||||
REPLIT AGENT — LAST 10 COMMITS WITH STATS:
|
||||
${replitAgentSample}
|
||||
${Object.entries(authorSamples).map(([author, sample]) =>
|
||||
`${author.toUpperCase()} — LAST 10 COMMITS WITH STATS:\n${sample}`
|
||||
).join("\n\n")}
|
||||
|
||||
KEY SOURCE FILES:
|
||||
|
||||
@@ -302,7 +306,7 @@ async function callClaude(systemPrompt: string, userContent: string): Promise<st
|
||||
async function main(): Promise<void> {
|
||||
if (STUB_MODE) {
|
||||
process.stdout.write(
|
||||
"\nWarning: AI_INTEGRATIONS_ANTHROPIC_BASE_URL / ANTHROPIC_API_KEY not set — writing stub Timmy report.\n",
|
||||
"\nWarning: AI_INTEGRATIONS_ANTHROPIC_BASE_URL / AI_INTEGRATIONS_ANTHROPIC_API_KEY not set — writing stub Timmy report.\n",
|
||||
);
|
||||
const stubReport = `# Timmy's Rubric Report (Stub Mode)
|
||||
|
||||
@@ -321,6 +325,25 @@ pnpm --filter @workspace/scripts timmy-report
|
||||
process.stdout.write("\nCalling Claude (claude-haiku-4-5) for Timmy's report…\n");
|
||||
const timmyReport = await callClaude(TIMMY_SYSTEM, userPrompt);
|
||||
|
||||
// Post-generation sanity check — catch malformed or truncated model outputs early
|
||||
// Match case-insensitively since Claude may use "PART 1" or "Part 1"
|
||||
const REQUIRED_SECTIONS = ["part 1", "part 2", "part 3"];
|
||||
const lowerReport = timmyReport.toLowerCase();
|
||||
const missingSections = REQUIRED_SECTIONS.filter((s) => !lowerReport.includes(s));
|
||||
const MIN_LINES = 30;
|
||||
const actualLines = timmyReport.split("\n").length;
|
||||
|
||||
if (missingSections.length > 0) {
|
||||
process.stderr.write(
|
||||
`Warning: timmy-report.md is missing sections: ${missingSections.join(", ")} — model output may be malformed.\n`,
|
||||
);
|
||||
}
|
||||
if (actualLines < MIN_LINES) {
|
||||
process.stderr.write(
|
||||
`Warning: timmy-report.md has only ${actualLines} lines (expected ≥${MIN_LINES}) — model output may be truncated.\n`,
|
||||
);
|
||||
}
|
||||
|
||||
const header = `# Timmy's Rubric Report
|
||||
## Repo: \`replit/token-gated-economy\` (Timmy Tower World)
|
||||
|
||||
@@ -333,7 +356,7 @@ pnpm --filter @workspace/scripts timmy-report
|
||||
`;
|
||||
|
||||
writeFileSync(join(ROOT, "reports/timmy-report.md"), header + timmyReport, "utf8");
|
||||
process.stdout.write(" ✓ reports/timmy-report.md written\n\nDone. Both reports are in reports/\n");
|
||||
process.stdout.write(` ✓ reports/timmy-report.md written (${actualLines} lines)\n\nDone. Both reports are in reports/\n`);
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
|
||||
Reference in New Issue
Block a user