feat(linkedin-studio): SB-S3e — retire dead content-history + read-side brain reconcile [skip-docs]
The LAST second-brain slice; the S0–S3e arc is now complete.
(b) Retire the dead, zero-reader content-history.md across its 8 plumbing
surfaces: the flaky Stop-hook writer prose, the template (git-rm'd), the
migrate-data.mjs B1 MOVE entry + its test assertions, .gitignore, the gate
SC2_CLASSES guard, the data-path ref-doc, and a session-start comment. SC1
grep = 0; migrate suite 5/5 (R8 idempotency demonstrated).
(c) `brain reconcile` — read-side triple-post reconciliation joining silo 1
(## Recent Posts, auto-tracked creation) to the silo 2↔3 graph, surfacing the
coverage gap: posts created via the plugin but never `brain ingest`-ed. New
pure core scripts/brain/src/reconcile.ts (parseRecentPosts tracks the WRITER
format state-updater.mjs:116, NOT the date-only pruner; reconcileRecentPosts
matches hook→record.body→graph for the in-graph/in-brain-only/orphaned tiers;
loadRecentPosts reads STATE_FILE via the canonical getStateFile() chain — a new
cross-seam read, the state file lives outside the brain dataRoot). Wired as the
`brain reconcile` CLI subcommand (inline parsePublishedRecord loader, not the
body-less listPublished). Read-only: never writes the state silo.
Tests (verified live): gate 99/0/0 (ASSERT floor 82→84; new Section 16f
self-test + CLI grep) · brain 127/127 (floor 114→127, +13 reconcile) · hook
suite 136/136. SC4/SC6 end-to-end run is a real behavioural pass (STATE_FILE
seam read + fallback). Honest limit: read-side cannot reconstruct un-captured
specifics/trends — auto-capture is the flagged follow-up.
Brief/plan: docs/second-brain/{brief,plan}-sb-s3e.md (fbad29d). Go-before-code
gate cleared (operator: retire · read-side · build-now).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RigJBiRFNtFZKCz21qNbQ4
This commit is contained in:
parent
fbad29d3d4
commit
68f6283d8a
12 changed files with 487 additions and 54 deletions
|
|
@ -26,6 +26,7 @@ import {
|
|||
type ProfileDiff,
|
||||
} from "./consolidate.js";
|
||||
import { assemblePostGraph, loadAnalyticsRows } from "./assemble.js";
|
||||
import { loadRecentPosts, reconcileRecentPosts, summarizeReconcile } from "./reconcile.js";
|
||||
import { dataRoot } from "./dataRoot.js";
|
||||
import { ingestText, listPublished, parsePublishedRecord, scanInbox } from "./ingest.js";
|
||||
import { parseProfile, serializeProfile } from "./profile.js";
|
||||
|
|
@ -91,7 +92,8 @@ function usage(msg: string): never {
|
|||
" ingest --file <path> [--source <s>] [--date <YYYY-MM-DD>] [--specific <id>]… [--trend <id>]…\n" +
|
||||
" ingest --scan-inbox [--source <s>]\n" +
|
||||
" published list [--json]\n" +
|
||||
" assemble",
|
||||
" assemble\n" +
|
||||
" reconcile",
|
||||
);
|
||||
process.exit(2);
|
||||
}
|
||||
|
|
@ -200,6 +202,52 @@ function runAssemble(_flags: Record<string, string>): void {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SB-S3e: read-only triple-post reconciliation — silo 1 (`## Recent Posts`,
|
||||
* auto-tracked creation) vs the silo 2↔3 graph. Surfaces the coverage gap
|
||||
* (created posts never `brain ingest`-ed). Writes NOTHING. Loads real
|
||||
* `PublishedRecord[]` (with `body`) via the same inline parse as `runAssemble`
|
||||
* — NOT `listPublished` (body-less). Calls the core by its literal name.
|
||||
*/
|
||||
function runReconcile(_flags: Record<string, string>): void {
|
||||
const pubDir = dataRoot(join("ingest", "published"));
|
||||
const records = existsSync(pubDir)
|
||||
? readdirSync(pubDir)
|
||||
.filter((f) => f.endsWith(".md") && !f.startsWith("."))
|
||||
.map((f) => {
|
||||
try {
|
||||
return parsePublishedRecord(readFileSync(join(pubDir, f), "utf8"));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter((r): r is NonNullable<typeof r> => r !== null)
|
||||
: [];
|
||||
const analytics = loadAnalyticsRows();
|
||||
const graph = assemblePostGraph({ records, analytics });
|
||||
const recentPosts = loadRecentPosts();
|
||||
const nodes = reconcileRecentPosts({ recentPosts, records, graph });
|
||||
const sum = summarizeReconcile(nodes);
|
||||
if (nodes.length === 0) {
|
||||
console.log(
|
||||
"No tracked posts in `## Recent Posts` to reconcile (check that posts were created via the plugin, or that STATE_FILE points at your state file).",
|
||||
);
|
||||
return;
|
||||
}
|
||||
console.log(
|
||||
`Reconcile — ${sum.created} created · ${sum.inGraph} in-graph · ${sum.inBrainOnly} in-brain-only · ${sum.orphaned} orphaned:`,
|
||||
);
|
||||
for (const n of nodes) {
|
||||
const id = n.contentId ? ` · ${n.contentId}` : "";
|
||||
console.log(`\n· ${n.recentPost.date} · ${n.tier}${id}\n "${n.recentPost.hook}"`);
|
||||
}
|
||||
if (sum.orphaned > 0) {
|
||||
console.log(
|
||||
`\n${sum.orphaned} created post(s) are not in the brain graph — \`brain ingest --file <p> [--specific <id>] [--trend <id>]\` to feed them.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function renderDiffMd(diff: ProfileDiff): string {
|
||||
const lines = ["# Pending profile diff", "", "> Operator-gated. Review, then `brain consolidate --apply --diff brain/pending-diff.json --confirm`.", ""];
|
||||
const section = (title: string, items: string[]) => {
|
||||
|
|
@ -310,6 +358,7 @@ function main(): void {
|
|||
if (command === "published") return runPublished(rest, flags);
|
||||
if (command === "consolidate") return runConsolidate(flags);
|
||||
if (command === "assemble") return runAssemble(flags);
|
||||
if (command === "reconcile") return runReconcile(flags);
|
||||
|
||||
usage(command ? `unknown command: ${command}` : "no command given");
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue