feat: add daily summaries feature
- Backend: daily_summaries table, API routes (GET/POST/PATCH) at /api/summaries - Frontend: SummariesPage with calendar view, markdown rendering, stats bar, highlights - Sidebar nav: added Summaries link between Activity and Chat - Data population script for importing from memory files - Bearer token + session auth support
This commit is contained in:
101
backend/src/scripts/populate-summaries.ts
Normal file
101
backend/src/scripts/populate-summaries.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Populate daily_summaries from ~/clawd/memory/*.md files.
|
||||
* Usage: bun run src/scripts/populate-summaries.ts
|
||||
*/
|
||||
import { db } from "../db";
|
||||
import { dailySummaries } from "../db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { readdir, readFile } from "fs/promises";
|
||||
import { join } from "path";
|
||||
|
||||
const MEMORY_DIR = process.env.MEMORY_DIR || "/home/clawdbot/clawd/memory";
|
||||
|
||||
function extractHighlights(content: string): { text: string }[] {
|
||||
const highlights: { text: string }[] = [];
|
||||
const lines = content.split("\n");
|
||||
|
||||
for (const line of lines) {
|
||||
// Match ## headings as key sections
|
||||
const h2Match = line.match(/^## (.+)/);
|
||||
if (h2Match) {
|
||||
highlights.push({ text: h2Match[1].trim() });
|
||||
}
|
||||
}
|
||||
|
||||
return highlights.slice(0, 20); // Cap at 20 highlights
|
||||
}
|
||||
|
||||
function extractStats(content: string): Record<string, number> {
|
||||
const lower = content.toLowerCase();
|
||||
const stats: Record<string, number> = {};
|
||||
|
||||
// Count deploy mentions
|
||||
const deployMatches = lower.match(/\b(deploy|deployed|deployment|redeployed)\b/g);
|
||||
if (deployMatches) stats.deploys = deployMatches.length;
|
||||
|
||||
// Count commit/push mentions
|
||||
const commitMatches = lower.match(/\b(commit|committed|pushed|push)\b/g);
|
||||
if (commitMatches) stats.commits = commitMatches.length;
|
||||
|
||||
// Count task mentions
|
||||
const taskMatches = lower.match(/\b(completed|task completed|hq-\d+.*completed)\b/g);
|
||||
if (taskMatches) stats.tasksCompleted = taskMatches.length;
|
||||
|
||||
// Count feature mentions
|
||||
const featureMatches = lower.match(/\b(feature|built|implemented|added|created)\b/g);
|
||||
if (featureMatches) stats.featuresBuilt = Math.min(featureMatches.length, 30);
|
||||
|
||||
// Count fix mentions
|
||||
const fixMatches = lower.match(/\b(fix|fixed|bug|bugfix|hotfix)\b/g);
|
||||
if (fixMatches) stats.bugsFixed = fixMatches.length;
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log(`Reading memory files from ${MEMORY_DIR}...`);
|
||||
|
||||
const files = await readdir(MEMORY_DIR);
|
||||
const mdFiles = files
|
||||
.filter((f) => /^\d{4}-\d{2}-\d{2}\.md$/.test(f))
|
||||
.sort();
|
||||
|
||||
console.log(`Found ${mdFiles.length} memory files`);
|
||||
|
||||
for (const file of mdFiles) {
|
||||
const date = file.replace(".md", "");
|
||||
const filePath = join(MEMORY_DIR, file);
|
||||
const content = await readFile(filePath, "utf-8");
|
||||
|
||||
const highlights = extractHighlights(content);
|
||||
const stats = extractStats(content);
|
||||
|
||||
// Upsert
|
||||
const existing = await db
|
||||
.select()
|
||||
.from(dailySummaries)
|
||||
.where(eq(dailySummaries.date, date))
|
||||
.limit(1);
|
||||
|
||||
if (existing.length > 0) {
|
||||
await db
|
||||
.update(dailySummaries)
|
||||
.set({ content, highlights, stats, updatedAt: new Date() })
|
||||
.where(eq(dailySummaries.date, date));
|
||||
console.log(`Updated: ${date}`);
|
||||
} else {
|
||||
await db
|
||||
.insert(dailySummaries)
|
||||
.values({ date, content, highlights, stats });
|
||||
console.log(`Inserted: ${date}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Done!");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error("Failed:", e);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user