用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/QianJinGuo/wiki --skill wiki-cron-pipeline-reconciliation命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
精选高价值 RSS feeds 扫描,输出到 raw/rss-inbox/ 暂存区。只保留有独立知识深度的 feed(非 digest 类),不再自动入库。包含 rss-inbox-curl-recovery.py 绕过 blogwatcher read-state 漏抓的兜底。
Meta-orchestrator that wires web-content-reviewer, llm-wiki, and wiki-evolver into a single four-phase knowledge pipeline (Triage → Gate → Store → Evolve). Use as the single entry point for all knowledge base operations. Includes a 6-URL-validated user-pasted WeChat URL fast path, three-axis dedup decision matrix (NEW/MERGE/DEDUP), orphan-raw detection protocol, and sibling-subagent race V7 evidence, and a two-variant V6 mid-write fix for matching vs different-slug duplicates.
从 Gmail 中提取 TLDR AI 等 newsletter 的链接,写入 raw/email-inbox/candidates.md,供后续 inbox-screener 评分。只做链接不做评分,与 inbox-screener 配合使用。
| name | wiki-cron-pipeline-reconciliation |
| description | Audit cron batch-script runs for silently dropped inputs. |
| version | 1.0.0 |
| author | Hermes Agent |
| license | MIT |
| metadata | {"hermes":{"tags":["wiki","cron","pipeline","verification","data-loss","reconciliation"],"category":"wiki","related_skills":["life-screener","wiki-pipeline","wiki/inbox-screener"]}} |
Batch scoring/ingest cron scripts (score_life_inbox.py, inbox-screener variants) print a summary line like 0 入库 | 6 reject | 9 skip | 0 dup | 0 error plus per-URL log lines. When the log line count < input count, or the summary doesn't obviously match the input list, do not trust the summary — reconcile before reporting. The user demands evidence chains, not speculative conclusions; this skill is the evidence chain for "nothing was lost".
[SCORE] / [SKIP] / [FETCH-FAIL] / [QUICK-SKIP])No log line ≠ dropped. Batch scripts have silent branches that consume inputs without logging. The reconciliation question is: which silent branch consumed it, and is that branch legitimate? Only an input that matches NO branch AND is missing from the input file's remaining content is actually lost.
[QUICK-SKIP] / [SCORE] / [FETCH-FAIL] lines from stdout.importlib), never by re-implementing its logic (you'll drift from its constants):
import sys, importlib.util, urllib.parse, re
spec = importlib.util.spec_from_file_location('sl', '/path/to/score_script.py')
sl = importlib.util.module_from_spec(spec); spec.loader.exec_module(sl)
bl = sl.build_blacklist()
for u in silent_urls:
real = sl.decode_convertkit(u) if hasattr(sl, 'decode_convertkit') else u
nu = sl.norm_url(real) if hasattr(sl, 'norm_url') else real
domain = urllib.parse.urlparse(real).netloc.lower()
reason = []
if nu in bl: reason.append('BLACKLIST')
if any(domain.endswith(b) for b in getattr(sl, 'BLOCKLIST_DOMAINS', [])): reason.append('BLOCKLIST_DOMAIN')
if re.search(r'utm_campaign|/lp/|/trial', real) and domain not in getattr(sl, 'HIVALUE_DOMAINS', []): reason.append('UTM/LP')
if any(re.search(p, real) for p (sl, , [])): reason.append()
(.join(reason) reason + u)
reject = scored-then-failed (stars≤2 / v×c<threshold / domain relevance fail) — always logged as [SCORE]skip = [FETCH-FAIL] + keyword-count-filtered inputs — [QUICK-SKIP] lines are logged but NOT counteddup = blacklist hits — silent, not logged, not counted0 入库 | 6 reject | 9 skip | 0 dup | 0 error ✓The RSS inbox pipeline runs SEVERAL writer scripts in sequence (recovery → watchdog → curl-recovery → problem-feeds), each reporting its own "N written / M skipped". Their sums can exceed the physical file count — the writers dedup against each other at write time. Reconciliation question: is the final count a data loss, or a cross-writer collision?
Ground truth hierarchy (in order of trust):
ls raw/rss-inbox/*.md | wc -l + programmatic per-feed/per-domain breakdown (read feed_name: from frontmatter, never infer from filename).url: frontmatter occurrences; any dup-pair count > 0 means two files share a URL (usually slug collisions from title dedup suffixes).Known off-by-N variants:
final = recovery + problem_feeds + curl − case-study_rm − sub-1KB_rm. When formula ≠ physical, trust physical; explain the delta by the curl-recovery "already in inbox" count before suspecting loss.Anticipation pattern (predicting the next run's baseline): when a DRAIN cron (wiki-inbox-scan-v2 / wechat-inbox-pipeline) ran between two scans, check its log.md entry for ingest/MERGE counts BEFORE predicting the next rebuild signature. Drain promotions of limbo articles to raw/articles drop the next recovery count by exactly the promoted set (verified 2026-08-15 twice: 04:50 drain promoted 2 → baseline 0→209 → 0→207, recovery 213→211, skipped 102→104; 09:39 run reproduced the −1 form: 07:15's Interconnects cascade article GLM-5.3 ingested at 07:37 → recovery 212→211, baseline 0→208 → 0→207, all other per-feed splits byte-identical). A recovery-count DECREASE = ingestion, not rotation or feed-health — verify by grepping raw/articles for the promoted filenames (presence confirms ingestion).
⚠️ Slug-normalization grep trap (hit 2026-08-15 09:39): when verifying ingestion by filename, the on-disk slug may differ from the article's published name because slugify normalizes special chars — glm-5.3 (dot) becomes glm-53 (hyphen) on disk. A glob like *glm-5.3* returns 0 matches → false "absent" signal that could misread an ingestion shift as rotation. Rule: grep a loose token (*glm*, *stride*, the distinctive word fragment) instead of the exact dotted title; then confirm via frontmatter (source_url + ingested date). This extends the existing shorthand-name trap to dot/hyphen normalization — exact-title globs are NEVER safe for verification.
⚠️ Organic cascade + ingestion reverse-shift co-occurrence — flat per-feed count ≠ unchanged (2026-08-20 08:22): when a drain ingestion (−1) and a scan organic cascade (+1) hit the SAME feed in the SAME cycle, the per-feed recovery count holds FLAT because the two cancel — superficially "nothing changed," actually two offsetting events. Observed: AWS-ML recovery held at 18 (ingested 1 → 17, New=1 organic → 18) while net inbox dropped −1 (0→171 → 0→170) from the ingested async-agentcore article. Disambiguate by (1) scan New>0 (organic tell), (2) watchdog 100%-hit (cascade corroborated), (3) grep raw/articles for the ingested filename, (4) git log drain commit naming the article, (5) attribute net −N to ingestion, not feed-health. A flat count is "unchanged" ONLY when scan New=0 AND no drain ran between cycles. Full evidence chain: references/2026-08-20-organic-cascade-plus-ingestion-cancel.md.
When the LLM API degrades (JSON parse failure, HTTP error, 402), scripts fall back to a keyword-count heuristic. Detect it in [SCORE] lines by the exact value×confidence products:
If a [WARN] JSON 解析失败 / [WARN] DeepSeek ... line precedes one of these, the batch may be heuristic-scored — treat scores as low-confidence and check for generic-landing-page false positives (sponsor/membership pages score high on vague productivity keywords).
✗ WeChat-小米技术: RSS fetch failed — The read operation timed out) while every other feed writes normally — the summary shows that feed at 0 written, N skipped (小米 18 articles vanished from the cycle). This is the same partial-output class as the fetch-problem-feeds.py end-of-run hang, but for the MAIN writer and for a MIDDLE feed (not the last). Recovery pattern: (1) verify the feed is ALIVE independently — proxy curl to its feed URL returning HTTP=200 + full size (小米: HTTP=200 size=2168034B) proves transient, not feed death; (2) re-run recovery.py BEFORE the filter chain — the "never re-run recovery.py" rule (rss-to-wiki-pipeline pitfall) applies ONLY AFTER filters run (re-running after filters re-pollutes removed files); a pre-filter re-run is the SAFE backfill and writes exactly the missing set (observed: run2 wrote 小米 18 + 量子位 2 that arrived in the interval, all other feeds 0 written, N skipped); (3) verify by per-feed physical count + mtime, not by re-reading the summary. Caveat: the backfill can surface organic content that arrived between the two runs (量子位 +2) — those get absorbed by the downstream case-study filter if they're metric-number news pieces, which is why physical per-feed count can hold flat despite more "written" this cycle (contributes to the off-by-4 variant above).ls -lt timestamps show what completed; (2) re-run the script in the BACKGROUND (terminal background=true) — its inbox-dedup skips already-written URLs, so it completes only the missing set (observed: first run wrote 4/5 HF limbo files, background re-run added exactly the 5th, voiceeq); (3) verify by filename for the known missing member, not by re-reading the summary. Do NOT re-run the whole pipeline chain — only the timed-out writer.references/2026-08-14-life-screener-reconciliation.md — worked example: 40 newsletter candidates, 27 log lines, 13 silent URLs classified (5 pre-fetch filters + 8 keyword-filtered), summary fully reconciled, 0 data loss.references/2026-08-27-life-screener-38-candidates-0-ingest.md — worked example: 38 candidates → 0 ingest, 12 silent removals, ambiguous NO-BRANCH URLs (ianbarber/vocab-break, ipurple/text-template) Jina-fetched to confirm they're AI/ML/security (correctly non-life); adds the ambiguous-URL Jina-fetch confirmation loop to the method.references/2026-08-15-rss-inbox-multi-writer-reconciliation.md — worked example: multi-writer RSS inbox pipeline, off-by-2 between writer-sum and physical count = cross-writer URL collisions (NOT data loss), evidence chain (unique-URL count + per-feed presence + drain-log ingestion check).references/2026-08-15-0715-rss-inbox-offby3-and-partial-write.md — worked example: off-by-3 (collisions + ACTIVE watchdog dup) and the writer-timeout partial-write recovery pattern (fetch-problem-feeds.py 300s timeout with 4/5 files written → background re-run completes the set).references/2026-08-15-rss-inbox-recovery-timeout-backfill.md — worked example: recovery.py per-feed fetch timeout (小米 18 dropped) → feed-alive verification → PRE-FILTER backfill re-run → off-by-4 reconciliation; includes the 腾讯 scan New=1 count-held rotation verification via blogwatcher DB unread list.references/2026-08-18-rss-inbox-post-filter-phantom-and-safe-kill.md — worked example: fetch-problem-feeds SP hang killed at ~490s (HF-written-first safe-kill), plus a post-Step-5-filter HF phantom (voiceeq) that required a SECOND dedup pass to remove (count 167→166). Covers the temp-script-labeling lesson (use hermes-verify- under OS temp dir, not the repo's tracked scripts/ tree).references/2026-08-18-triple-invocation-recovery-rotation.md — worked example: recovery.py killed twice at the 180s default timeout then completed on a 600s third run (idempotent-across-kills, combined output clean); the required source_url corruption-verification snippet; and the rotation off-by-0 reconciliation (organic small entry replacing an outgoing big → zero collision slack, per-feed count held at 10 with a size-class composition shift).references/2026-08-20-organic-cascade-plus-ingestion-cancel.md — worked example: organic cascade (+1) and ingestion reverse-shift (−1) co-occurring on the same feed (AWS-ML recovery held flat at 18, net inbox −1 from async-agentcore ingestion); evidence chain to decompose a flat per-feed count before classifying.??NO-BRANCH → the input was fetched, then consumed by a deeper silent branch (redline filter / keyword-count filter). Still legitimate domain filtering — confirm by checking the summary counters for the extra reject/skip counts. For ambiguous NO-BRANCH inputs whose domain is NOT obviously tech/marketing (a personal blog, an unknown .team/.blog domain that could be life content), Jina-fetch the URL (via proxy — direct r.jina.ai times out on this host) and read the first ~1KB body to judge domain relevance yourself. This both proves the input was actually fetched/processed and confirms the silent keyword-removal was legitimate (closes the loop that nothing in-scope was lost). Worked example: references/2026-08-27-life-screener-38-candidates-0-ingest.md.[FETCH-FAIL] items must be RETAINED for retry (known-good pattern — deleting them silently loses high-value articles); all processed items removed.0 files lacking source_urlexecute_codepython3 -cprocess(kill) is faster and cleaner than a background re-run. Ordering guarantee: fetch-problem-feeds writes HF articles FIRST, then attempts SP — so once the HF files you expect are present in the inbox (verify by ls -lt / filename), killing the still-blowing SP fetch loses NOTHING (SP produces 0 written anyway, Substack CN dead-end). Verified: killed at ~490s, HF set intact (baseten + security-incident re-entry), pipeline unscathed. Distinguish from the 07:15 case (needed a background re-run to complete an incomplete HF set) by checking whether the expected HF files already landed — kill only if they did.voiceeq (a quote-blind phantom already in raw/articles) into the inbox, pushing the count 166→167 mid-reconciliation. dedup_rm=42 (first pass 41) only became 42 after a SECOND dedup pass over the post-fetch state removed the phantom. Rule: whenever fetch-problem-feeds ran (or was killed) AFTER Step 5, run the dedup snippet a second time before trusting the final count — it is idempotent and catches post-filter phantom writes that the first pass missed. This is why the raw reconciliation formula recovery + HF + curl − case-study_rm − sub-1KB_rm can read 168 while physical=166: the −2 cross-writer collisions PLUS a same-URL phantom write both resolve to 1 physical file each.grep -n / sed -n via terminal instead; importlib loading still works fine.