feat(linkedin-studio): RE-R2b — dated morning-brief artifact + session-start surfacing [skip-docs]
The visible layer of R2. Pure brief.ts: rankForBrief (pillar-overlap -> recency over the store; publishedAt ?? capturedAt freshness, 7d window; total-order sort), renderBrief (dated Markdown + hook-surfaceable summary frontmatter), briefSummary (one summary source), defaultBriefDir (derived from defaultStorePath). CLI `brief` writes <data>/trends/morning-brief/YYYY-MM-DD.md; session-start surfaces the latest zero-tsx (latestMorningBrief). Wired into trend-spotter Step 4.6 (scan->capture->brief->surfaced). No store-schema/scoring change; no scheduler (R3). 25 new trends tests (21 brief.test + 4 cli brief, RED-first) + 3 hook tests (morning-brief surfacing). trends 104/104 (floor 104), hook-suite 139/139, gate FAIL=0 (ASSERT floor 94, Section 16i: cli brief-handler + trend-spotter brief-pointer + session-start surfacing greps), tsc clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VmHCQjJHUyWwxGAVVjNLgp
This commit is contained in:
parent
88fbbadb1b
commit
fa7551070e
9 changed files with 712 additions and 10 deletions
|
|
@ -2,7 +2,7 @@ import { describe, test } from "node:test";
|
|||
import assert from "node:assert/strict";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { mkdtempSync, rmSync, readFileSync } from "node:fs";
|
||||
import { mkdtempSync, rmSync, readFileSync, existsSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
|
||||
|
|
@ -159,3 +159,94 @@ describe("trends CLI — normalize/score subcommands (RE-R1 / Step 4)", () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("trends CLI — brief subcommand (RE-R2b / Step 3)", () => {
|
||||
// brief is flag-driven (reads the store, not stdin). spawn with an env-overridable
|
||||
// LINKEDIN_STUDIO_DATA so defaultBriefDir() resolves into a temp root (never the real HOME).
|
||||
function runBrief(args: string[], env: Record<string, string> = {}): { status: number | null; stdout: string } {
|
||||
const res = spawnSync("node", ["--import", "tsx", "src/cli.ts", "brief", ...args], {
|
||||
input: "",
|
||||
encoding: "utf8",
|
||||
cwd: trendsDir,
|
||||
env: { ...process.env, ...env },
|
||||
});
|
||||
return { status: res.status, stdout: res.stdout };
|
||||
}
|
||||
const freshIso = new Date(Date.now() - 2 * 86400000).toISOString().slice(0, 10);
|
||||
function seedStore(trends: unknown[]): string {
|
||||
const store = join(mkdtempSync(join(tmpdir(), "trends-brief-")), "trends.json");
|
||||
writeFileSync(store, JSON.stringify({ schemaVersion: 2, trends }));
|
||||
return store;
|
||||
}
|
||||
|
||||
test("happy: writes a dated brief, --json carries path/date/totals/summary", () => {
|
||||
const store = seedStore([
|
||||
{ id: "a", title: "Fresh Match", url: "https://e/a", source: "tavily", capturedAt: freshIso, publishedAt: freshIso, topics: ["ai", "gov"] },
|
||||
]);
|
||||
const out = mkdtempSync(join(tmpdir(), "brief-out-"));
|
||||
try {
|
||||
const { status, stdout } = runBrief(["--pillars", "ai,gov", "--store", store, "--out", out, "--json"]);
|
||||
assert.equal(status, 0);
|
||||
const summary = JSON.parse(stdout);
|
||||
assert.match(summary.path, /\d{4}-\d{2}-\d{2}\.md$/);
|
||||
assert.match(summary.date, /^\d{4}-\d{2}-\d{2}$/);
|
||||
assert.equal(summary.totals.trends, 1);
|
||||
assert.equal(summary.totals.fresh, 1);
|
||||
assert.ok(existsSync(summary.path), "the dated brief file is written");
|
||||
const md = readFileSync(summary.path, "utf8");
|
||||
const m = md.match(/^summary: (.*)$/m);
|
||||
assert.ok(m, "the brief frontmatter has a summary line");
|
||||
assert.equal(m![1], summary.summary, "--json summary equals the file frontmatter summary (one source)");
|
||||
} finally {
|
||||
rmSync(join(store, ".."), { recursive: true, force: true });
|
||||
rmSync(out, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("bad invocation: --fresh-days non-numeric -> exit 2", () => {
|
||||
const store = seedStore([]);
|
||||
try {
|
||||
const { status } = runBrief(["--pillars", "ai", "--store", store, "--fresh-days", "xyz"]);
|
||||
assert.equal(status, 2);
|
||||
} finally {
|
||||
rmSync(join(store, ".."), { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("empty --pillars -> exit 0 + a no-match brief is still written", () => {
|
||||
const store = seedStore([
|
||||
{ id: "a", title: "X", url: "https://e/x", source: "tavily", capturedAt: freshIso, publishedAt: freshIso, topics: ["ai"] },
|
||||
]);
|
||||
const out = mkdtempSync(join(tmpdir(), "brief-out-"));
|
||||
try {
|
||||
const { status, stdout } = runBrief(["--store", store, "--out", out, "--json"]);
|
||||
assert.equal(status, 0);
|
||||
const summary = JSON.parse(stdout);
|
||||
assert.equal(summary.totals.matched, 0, "no pillars -> nothing matched");
|
||||
assert.ok(existsSync(summary.path), "a dated no-match brief is still written");
|
||||
} finally {
|
||||
rmSync(join(store, ".."), { recursive: true, force: true });
|
||||
rmSync(out, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("bare --out (no value) -> falls back to defaultBriefDir, never ./true", () => {
|
||||
const store = seedStore([]);
|
||||
const dataRoot = mkdtempSync(join(tmpdir(), "brief-data-"));
|
||||
try {
|
||||
// bare --out --json: parseFlags yields out:"true"; the !== "true" guard must
|
||||
// fall back to defaultBriefDir() = <LINKEDIN_STUDIO_DATA>/trends/morning-brief.
|
||||
const { status, stdout } = runBrief(["--pillars", "ai", "--store", store, "--json", "--out"], { LINKEDIN_STUDIO_DATA: dataRoot });
|
||||
assert.equal(status, 0);
|
||||
const summary = JSON.parse(stdout);
|
||||
assert.ok(
|
||||
summary.path.startsWith(join(dataRoot, "trends", "morning-brief")),
|
||||
"bare --out must fall back to defaultBriefDir under LINKEDIN_STUDIO_DATA, not ./true",
|
||||
);
|
||||
assert.ok(!existsSync(join(trendsDir, "true")), "no ./true dir was created");
|
||||
} finally {
|
||||
rmSync(join(store, ".."), { recursive: true, force: true });
|
||||
rmSync(dataRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue