fix(linkedin): close dogfood friction (S14)
Close all 9 friction points from the S13 newsletter dogfood (operator elected to fix F6-F9 rather than defer): - F1: namespace all subagent_type calls in newsletter.md to linkedin-thought-leadership:<name> (4 sites + canonical note) - F2: document agent invocation form + reload requirement in CLAUDE.md + README.md (reload itself is an operator action) - F3: add edition-config / edition-delingstekst / edition-HANDOVER templates under config/ + wire into Steps 0 and 8 + footer - F4: reconcile draft path to <serie>/NN-utkast.md (series root) - F5: de-hardcode series root (explicit arg / LTL_SERIES_ROOT / default) - F6: config-derive carousel editions (remove Seres CAROUSEL set); correct samle comment - F7: build-html.mjs exits non-zero when zero HTML produced - F8: guard parseDelingstekst (graceful ENOENT) + correct Step 8 wording - F9: relocate agents/README.md -> docs/agents-capability-matrix.md Re-tested: 87/87 plugin tests pass; build-html/build-linkedin behavior re-verified live. Per-item outcomes logged in dogfood-S13-friction.md. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
adfa2085fc
commit
92e0a0b4f5
11 changed files with 339 additions and 54 deletions
|
|
@ -65,6 +65,7 @@ describe("build-linkedin edition-config", () => {
|
|||
freshness: {},
|
||||
coverCredit: "",
|
||||
captions: {},
|
||||
carousel: [],
|
||||
});
|
||||
// editionPost still renders without throwing (uses "—" fallbacks)
|
||||
const html = editionPost("01", meta, body, share, cfg);
|
||||
|
|
|
|||
|
|
@ -1030,16 +1030,20 @@ ${CLIENT_JS}
|
|||
// ---------------------------------------------------------------------------
|
||||
// Main
|
||||
// ---------------------------------------------------------------------------
|
||||
// Returnerer antall HTML-filer skrevet. Eksitkoden settes av CLI-guarden under
|
||||
// (S14/F7): main() kaller aldri process.exit() selv, slik at modulen kan
|
||||
// importeres/testes uten å drepe prosessen.
|
||||
export function main() {
|
||||
const args = process.argv.slice(2);
|
||||
if (!args.length) {
|
||||
console.error("Bruk: node build-html.mjs <fil.md> [flere.md ...]");
|
||||
process.exit(1);
|
||||
return 0;
|
||||
}
|
||||
// Output følger serien (kjøres fra serie-mappa), ikke scriptet i tools/.
|
||||
const outDir = path.join(process.cwd(), "review");
|
||||
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
|
||||
|
||||
let written = 0;
|
||||
for (const arg of args) {
|
||||
const inPath = path.isAbsolute(arg) ? arg : path.join(process.cwd(), arg);
|
||||
if (!fs.existsSync(inPath)) {
|
||||
|
|
@ -1054,11 +1058,20 @@ export function main() {
|
|||
const outPath = path.join(outDir, base + ".html");
|
||||
fs.writeFileSync(outPath, html, "utf8");
|
||||
console.log(`Skrev ${outPath} (${(html.length / 1024).toFixed(1)} KB)`);
|
||||
written++;
|
||||
}
|
||||
|
||||
// S14/F7: en typo'd/manglende input-fil ga tidligere exit 0 uten HTML (stille
|
||||
// footgun). Skrev vi ingenting, er det en feil — rapporter og la CLI-guarden
|
||||
// sette ikke-null exit.
|
||||
if (written === 0) {
|
||||
console.error(`Ingen HTML produsert (0 av ${args.length} input-fil(er) funnet) — sjekk filnavn og sti.`);
|
||||
}
|
||||
return written;
|
||||
}
|
||||
|
||||
// CLI-guard: kjør kun når scriptet startes direkte, ikke ved import
|
||||
// (mønster fra hooks/scripts/state-updater.mjs).
|
||||
// (mønster fra hooks/scripts/state-updater.mjs). Exit non-zero hvis ingen HTML.
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
main();
|
||||
process.exit(main() > 0 ? 0 : 1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ const DELINGSTEKST_FILE = path.join(OUT_ROOT, "edition-delingstekst.md");
|
|||
// ---------------------------------------------------------------------------
|
||||
const CONFIG_FILE = path.join(OUT_ROOT, "edition-config.json");
|
||||
|
||||
const EMPTY_CONFIG = { calendar: {}, freshness: {}, coverCredit: "", captions: {} };
|
||||
const EMPTY_CONFIG = { calendar: {}, freshness: {}, coverCredit: "", captions: {}, carousel: [] };
|
||||
|
||||
// Les edition-config.json fra rootDir (serie-mappas linkedin/). Normaliser alle
|
||||
// felt til kjente former; manglende/ugyldig fil → tomme standarder (graceful).
|
||||
|
|
@ -55,13 +55,13 @@ export function loadEditionConfig(rootDir = OUT_ROOT) {
|
|||
freshness: cfg.freshness && typeof cfg.freshness === "object" ? cfg.freshness : {},
|
||||
coverCredit: typeof cfg.coverCredit === "string" ? cfg.coverCredit : "",
|
||||
captions: cfg.captions && typeof cfg.captions === "object" ? cfg.captions : {},
|
||||
// S14/F6: carousel editions are config-derived, not Seres-hardcoded. A list of
|
||||
// zero-padded NN strings ("03","06"); empty/absent → no carousel block for any
|
||||
// edition. Generalizes away the old `new Set(["03","06"])` Seres assumption.
|
||||
carousel: Array.isArray(cfg.carousel) ? cfg.carousel.map(String) : [],
|
||||
};
|
||||
}
|
||||
|
||||
// CAROUSEL-settet er ikke en del av S2-konfig-scope (kun CALENDAR/FRESHNESS/
|
||||
// CAPTIONS/COVER_CREDIT generaliseres) — beholdes hardkodet inntil videre.
|
||||
const CAROUSEL = new Set(["03", "06"]);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// YAML front matter (flate key: "value"-par mellom --- ... ---)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -177,7 +177,16 @@ function seoTitle(title) {
|
|||
// En seksjon = «## Del N — …» eller «## Samle…». «## SYSTEM …» ignoreres.
|
||||
// ---------------------------------------------------------------------------
|
||||
function parseDelingstekst() {
|
||||
const raw = fs.readFileSync(DELINGSTEKST_FILE, "utf8").replace(/\r\n/g, "\n");
|
||||
// Graceful (S14/F8): missing or unreadable delingstekst → no distribution copy.
|
||||
// Matches loadEditionConfig's fail-soft contract — the article POST.html still
|
||||
// builds; only the share text is absent. Previously this threw ENOENT before any
|
||||
// POST.html was written, killing the whole build incl. article posts.
|
||||
let raw;
|
||||
try {
|
||||
raw = fs.readFileSync(DELINGSTEKST_FILE, "utf8").replace(/\r\n/g, "\n");
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
const lines = raw.split("\n");
|
||||
const out = {};
|
||||
let i = 0;
|
||||
|
|
@ -266,7 +275,7 @@ export function editionPost(nn, meta, body, share, config = EMPTY_CONFIG) {
|
|||
const copyZone = [subtitle, ...blocks].filter(Boolean).join("\n ");
|
||||
const shareField = share ? `${share.share}\n\n${share.hashtags}` : "—";
|
||||
|
||||
const carouselBlock = CAROUSEL.has(nn)
|
||||
const carouselBlock = (config.carousel || []).includes(nn)
|
||||
? `<div class="fld"><h2>6 · Carousel (valgfritt rekkevidde-tillegg)</h2>
|
||||
<div class="val">Egen dokument-post, helst egen dag: last opp <code>linkedin/${nn}/carousel.pdf</code>.
|
||||
Caption = delingstekstens premiss-linje.</div></div>`
|
||||
|
|
@ -308,7 +317,7 @@ export function editionPost(nn, meta, body, share, config = EMPTY_CONFIG) {
|
|||
|
||||
${carouselBlock}
|
||||
|
||||
<div class="marker">⬇︎ ${CAROUSEL.has(nn) ? "7" : "6"} · BRØDTEKST — merk alt herfra, kopier (⌘C), lim i editoren ⬇︎</div>
|
||||
<div class="marker">⬇︎ ${(config.carousel || []).includes(nn) ? "7" : "6"} · BRØDTEKST — merk alt herfra, kopier (⌘C), lim i editoren ⬇︎</div>
|
||||
<div class="copyzone">
|
||||
${copyZone}
|
||||
</div>
|
||||
|
|
@ -361,7 +370,9 @@ export function main(files = process.argv.slice(2)) {
|
|||
console.log(`✓ linkedin/${nn}/POST.html (${meta.title || base})`);
|
||||
}
|
||||
|
||||
// Samle bygges alltid (innhold er uavhengig av utkast-filene)
|
||||
// Samle bygges KUN når delingsteksten deklarerer en «## Samle»-seksjon (S14/F6:
|
||||
// tidligere kommentar sa «alltid», men bygget har alltid vært betinget av
|
||||
// shareMap.samle — innholdet er uavhengig av utkast-filene, men ikke av delingstekst).
|
||||
if (shareMap.samle) {
|
||||
const dir = path.join(OUT_ROOT, "samle");
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue