feat(linkedin-studio): RE-R3e — brief history + day-over-day diff (surfaced: frontmatter + Nytt siden sist) [skip-docs]
Closes hull #7 ("ingen brief-historikk"). Each morning brief now records the trend ids it showed into its own YAML frontmatter (surfaced: <id-csv> = surfacedIds(ranking)) and renders a day-over-day diff against the most recent prior brief — a "## Nytt siden sist (<prior-date>)" section that leads the ranked list, plus a " N nye siden sist." marker on the one-line summary the SessionStart hook surfaces (no hook change). - brief.ts: BRIEF_SCHEMA_VERSION 1->2 (artifact frontmatter gained surfaced:; the store's SCHEMA_VERSION stays 4 — no store field). Three PURE helpers (diffSurfaced / parseSurfacedFrontmatter / selectPriorBriefFile) + the surfaced: emit + the section + the summary marker. No fs/clock in brief.ts. - cli.ts: the brief handler discovers the prior dated file (existsSync-guarded readdirSync -> selectPriorBriefFile, strict < today so a same-day re-run is byte-identical), parses its surfaced: line, computes the diff, threads it into renderBrief AND the shared briefSummary(ranking, diff) (one-source: file frontmatter == --json summary, cli.test one-source invariant). --json gains a diff:{priorDate,added,carried,dropped} counts object; the console line appends the delta. Any fs error degrades to the empty-prior (first-brief) path. TDD two-phase: stubs -> 17 value-RED (no module-not-found) -> GREEN. Trends suite 216 -> 245 (brief +27, cli +2), 0 fail. New unconditional gate Section 16n (6 checks); ASSERT_BASELINE_FLOOR 117 -> 123; TRENDS_TESTS_FLOOR -> 245. Full gate FAIL=0; hook suite 139/139 + R3c schedule/run-daily green untouched. Behavioural: real two-day rename-real-write diff + same-day byte-identity confirmed. Counts 29/19/27 unchanged; no version bump (additive, v0.5.2 dev). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011vmzxpsFpc8q19LaogAWLD
This commit is contained in:
parent
ddedb3d1de
commit
5b51b4baeb
7 changed files with 462 additions and 18 deletions
|
|
@ -48,7 +48,7 @@
|
|||
* `schedule` adds no new exit code: an autonomy install never RUNS the system mutation.
|
||||
*/
|
||||
|
||||
import { readFileSync, mkdirSync, writeFileSync, existsSync, rmSync } from "node:fs";
|
||||
import { readFileSync, readdirSync, mkdirSync, writeFileSync, existsSync, rmSync } from "node:fs";
|
||||
import { join, dirname } from "node:path";
|
||||
import { homedir } from "node:os";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
|
@ -68,7 +68,16 @@ import type { TrendStatus } from "./types.js";
|
|||
import { normalizeItem, normalizeItems, itemToInput } from "./item.js";
|
||||
import { triage } from "./score.js";
|
||||
import type { ScoreMode } from "./score.js";
|
||||
import { rankForBrief, renderBrief, briefSummary, defaultBriefDir, surfacedIds } from "./brief.js";
|
||||
import {
|
||||
rankForBrief,
|
||||
renderBrief,
|
||||
briefSummary,
|
||||
defaultBriefDir,
|
||||
surfacedIds,
|
||||
diffSurfaced,
|
||||
parseSurfacedFrontmatter,
|
||||
selectPriorBriefFile,
|
||||
} from "./brief.js";
|
||||
import { launchdPlist, crontabLine, installInstructions, uninstallInstructions, defaultLabel } from "./schedule.js";
|
||||
import type { ScheduleSpec } from "./schedule.js";
|
||||
|
||||
|
|
@ -337,7 +346,26 @@ function main(): void {
|
|||
const day = today(); // one wall-clock read for both the ranking and the filename
|
||||
const store = loadStore(storePath); // hoisted: also needed for the surfacing write below
|
||||
const ranking = rankForBrief(store, pillars, day, { freshDays, firstMoverDays, saturationAt });
|
||||
const md = renderBrief(ranking);
|
||||
// RE-R3e: discover the most recent prior brief in outDir and diff today's surfaced cohort
|
||||
// against its `surfaced:` line. Any fs error (no dir, unreadable file) degrades to the
|
||||
// empty-prior (first-brief) path. The dir read lives HERE (the edge) — brief.ts stays fs-free.
|
||||
const todayIds = surfacedIds(ranking);
|
||||
let priorIds: string[] = [];
|
||||
let priorDate: string | null = null;
|
||||
try {
|
||||
if (existsSync(outDir)) {
|
||||
const priorFile = selectPriorBriefFile(readdirSync(outDir), day);
|
||||
if (priorFile) {
|
||||
priorIds = parseSurfacedFrontmatter(readFileSync(join(outDir, priorFile), "utf8"));
|
||||
priorDate = priorFile.slice(0, 10);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
priorIds = [];
|
||||
priorDate = null;
|
||||
}
|
||||
const diff = diffSurfaced(todayIds, priorIds, priorDate);
|
||||
const md = renderBrief(ranking, diff);
|
||||
const path = join(outDir, `${day}.md`);
|
||||
mkdirSync(outDir, { recursive: true });
|
||||
writeFileSync(path, md, "utf8");
|
||||
|
|
@ -347,12 +375,33 @@ function main(): void {
|
|||
const mark = flags["no-mark"] !== "true";
|
||||
const marked = mark ? markSurfaced(store, surfacedIds(ranking), day).marked : 0;
|
||||
if (mark) saveStore(storePath, store);
|
||||
const summary = briefSummary(ranking); // SAME source the frontmatter carries
|
||||
const summary = briefSummary(ranking, diff); // SAME source the frontmatter carries (RE-R3e: incl. the delta marker)
|
||||
if (asJson) {
|
||||
console.log(JSON.stringify({ path, date: ranking.today, totals: ranking.totals, summary, marked }, null, 2));
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
path,
|
||||
date: ranking.today,
|
||||
totals: ranking.totals,
|
||||
summary,
|
||||
marked,
|
||||
diff: {
|
||||
priorDate: diff.priorDate,
|
||||
added: diff.added.length,
|
||||
carried: diff.carried.length,
|
||||
dropped: diff.dropped.length,
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
console.log(`Wrote brief: ${path} (${ranking.totals.matched} matched, ${ranking.totals.fresh} fresh, ${marked} surfaced)`);
|
||||
const deltaNote = diff.added.length > 0 && diff.priorDate !== null ? `, ${diff.added.length} nye siden sist` : "";
|
||||
console.log(
|
||||
`Wrote brief: ${path} (${ranking.totals.matched} matched, ${ranking.totals.fresh} fresh, ${marked} surfaced)${deltaNote}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue