feat(linkedin-thought-leadership): v1.1.0 — Q2 2026 feature release

9 improvements across 3 tracks:

Onboarding: /linkedin:onboarding wizard, README Quick Start rewrite
Content Quality: voice drift scoring, industry angle variants,
  /linkedin:carousel, /linkedin:react multi-URL comparison
Analytics: automated week-rollover, day-of-week heatmap,
  month-over-month reports

25→27 commands. All Q2 ROADMAP items completed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-04-08 06:16:35 +02:00
commit 1a8cc1942c
33 changed files with 1726 additions and 236 deletions

View file

@ -1,7 +1,7 @@
import { readFileSync, writeFileSync, readdirSync, existsSync, mkdirSync } from "node:fs";
import { join, resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import type { AnalyticsBatch, WeeklyReport, PostAnalytics } from "../models/types.js";
import type { AnalyticsBatch, WeeklyReport, MonthlyReport, PostAnalytics } from "../models/types.js";
const __dirname = dirname(fileURLToPath(import.meta.url));
@ -25,7 +25,7 @@ export function getAnalyticsRoot(): string {
* Ensure required subdirectories exist under analytics root
*/
export function ensureDirectories(root: string): void {
const directories = ["exports", "posts", "weekly-reports"];
const directories = ["exports", "posts", "weekly-reports", "monthly-reports"];
if (!existsSync(root)) {
mkdirSync(root, { recursive: true });
@ -252,3 +252,39 @@ export function loadAllWeeklyReports(root: string): WeeklyReport[] {
b.week.localeCompare(a.week)
);
}
/**
* Sanitize month string to only allow YYYY-MM format
*/
function sanitizeMonth(month: string): string {
if (!/^\d{4}-\d{2}$/.test(month)) {
throw new Error(`Invalid month format: ${month}. Expected YYYY-MM`);
}
return month;
}
/**
* Save a monthly report to disk
*/
export function saveMonthlyReport(root: string, report: MonthlyReport): string {
ensureDirectories(root);
const reportsDir = join(root, "monthly-reports");
const month = sanitizeMonth(report.month);
const filename = `${month}.json`;
const filepath = join(reportsDir, filename);
verifyPathWithinDirectory(filepath, reportsDir);
writeFileSync(filepath, JSON.stringify(report, null, 2), "utf-8");
return filename;
}
/**
* Load a specific monthly report by month identifier
*/
export function loadMonthlyReport(root: string, month: string): MonthlyReport | null {
month = sanitizeMonth(month);
const reportsDir = join(root, "monthly-reports");
const filepath = join(reportsDir, `${month}.json`);
if (!existsSync(filepath)) return null;
const content = readFileSync(filepath, "utf-8");
return JSON.parse(content) as MonthlyReport;
}