@gobing-ai/knowledge-kit 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/dist/board/collect/index.js +744 -1107
  3. package/dist/board/daily-voice/index.js +741 -107
  4. package/dist/board/itc/index.js +32 -6
  5. package/dist/index.js +1793 -103
  6. package/package.json +1 -1
  7. package/plugins/ingestions/horizon-ingest/dist/index.js +24 -1
  8. package/plugins/ingestions/horizon-ingest/src/index.ts +34 -0
  9. package/plugins/kk/config.example.yaml +8 -0
  10. package/plugins/kk/decisions/extract/ai-news.yaml +7 -1
  11. package/plugins/kk/plugin.json +1 -1
  12. package/plugins/kk/publish-presets/daily-ai-voice.json +123 -0
  13. package/plugins/kk/schedules/kk-daily-publish3-cn-peak.cron.tmpl +20 -0
  14. package/plugins/kk/schedules/kk-daily-publish3-overseas.cron.tmpl +17 -0
  15. package/plugins/kk/scripts/daily-stages.ts +664 -83
  16. package/plugins/kk/scripts/kk-workflow-stages.ts +11 -1
  17. package/plugins/kk/scripts/topic-boundary.json +18 -83
  18. package/plugins/kk/workflows/kk-collect.yaml +2 -0
  19. package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +82 -8
  20. package/plugins/kk/workflows/kk-daily-ai-voice3.yaml +842 -0
  21. package/plugins/kk/workflows/kk-daily-publish3.yaml +282 -0
  22. package/plugins/publishings/podcast-pub/README.md +65 -0
  23. package/plugins/publishings/podcast-pub/dist/index.js +1709 -274
  24. package/plugins/publishings/podcast-pub/plugin.json +1 -1
  25. package/plugins/publishings/podcast-pub/src/backfill.ts +465 -0
  26. package/plugins/publishings/podcast-pub/src/index.ts +211 -34
  27. package/plugins/publishings/podcast-pub/src/microfeed-client.ts +32 -3
  28. package/plugins/publishings/podcast-pub/src/seo.ts +1081 -0
  29. package/plugins/publishings/podcast-pub/src/show-notes.ts +71 -6
  30. package/plugins/publishings/surfdash-pub/dist/index.js +66 -5
  31. package/plugins/publishings/surfdash-pub/src/index.ts +94 -10
  32. package/plugins/publishings/wechatmp-pub/dist/index.js +54 -30
  33. package/plugins/publishings/wechatmp-pub/dist/profile-check.js +8 -3
  34. package/plugins/publishings/wechatmp-pub/src/enrich.ts +58 -38
  35. package/plugins/publishings/wechatmp-pub/src/flow.ts +18 -1
  36. package/plugins/publishings/wechatmp-pub/src/profile-check.ts +7 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/knowledge-kit",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "An ingest → create → publish content pipeline CLI (Bun).",
5
5
  "repository": {
6
6
  "type": "git",
@@ -12328,8 +12328,10 @@ var InputConfigSchema = object({
12328
12328
  config: string2().trim().min(1).optional(),
12329
12329
  hours: number2().int().min(1).optional(),
12330
12330
  languages: array(string2().trim().min(1)).optional(),
12331
- horizon_dir: string2().trim().min(1).optional()
12331
+ horizon_dir: string2().trim().min(1).optional(),
12332
+ stale_fallback_ms: number2().int().min(0).optional()
12332
12333
  });
12334
+ var DEFAULT_STALE_FALLBACK_MS = 26 * 3600000;
12333
12335
  var defaultRunner = async (config, horizonDir) => {
12334
12336
  const argv = [process.env.HORIZON_UV ?? "uv", "run", "horizon"];
12335
12337
  if (config.hours !== undefined)
@@ -12447,6 +12449,27 @@ async function processIngestionIO(options, config, runner = defaultRunner, now =
12447
12449
  continue;
12448
12450
  docs.push(...digestToDocs(await fs.readFile(file), match[1], match[2]));
12449
12451
  }
12452
+ if (docs.length === 0) {
12453
+ const fallbackMs = config.stale_fallback_ms ?? DEFAULT_STALE_FALLBACK_MS;
12454
+ let newest = null;
12455
+ for (const entry of entries) {
12456
+ const match = entry.match(digestRe);
12457
+ if (!match?.[1] || !match?.[2] || !langs.has(match[2]))
12458
+ continue;
12459
+ const stat = await fs.stat(join(summariesDir, entry));
12460
+ if (!stat)
12461
+ continue;
12462
+ if (!newest || stat.mtimeMs > newest.mtimeMs)
12463
+ newest = { entry, mtimeMs: stat.mtimeMs };
12464
+ }
12465
+ if (newest && runStartedAt - newest.mtimeMs <= fallbackMs) {
12466
+ const match = newest.entry.match(digestRe);
12467
+ if (match?.[1] && match?.[2]) {
12468
+ echoError(`horizon-ingest: no fresh digest this run \u2014 falling back to ${newest.entry} (${Math.round((runStartedAt - newest.mtimeMs) / 3600000)}h old)`);
12469
+ docs.push(...digestToDocs(await fs.readFile(join(summariesDir, newest.entry)), match[1], match[2]));
12470
+ }
12471
+ }
12472
+ }
12450
12473
  if (docs.length === 0) {
12451
12474
  echoError(`horizon-ingest: no fresh digest in ${summariesDir} (languages: ${[...langs].join(", ")}) \u2014 emitting []`);
12452
12475
  }
@@ -19,8 +19,18 @@ const InputConfigSchema = z.object({
19
19
  hours: z.number().int().min(1).optional(),
20
20
  languages: z.array(z.string().trim().min(1)).optional(),
21
21
  horizon_dir: z.string().trim().min(1).optional(),
22
+ /**
23
+ * When this tick's run writes no digest (Horizon exits 0 on an empty 6h
24
+ * window), fall back to the newest digest at most this many ms old instead
25
+ * of emitting []. Doc ids are date-keyed and stable, so repeats dedupe
26
+ * downstream (2026-10-23: Oct 21 contributed zero docs — every tick hit an
27
+ * empty window).
28
+ */
29
+ stale_fallback_ms: z.number().int().min(0).optional(),
22
30
  });
23
31
 
32
+ const DEFAULT_STALE_FALLBACK_MS = 26 * 3_600_000;
33
+
24
34
  export interface HorizonRunResult {
25
35
  exitCode: number;
26
36
  stderr: string;
@@ -166,6 +176,30 @@ export async function processIngestionIO(
166
176
  if (!stat || stat.mtimeMs < runStartedAt - 1000) continue; // stale file from an earlier run
167
177
  docs.push(...digestToDocs(await fs.readFile(file), match[1], match[2]));
168
178
  }
179
+ if (docs.length === 0) {
180
+ // This tick wrote no digest (empty fetch window exits 0). Reuse the newest
181
+ // digest while it is young enough to still matter for a daily podcast.
182
+ const fallbackMs = config.stale_fallback_ms ?? DEFAULT_STALE_FALLBACK_MS;
183
+ let newest: { entry: string; mtimeMs: number } | null = null;
184
+ for (const entry of entries) {
185
+ const match = entry.match(digestRe);
186
+ if (!match?.[1] || !match?.[2] || !langs.has(match[2])) continue;
187
+ const stat = await fs.stat(join(summariesDir, entry));
188
+ if (!stat) continue;
189
+ if (!newest || stat.mtimeMs > newest.mtimeMs) newest = { entry, mtimeMs: stat.mtimeMs };
190
+ }
191
+ if (newest && runStartedAt - newest.mtimeMs <= fallbackMs) {
192
+ const match = newest.entry.match(digestRe);
193
+ if (match?.[1] && match?.[2]) {
194
+ echoError(
195
+ `horizon-ingest: no fresh digest this run — falling back to ${newest.entry} (${Math.round(
196
+ (runStartedAt - newest.mtimeMs) / 3_600_000,
197
+ )}h old)`,
198
+ );
199
+ docs.push(...digestToDocs(await fs.readFile(join(summariesDir, newest.entry)), match[1], match[2]));
200
+ }
201
+ }
202
+ }
169
203
  if (docs.length === 0) {
170
204
  echoError(
171
205
  `horizon-ingest: no fresh digest in ${summariesDir} (languages: ${[...langs].join(', ')}) — emitting []`,
@@ -9,6 +9,14 @@ defaults:
9
9
  maxResults: 8
10
10
  fixture: false
11
11
 
12
+ # Per-workflow settings. `publish_preset` names the publish preset that owns the daily channel
13
+ # matrix (files live in `plugins/kk/publish-presets/`, overridable per project under
14
+ # `.kk/publish-presets/`). The preset decides WHICH channels publish; `live` is never in a preset
15
+ # file — it is a launch var (`live_channels`) or an installed schedule entry (ADR-022).
16
+ workflows:
17
+ kk-daily-ai-voice3:
18
+ publish_preset: "daily-ai-voice"
19
+
12
20
  # Plugin credentials — paste values between the quotes.
13
21
  # Path: plugins/<collection>/<name>/env — each entry is injected into that plugin's
14
22
  # process environment at exec time. A real environment variable always wins
@@ -12,7 +12,13 @@
12
12
  # `kk store extract --limit` overrides it.
13
13
  topic: ai-news
14
14
  where:
15
- kk-item-category: { in: [ai-news, ai-industry] }
15
+ # 2026-10-21: the allowlist is the whole AI taxonomy, in lockstep with the workflow's
16
+ # qc_categories (`ai-news,ai-technical,ai-research,ai-industry`) and episode-plan-gen's
17
+ # DEFAULT_FILTER_CONFIG. The two-category gate here front-ran that widening one layer upstream:
18
+ # a live pool labelled 20 ai-industry + 7 ai-research + 7 ai-technical + 3 ai-news came out of
19
+ # extract as 6 items, so `daily-qc-content`'s floor refusal closed the day as no-content before
20
+ # the widened QC allowlist could admit a technical or research story.
21
+ kk-item-category: { in: [ai-news, ai-industry, ai-technical, ai-research] }
16
22
  # quality 2, not 3: real ingest bodies (last30days/aihot summaries) are stub-length, so the
17
23
  # heuristic labeler tops out at 1-2 and a gte-3 gate empties every real pool (task 0212 dogfood
18
24
  # finding). Raise this back when a full-text ingest source lands.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "kk",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "knowledge-kit Claude Code plugin: skills, commands, subagents, hooks, and rules"
5
5
  }
@@ -0,0 +1,123 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "name": "daily-ai-voice",
4
+ "targets": [
5
+ {
6
+ "plugin": "surfdash-pub",
7
+ "operation": "post",
8
+ "variant": "article",
9
+ "tier": 0,
10
+ "window": "after-generate",
11
+ "mode": "draft",
12
+ "required": true
13
+ },
14
+ {
15
+ "plugin": "podcast-pub",
16
+ "operation": "episode",
17
+ "variant": "episode",
18
+ "tier": 0,
19
+ "window": "after-generate",
20
+ "mode": "draft",
21
+ "required": true
22
+ },
23
+ {
24
+ "plugin": "wechatmp-pub",
25
+ "operation": "播客",
26
+ "variant": "episode",
27
+ "tier": 1,
28
+ "window": "after-generate",
29
+ "mode": "draft"
30
+ },
31
+ {
32
+ "plugin": "xhs-pub",
33
+ "operation": "发播客",
34
+ "variant": "episode",
35
+ "tier": 1,
36
+ "window": "after-generate",
37
+ "mode": "draft"
38
+ },
39
+ {
40
+ "plugin": "wechatmp-pub",
41
+ "operation": "文章",
42
+ "variant": "article",
43
+ "tier": 2,
44
+ "window": "after-generate",
45
+ "mode": "draft",
46
+ "options": {
47
+ "collection": "每日AI资讯"
48
+ }
49
+ },
50
+ {
51
+ "plugin": "xhs-pub",
52
+ "operation": "写长文",
53
+ "variant": "article",
54
+ "tier": 2,
55
+ "window": "after-generate",
56
+ "mode": "dummy"
57
+ },
58
+ {
59
+ "plugin": "xhs-pub",
60
+ "operation": "上传图文",
61
+ "variant": "cards",
62
+ "tier": 3,
63
+ "window": "after-generate",
64
+ "mode": "off"
65
+ },
66
+ {
67
+ "plugin": "weibo-pub",
68
+ "operation": "article",
69
+ "variant": "article",
70
+ "tier": 2,
71
+ "window": "cn-peak",
72
+ "mode": "draft"
73
+ },
74
+ {
75
+ "plugin": "x-pub",
76
+ "operation": "article",
77
+ "variant": "article",
78
+ "tier": 2,
79
+ "window": "cn-peak",
80
+ "mode": "draft"
81
+ },
82
+ {
83
+ "plugin": "x-pub",
84
+ "operation": "post",
85
+ "variant": "social:x",
86
+ "tier": 3,
87
+ "window": "overseas-rss",
88
+ "mode": "draft"
89
+ },
90
+ {
91
+ "plugin": "weibo-pub",
92
+ "operation": "post",
93
+ "variant": "social:weibo",
94
+ "tier": 3,
95
+ "window": "overseas-rss",
96
+ "mode": "draft"
97
+ },
98
+ {
99
+ "plugin": "threads-pub",
100
+ "operation": "post",
101
+ "variant": "social:threads",
102
+ "tier": 3,
103
+ "window": "overseas-rss",
104
+ "mode": "draft"
105
+ },
106
+ {
107
+ "plugin": "shipinhao-pub",
108
+ "operation": "图文",
109
+ "variant": "cards",
110
+ "tier": 3,
111
+ "window": "overseas-rss",
112
+ "mode": "off"
113
+ },
114
+ {
115
+ "plugin": "threads-pub",
116
+ "operation": "post-images",
117
+ "variant": "cards",
118
+ "tier": 3,
119
+ "window": "overseas-rss",
120
+ "mode": "off"
121
+ }
122
+ ]
123
+ }
@@ -0,0 +1,20 @@
1
+ # kk-daily-publish3-cn-peak — domestic live window (task 0323 R5).
2
+ #
3
+ # Fires at 17:00 local because the audience window is 09:00 Asia/Shanghai and launchd/cron
4
+ # intervals follow the MACHINE's zone; the first node (`publish-matrix-wait-until 09:00
5
+ # Asia/Shanghai`) sleeps the remainder, which is 1 h from 17:00 PDT and 0 h after the DST flip —
6
+ # so the entry never needs editing twice a year.
7
+ #
8
+ # `live_channels` is baked in: this installed line IS the operator's ADR-022 consent for a live
9
+ # send. Nothing in the workflow or the preset can produce `live` on its own.
10
+ #
11
+ # Rendered by `kk schedule render kk-daily-publish3-cn-peak --platform cron --repo <checkout>`;
12
+ # kk never edits the operator's crontab. MERGE, never replace:
13
+ #
14
+ # crontab -l > /tmp/kk-cn-peak.cron 2>/dev/null || :
15
+ # kk schedule render kk-daily-publish3-cn-peak --platform cron --repo "$PWD" >> /tmp/kk-cn-peak.cron
16
+ # crontab /tmp/kk-cn-peak.cron
17
+ #
18
+ # `run_date` is computed at tick time in America/Los_Angeles: a run that starts at 17:00 PDT is
19
+ # still "today" for the day dir the 04:30 generation wrote.
20
+ 0 17 * * * cd {{repo}} && mkdir -p .works/kk-daily-publish3 && PATH="{{home}}/.bun/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" TZ=America/Los_Angeles spur workflow run plugins/kk/workflows/kk-daily-publish3.yaml --vars "{\"work_dir\":\"{{repo}}/.works/kk-daily-publish3/$(TZ=America/Los_Angeles date +%Y%m%d)\",\"run_date\":\"$(TZ=America/Los_Angeles date +%F)\",\"window\":\"cn-peak\",\"live_channels\":\"wechatmp-pub,xhs-pub,weibo-pub,x-pub\"}" >> {{repo}}/.works/kk-daily-publish3/cn-peak.log 2>&1
@@ -0,0 +1,17 @@
1
+ # kk-daily-publish3-overseas — overseas window behind the podcast RSS gate (task 0323 R5).
2
+ #
3
+ # 07:00 America/Los_Angeles, which needs no DST adjustment: the entry is defined in the zone the
4
+ # machine already runs in. The gated date is the PREVIOUS day, because the observed episodes land
5
+ # 13:43–15:22 PDT (after this tick) — `run_date` is therefore yesterday's date, not today's.
6
+ #
7
+ # The gate decides whether anything publishes: a miss cancels the overseas targets and runs
8
+ # `diagnose` + the tier-0-only repair. This line carries no `live_channels`; the overseas channels
9
+ # in the shipped preset are `draft`, so a live overseas post stays an operator launch.
10
+ #
11
+ # Rendered by `kk schedule render kk-daily-publish3-overseas --platform cron --repo <checkout>`;
12
+ # kk never edits the operator's crontab. MERGE, never replace:
13
+ #
14
+ # crontab -l > /tmp/kk-overseas.cron 2>/dev/null || :
15
+ # kk schedule render kk-daily-publish3-overseas --platform cron --repo "$PWD" >> /tmp/kk-overseas.cron
16
+ # crontab /tmp/kk-overseas.cron
17
+ 0 7 * * * cd {{repo}} && mkdir -p .works/kk-daily-publish3 && PATH="{{home}}/.bun/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" TZ=America/Los_Angeles spur workflow run plugins/kk/workflows/kk-daily-publish3.yaml --vars "{\"work_dir\":\"{{repo}}/.works/kk-daily-publish3/$(TZ=America/Los_Angeles date -v-1d +%Y%m%d 2>/dev/null || TZ=America/Los_Angeles date -d yesterday +%Y%m%d)\",\"run_date\":\"$(TZ=America/Los_Angeles date -v-1d +%F 2>/dev/null || TZ=America/Los_Angeles date -d yesterday +%F)\",\"window\":\"overseas-rss\"}" >> {{repo}}/.works/kk-daily-publish3/overseas.log 2>&1