feat(linkedin-studio): N7.5 — MR-F9 etterspørsels-sveip (Tier-5-kilder + smertepunkt + vokabular-oversettelse + arc-kontrakt) [skip-docs]
The «innenfra og ut» demand-sweep: the mechanism that FILLS the N6 reader fields (readerQuestion/painPoint/saturation). Discovery finds "what happened"; this layer translates it into "the problem the reader is stuck on". - demand-spotter agent (agents 19->20, inherits session): three passes after discovery, before drafting — demand-sweep -> pain-point map -> vocabulary translation. - Tier 5 demand sources in config/trends-sources.template.md (inverse of Tier 1-4; honest blind spots: YouTube API, Reddit approximate, HN/GitHub ground truth). - demand signal on TrendRecord (strength + answered) — rankable twin of the verbatim saturation text; additive-optional, store schema stays v4 (no migration). - arc.ts (§4 output contract): groupIntoArcs (relatedIds transitive closure), rankArcQuestions (etterspørsel x kan-svare x ikke-besvart), classifyMarketGap (supply-gap != demand-gap != saturated). Pure + deterministic (TDD). - arcs CLI verb + /linkedin:trends --demand mode (commands stay 30); morning brief shows the per-candidate demand signal. Suites: trends 276->300, test-runner 139->140, tsc clean. Others unchanged (brain 134, hooks 140, tests 35, render 60). MR-F9 built (bygget-men-ubevist) — the (a)/(b)/(c) evidence gate is a runtime demonstration, proven consumer-side (plugin agents don't resolve in the dev repo). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014bE7VbkmR3cqHFEeGfzgwb
This commit is contained in:
parent
0b34218a38
commit
9de38c4939
16 changed files with 968 additions and 8 deletions
|
|
@ -13,6 +13,7 @@
|
|||
* echo '<raw item|batch>' | node --import tsx src/cli.ts capture [--store <path>] [--json]
|
||||
* node --import tsx src/cli.ts brief [--pillars <a,b>] [--fresh-days N] [--first-mover-days N] [--saturation-at N]
|
||||
* [--out <dir>] [--no-mark] [--store <path>] [--json]
|
||||
* node --import tsx src/cli.ts arcs [--topics <a,b>] [--out <dir>] [--store <path>] [--json]
|
||||
* node --import tsx src/cli.ts schedule --pillars <a,b> [--at HH:MM] [--fresh-days N]
|
||||
* [--platform auto|launchd|cron] [--install|--uninstall] [--store <path>]
|
||||
*
|
||||
|
|
@ -83,6 +84,7 @@ import {
|
|||
} from "./brief.js";
|
||||
import { launchdPlist, crontabLine, installInstructions, uninstallInstructions, defaultLabel } from "./schedule.js";
|
||||
import type { ScheduleSpec } from "./schedule.js";
|
||||
import { groupIntoArcs, renderArcs } from "./arc.js";
|
||||
|
||||
function parseFlags(args: string[]): Record<string, string> {
|
||||
const out: Record<string, string> = {};
|
||||
|
|
@ -123,6 +125,7 @@ function usage(msg: string): never {
|
|||
" score [--mode kortform|long-form] [--threshold N] < scored-candidates.json\n" +
|
||||
" capture [--store <path>] [--json] < raw-item-or-batch.json\n" +
|
||||
" brief [--pillars <a,b>] [--fresh-days N] [--first-mover-days N] [--saturation-at N] [--out <dir>] [--no-mark] [--store <path>] [--json]\n" +
|
||||
" arcs [--topics <a,b>] [--out <dir>] [--store <path>] [--json]\n" +
|
||||
" schedule --pillars <a,b> [--at HH:MM] [--fresh-days N] [--platform auto|launchd|cron] [--install|--uninstall] [--store <path>]",
|
||||
);
|
||||
process.exit(2);
|
||||
|
|
@ -418,6 +421,44 @@ function main(): void {
|
|||
return;
|
||||
}
|
||||
|
||||
if (command === "arcs") {
|
||||
// The demand-sweep's §4 output (MR-F9, N7.5): group the demand-swept records into veins (arcs)
|
||||
// and render the reader-side output contract — question inventory ranked on etterspørsel × kan-svare
|
||||
// × ikke-besvart, per-vein market verdict (supply-gap ≠ demand-gap ≠ saturated), BÆRENDE/STØTTE order.
|
||||
// Reads only records that carry a demand-side signal (the sweep populated them); an optional --topics
|
||||
// narrows the pool to a theme. Print to stdout by default; --out <dir> writes a dated arc map; --json
|
||||
// emits the arc structures. No store mutation (a read-only view, like brief without the surfacing write).
|
||||
const store = loadStore(storePath);
|
||||
const wanted = splitTopics(flags.topics).map((t) => t.toLowerCase());
|
||||
const swept = store.trends.filter((t) => {
|
||||
const hasSignal =
|
||||
t.demand !== undefined ||
|
||||
t.readerQuestion !== undefined ||
|
||||
t.painPoint !== undefined ||
|
||||
t.saturation !== undefined;
|
||||
if (!hasSignal) return false;
|
||||
if (wanted.length === 0) return true;
|
||||
const have = new Set(t.topics.map((x) => x.toLowerCase()));
|
||||
return wanted.some((w) => have.has(w));
|
||||
});
|
||||
const arcs = groupIntoArcs(swept);
|
||||
if (asJson) {
|
||||
console.log(JSON.stringify(arcs, null, 2));
|
||||
return;
|
||||
}
|
||||
const md = renderArcs(arcs);
|
||||
if (flags.out && flags.out !== "true") {
|
||||
const outDir = flags.out;
|
||||
mkdirSync(outDir, { recursive: true });
|
||||
const path = join(outDir, `${today()}-arcs.md`);
|
||||
writeFileSync(path, md, "utf8");
|
||||
console.log(`Wrote arc map: ${path} (${arcs.length} åre(r), ${swept.length} swept record(s))`);
|
||||
return;
|
||||
}
|
||||
process.stdout.write(md);
|
||||
return;
|
||||
}
|
||||
|
||||
if (command === "schedule") {
|
||||
// RE-R3c — print-first autonomous trigger. Emits (or installs) a launchd plist / cron line firing
|
||||
// the DETERMINISTIC brief daily via run-daily.sh; never runs launchctl or the cron table (C2).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue