newsflow

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Newsflow

新闻流(Newsflow)

Execution Setup

执行设置

  1. Resolve
    <SKILL_ROOT>
    as the absolute directory containing this loaded
    SKILL.md
    .
  2. Use the current environment's command or shell execution capability to run bundled scripts with
    python3
    .
  3. Use the current environment's file-reading and file-writing capabilities for configuration, run artifacts, and model-produced translation JSON.
  4. Substitute the resolved absolute
    <SKILL_ROOT>
    in every command, quote every path argument, and pass all arguments in the same command invocation. Do not rely on environment variables or shell state surviving between calls.
  5. Do not detect or guess the runtime, load runtime-specific adapters, or assume a global install path or canonical repository path. This skill uses the same workflow in every environment.
  1. <SKILL_ROOT>
    解析为包含已加载的
    SKILL.md
    文件的绝对目录。
  2. 使用当前环境的命令或shell执行能力,通过
    python3
    运行打包的脚本。
  3. 使用当前环境的文件读写能力处理配置文件、运行产物及模型生成的翻译JSON文件。
  4. 在每个命令中替换已解析的绝对路径
    <SKILL_ROOT>
    ,为每个路径参数添加引号,并在同一命令调用中传递所有参数。不要依赖环境变量或shell状态在多次调用间保留。
  5. 无需检测或猜测运行时环境,无需加载特定于运行时的适配器,也无需假设全局安装路径或标准仓库路径。该工具在所有环境中使用相同的工作流。

Workflow

工作流程

  1. Use current working directory as the output directory.
  2. Read command configuration:
    • Default:
      <SKILL_ROOT>/references/commands.json
      .
    • Optional override: user-provided config path via
      --config
      .
  3. Define run-scoped working paths. Do not reuse flat temp files like
    .news_state/tmp_current.json
    ; each run must use its own artifact directory under
    .news_state/runs/<run-dir>/
    :
bash
WORKDIR=<absolute current working directory>
STATE_DIR=<WORKDIR>/.news_state
RUNS_DIR=<STATE_DIR>/runs
RUN_DIR=<RUNS_DIR>/<unique-run-dir>
CURRENT_JSON_PATH=<RUN_DIR>/current.json
INCREMENTAL_JSON_PATH=<RUN_DIR>/incremental.json
TRANSLATED_JSON_PATH=<RUN_DIR>/translated.json
  1. Run pipeline script sequentially:
bash
python3 "<SKILL_ROOT>/scripts/run_news_pipeline.py" --config "<commands.json>" --out-json "<CURRENT_JSON_PATH>"
  1. Wait for step 4 to exit successfully before continuing. Never run
    prepare
    while the pipeline command is still in flight.
  2. Prepare incremental payload:
bash
python3 "<SKILL_ROOT>/scripts/run_incremental_news.py" prepare --current-json "<CURRENT_JSON_PATH>" --state-dir "<STATE_DIR>" --out-json "<INCREMENTAL_JSON_PATH>"
Prepare recovery policy:
  • If
    prepare
    succeeds, continue normally.
  • If
    prepare
    fails, inspect stderr for a bracketed
    PREPARE_*
    error code.
  • For recoverable prepare codes, abandon the current run artifact directory, create a fresh
    <RUN_DIR>
    , rerun the pipeline from step 4, then rerun
    prepare
    once.
  • Do not reuse the failed run's
    current.json
    ,
    incremental.json
    , or
    translated.json
    .
  • Do not retry more than once. If the second
    prepare
    fails, stop and report both the original and retry failures.
  • Do not retry non-recoverable prepare codes; stop and report the error.
  • When an automatic prepare retry happens, mention it in the final response with the first failure reason and the new run directory.
Recoverable prepare codes:
  • PREPARE_STALE_CURRENT_JSON
    : the current payload is older than the latest finalized run.
  • PREPARE_RUN_ID_ALREADY_FINALIZED
    : the run id has already been finalized today.
  • PREPARE_CURRENT_JSON_UNREADABLE
    : the current run artifact is missing or not valid JSON.
Non-recoverable prepare codes:
  • PREPARE_BAD_ARTIFACT_PATH
    : artifact paths are outside the run directory, mixed across run directories, identical, or inconsistent with stored metadata.
  • PREPARE_BAD_CURRENT_JSON
    :
    current.json
    exists but does not match the expected pipeline payload structure.
  • PREPARE_BAD_RUN_METADATA
    : run identity, timezone, or timestamp metadata is missing or invalid.
  • PREPARE_BAD_STATE
    : the daily state file is invalid or cannot be parsed safely.
  • PREPARE_WRITE_FAILED
    :
    incremental.json
    could not be written.
  1. Parse incremental JSON result:
    • run_fresh_items_raw
      : this run's fresh stories after removing yesterday URLs and earlier same-day URLs.
    • items_to_translate
      : stories whose titles still need model translation for display.
    • current_run_errors
      : errors and recovered degradations from this run. When a primary command fails but a retry or fallback succeeds, the pipeline may still emit an
      已恢复:...
      entry here so downstream reports can surface source health issues.
    • daily_errors
      : accumulated errors and recovered degradations for the current day.
    • run_id
      /
      started_at
      /
      finished_at
      : immutable run identity fields. Downstream steps must preserve them exactly.
    • run_output_stem
      : deterministic
      YYYY-MM-DD-HH-mm-ss-<sha256前12位>
      stem derived from
      generated_at + run_id
      ; use it for per-run output names.
    • section_metadata
      : immutable per-run snapshot of source display/classification/translation metadata; downstream steps must use this snapshot rather than reread a mutable config.
    • state_snapshot
      : the latest finalized daily state seen during
      prepare
      .
      finalize
      will reject stale snapshots.
  2. Translate display text into Chinese in-model:
    • Create the initial deterministic plan. The script derives required fields and keeps the existing
      auto
      rule that any title containing a Unicode Han character (including mixed-language text) does not need title translation, while still planning an English quote or Bloomberg summary when required. This is a character check, not language detection: Japanese text containing Kanji also satisfies it.
bash
python3 "<SKILL_ROOT>/scripts/run_incremental_news.py" plan-translations --incremental-json "<INCREMENTAL_JSON_PATH>" --translated-json "<TRANSLATED_JSON_PATH>" --out-json "<RUN_DIR>/translation-plan.json" --phase initial
  • Translate each
    batches[*].items
    in
    translation-plan.json
    . The plan counts only required source text from titles, quotes, and summaries. Up to
    batch_source_char_limit
    (currently 12,000 characters) stays in one batch; larger input is split by source-text capacity. No item is truncated or summarized to fit, and one oversized item remains intact in its own batch.
  • Write each model result as
    <RUN_DIR>/translation-initial-batch-NNN.json
    , using exactly the batch's
    expected_urls
    as its top-level URL keys. Do not add, omit, or substitute a URL.
  • Merge each batch only through the script, which checks the exact URL set and required fields before atomically updating the cumulative map:
bash
python3 "<SKILL_ROOT>/scripts/run_incremental_news.py" merge-translation-batch --plan-json "<RUN_DIR>/translation-plan.json" --batch-id batch-NNN --batch-json "<RUN_DIR>/translation-initial-batch-NNN.json" --translated-json "<TRANSLATED_JSON_PATH>"
  • For every planned item whose
    required_fields
    includes
    title
    , translate
    title
    .
  • For Twitter quote items, translate quote text when present.
  • For Twitter items,
    title
    always means the main tweet
    text
    , and
    quoted_text
    always means
    quotedTweet.text
    . Never swap them.
  • For long Twitter posts, translate the main tweet and quoted tweet in full, preserving paragraph boundaries / numbering when practical. Do not summarize, compress, or rewrite them into a shorter takeaway sentence.
  • For Bloomberg items with
    summary
    , translate
    summary
    too; final Markdown displays the translated summary under the Bloomberg item.
  • Translation must stay in the model, not inside any script.
  • Write a JSON object into
    <TRANSLATED_JSON_PATH>
    :
    • Legacy format (still supported): map URL to translated title string.
    • Extended format (recommended): map URL to object with
      title
      , optional quote fields, and optional
      summary
      /
      summary_zh
      .
  • If
    items_to_translate
    is empty, still write
    {}
    to
    <TRANSLATED_JSON_PATH>
    .
  1. Validate translated map before finalize:
bash
python3 "<SKILL_ROOT>/scripts/run_incremental_news.py" validate-translations --incremental-json "<INCREMENTAL_JSON_PATH>" --translated-json "<TRANSLATED_JSON_PATH>"
Validation workflow:
  • If validate returns
    ok=true
    , continue to finalize.
  • If validate returns
    ok=false
    , generate the one permitted repair plan; it contains only required fields still missing or non-Chinese, split by the same deterministic rules:
bash
python3 "<SKILL_ROOT>/scripts/run_incremental_news.py" plan-translations --incremental-json "<INCREMENTAL_JSON_PATH>" --translated-json "<TRANSLATED_JSON_PATH>" --out-json "<RUN_DIR>/translation-repair-plan.json" --phase repair
  • Translate and merge each repair batch with
    merge-translation-batch
    , using
    translation-repair-batch-NNN.json
    and
    translation-repair-plan.json
    . Do not directly edit the cumulative map or create another repair plan.
  • Run
    validate-translations
    exactly one more time after repair.
  • Validation automatically records hidden run-scoped state in
    translation-validation.json
    . Do not edit it. If initial validation has issues,
    finalize
    rejects the run until one repair plan and a second validation have been completed.
  • Translation diagnostics remain in the hidden validation state; they are not rendered in Markdown or news-reader sidecar
    errors
    .
  • Do not loop indefinitely. Even if the second validate still reports title issues, continue to finalize so news collection is not blocked; report the result as a partial translation outcome, never as full translation success. Diagnostics remain only in hidden run state, not user output errors.
  • validate-translations
    checks structure and required-field coverage only; it does not score translation style/quality.
json
{
  "https://example.com/story": "中文标题",
  "https://x.com/ivanalog_com/status/123?s=20": {
    "title": "中文正文标题",
    "quoted_text_zh": "引用推文中文翻译"
  },
  "https://www.bloomberg.com/news/articles/example": {
    "title": "中文标题",
    "summary_zh": "中文摘要"
  }
}
  1. Finalize outputs:
bash
python3 "<SKILL_ROOT>/scripts/run_incremental_news.py" finalize --incremental-json "<INCREMENTAL_JSON_PATH>" --translated-json "<TRANSLATED_JSON_PATH>" --state-dir "<STATE_DIR>" --out-dir "<WORKDIR>"
Optional export step (post-finalize):
bash
python3 "<SKILL_ROOT>/scripts/export_outputs.py" --daily "<daily_fresh_path>" --fresh "<run_fresh_path>" --target-root "<export_root>"
Export rules:
  • Export copies the daily Markdown, per-run fresh Markdown, and the daily
    .newsreader.json
    sidecar. Per-run freshNews has no sidecar and export must not require or copy one.
  • Export root precedence is:
    --target-root
    CLI argument,
    NEWSFLOW_EXPORT_ROOT
    , then the legacy personal default
    /Users/x/Library/Mobile Documents/iCloud~md~obsidian/Documents/DailyNews
    .
  • When the legacy default is used, the command prints a compatibility warning to stderr; pass an explicit root for portable use.
  • If the root directory does not exist, export fails with explicit error and root path.
  • Month subdirectory is auto-created as
    YYYY年M月
    , parsed from filenames.
  • dailyFreshNews_YYYY-MM-DD.md
    and either legacy
    YYYY-MM-DD-HH-mm_freshNews.md
    or current
    YYYY-MM-DD-HH-mm-ss-<12hex>_freshNews.md
    must resolve to the same year-month.
  • Export overwrites same-name files by default.
  • Export failure never rolls back finalized local outputs.
Finalize recovery policy:
  • If
    finalize
    succeeds, continue normally.
  • If
    finalize
    fails, inspect stderr for a bracketed
    FINALIZE_*
    error code.
  • For
    FINALIZE_STATE_CHANGED_SINCE_PREPARE
    , rerun
    prepare
    once using the same
    current_json_path
    stored in
    incremental.json.paths.current_json_path
    , the same
    STATE_DIR
    , and the same
    INCREMENTAL_JSON_PATH
    .
  • After rerunning
    prepare
    , reuse the existing
    translated.json
    as a base, translate only newly missing
    items_to_translate
    fields, then rerun
    finalize
    once.
  • Do not rerun the pipeline as part of finalize recovery. If the same
    current.json
    is rejected during the new
    prepare
    , stop and report that the current artifact is no longer usable against the latest state.
  • Do not retry
    FINALIZE_OUTPUT_EXISTS
    ,
    FINALIZE_WRITE_FAILED
    , bad artifact paths, bad JSON, bad metadata, bad state, or already-finalized runs.
  • Bloomberg summary translation issues are handled before
    finalize
    by repairing
    translated.json
    ; if still unresolved after one repair,
    finalize
    uses the original summary and keeps diagnostics in hidden run state.
Recoverable finalize codes:
  • FINALIZE_STATE_CHANGED_SINCE_PREPARE
    : daily state changed after
    prepare
    ; rerun
    prepare
    from the same
    current.json
    .
Non-recoverable finalize codes:
  • FINALIZE_BAD_ARTIFACT_PATH
    : artifact paths are outside the run directory, mixed across run directories, or inconsistent with stored metadata.
  • FINALIZE_BAD_INCREMENTAL_JSON
    :
    incremental.json
    is missing, invalid JSON, or not an object.
  • FINALIZE_BAD_TRANSLATED_JSON
    :
    translated.json
    is missing, invalid JSON, or not an object.
  • FINALIZE_BAD_RUN_METADATA
    : run identity, timezone, or timestamp metadata is missing or invalid.
  • FINALIZE_BAD_INCREMENTAL_METADATA
    : date, run timestamp, paths, or state snapshot metadata is invalid.
  • FINALIZE_BAD_STATE
    : the daily state file is invalid or cannot be parsed safely.
  • FINALIZE_RUN_ALREADY_FINALIZED
    : this run id or generated timestamp has already been finalized.
  • FINALIZE_OUTPUT_EXISTS
    : the target per-run
    freshNews.md
    already exists.
  • FINALIZE_WRITE_FAILED
    : an output Markdown or state file could not be written.
  1. Finalize writes exactly two user-facing Markdown files:
  • dailyFreshNews_YYYY-MM-DD.md
    : one rolling summary file per day.
  • YYYY-MM-DD-HH-mm-ss-<sha256前12位>_freshNews.md
    : one collision-resistant per-run fresh-news file.
  • Timezone:
    Asia/Shanghai
    unless user explicitly requests another timezone.
  • Finalize also writes only
    dailyFreshNews_YYYY-MM-DD.newsreader.json
    ; it must not create a per-run
    *_freshNews.newsreader.json
    .
  • Do not delete a legacy per-run sidecar if one already exists. Older state entries may contain
    run_sidecar_path
    ; accept and ignore that field, while new run records must not write it.
  1. Hidden state is stored separately in the state directory, one JSON file per day.
  2. Safety rules:
  • prepare
    and
    finalize
    now require run artifacts to live under
    <STATE_DIR>/runs/<run-dir>/
    .
  • finalize
    never overwrites an existing per-run fresh-news file; legacy incremental artifacts without
    run_output_stem
    are finalized with the newly derived stem.
  • If
    prepare
    sees a
    current.json
    older than the latest finalized run, it fails instead of returning a misleading
    0 条新增
    .
  • Recoverable
    prepare
    failures may trigger one clean retry from a new run directory; non-recoverable failures must remain hard stops.
  • If state changes after
    prepare
    , rerun
    prepare
    from the same
    current.json
    ; do not force
    finalize
    and do not automatically rerun pipeline.
  1. 将当前工作目录作为输出目录。
  2. 读取命令配置:
    • 默认路径:
      <SKILL_ROOT>/references/commands.json
    • 可选覆盖:用户可通过
      --config
      参数提供自定义配置路径
  3. 定义运行范围的工作路径。请勿复用
    .news_state/tmp_current.json
    这类扁平临时文件;每次运行必须在
    .news_state/runs/<run-dir>/
    下使用独立的产物目录:
bash
WORKDIR=<absolute current working directory>
STATE_DIR=<WORKDIR>/.news_state
RUNS_DIR=<STATE_DIR>/runs
RUN_DIR=<RUNS_DIR>/<unique-run-dir>
CURRENT_JSON_PATH=<RUN_DIR>/current.json
INCREMENTAL_JSON_PATH=<RUN_DIR>/incremental.json
TRANSLATED_JSON_PATH=<RUN_DIR>/translated.json
  1. 按顺序运行流水线脚本:
bash
python3 "<SKILL_ROOT>/scripts/run_news_pipeline.py" --config "<commands.json>" --out-json "<CURRENT_JSON_PATH>"
  1. 等待步骤4执行成功后再继续。流水线命令运行期间切勿执行
    prepare
    操作。
  2. 准备增量负载:
bash
python3 "<SKILL_ROOT>/scripts/run_incremental_news.py" prepare --current-json "<CURRENT_JSON_PATH>" --state-dir "<STATE_DIR>" --out-json "<INCREMENTAL_JSON_PATH>"
准备恢复策略:
  • prepare
    执行成功,正常继续后续流程。
  • prepare
    执行失败,检查标准错误输出中带括号的
    PREPARE_*
    错误码。
  • 对于可恢复的错误码,丢弃当前运行的产物目录,创建新的
    <RUN_DIR>
    ,从步骤4重新运行流水线,然后重新执行一次
    prepare
  • 请勿复用失败运行的
    current.json
    incremental.json
    translated.json
    文件。
  • 重试次数不得超过一次。若第二次
    prepare
    仍失败,停止流程并报告首次和重试的失败信息。
  • 对于不可恢复的错误码,无需重试;停止流程并报告错误。
  • 当自动重试
    prepare
    时,需在最终响应中提及首次失败原因和新的运行目录。
可恢复的prepare错误码:
  • PREPARE_STALE_CURRENT_JSON
    :当前负载早于最新的已完成运行版本
  • PREPARE_RUN_ID_ALREADY_FINALIZED
    :该运行ID今日已完成
  • PREPARE_CURRENT_JSON_UNREADABLE
    :当前运行产物缺失或不是有效的JSON文件
不可恢复的prepare错误码:
  • PREPARE_BAD_ARTIFACT_PATH
    :产物路径位于运行目录之外、跨运行目录混合、路径相同或与存储的元数据不一致
  • PREPARE_BAD_CURRENT_JSON
    current.json
    存在但不符合预期的流水线负载结构
  • PREPARE_BAD_RUN_METADATA
    :运行标识、时区或时间戳元数据缺失或无效
  • PREPARE_BAD_STATE
    :每日状态文件无效或无法安全解析
  • PREPARE_WRITE_FAILED
    :无法写入
    incremental.json
    文件
  1. 解析增量JSON结果:
    • run_fresh_items_raw
      :本次运行中移除昨日URL及当日更早URL后的新鲜新闻
    • items_to_translate
      :标题仍需模型翻译以用于展示的新闻
    • current_run_errors
      :本次运行中的错误及已恢复的异常。当主命令失败但重试或备用命令成功时,流水线可能会在此处输出
      已恢复:...
      条目,以便下游报告能显示数据源健康问题
    • daily_errors
      :当日累计的错误及已恢复的异常
    • run_id
      /
      started_at
      /
      finished_at
      :不可变的运行标识字段。下游步骤必须完全保留这些字段
    • run_output_stem
      :由
      generated_at + run_id
      生成的确定性标识,格式为
      YYYY-MM-DD-HH-mm-ss-<sha256前12位>
      ;用于命名单次运行的输出文件
    • section_metadata
      :数据源展示/分类/翻译元数据的运行范围快照;下游步骤必须使用该快照,而非重新读取可变配置
    • state_snapshot
      prepare
      过程中读取的最新已完成每日状态。
      finalize
      会拒绝过时的快照
  2. 通过模型将展示文本翻译成中文:
    • 创建初始确定性翻译计划。脚本会推导所需字段,并保留现有
      auto
      规则:任何包含Unicode汉字(包括混合语言文本)的标题无需翻译,但在需要时仍会计划翻译英文引用或彭博社摘要。这是字符检查,而非语言检测:包含汉字的日文文本也符合该规则。
bash
python3 "<SKILL_ROOT>/scripts/run_incremental_news.py" plan-translations --incremental-json "<INCREMENTAL_JSON_PATH>" --translated-json "<TRANSLATED_JSON_PATH>" --out-json "<RUN_DIR>/translation-plan.json" --phase initial
  • 翻译
    translation-plan.json
    batches[*].items
    的内容。计划仅统计标题、引用和摘要中的必要源文本。不超过
    batch_source_char_limit
    (当前为12000字符)的内容保留在一个批次中;超出容量的输入会按源文本拆分。不会为了适配而截断或摘要任何条目,单个超大条目会完整保留在独立批次中。
  • 将每个模型结果写入
    <RUN_DIR>/translation-initial-batch-NNN.json
    ,并以批次的
    expected_urls
    作为顶级URL键。请勿添加、省略或替换URL。
  • 仅通过脚本合并每个批次,脚本会检查URL集和所需字段,然后原子性更新累积映射:
bash
python3 "<SKILL_ROOT>/scripts/run_incremental_news.py" merge-translation-batch --plan-json "<RUN_DIR>/translation-plan.json" --batch-id batch-NNN --batch-json "<RUN_DIR>/translation-initial-batch-NNN.json" --translated-json "<TRANSLATED_JSON_PATH>"
  • 对于每个
    required_fields
    包含
    title
    的计划条目,翻译
    title
    字段。
  • 对于Twitter引用条目,若存在引用文本则翻译该文本。
  • 对于Twitter条目,
    title
    始终指主推文的
    text
    字段,
    quoted_text
    始终指
    quotedTweet.text
    字段。切勿混淆两者。
  • 对于长Twitter帖子,完整翻译主推文和引用推文,尽可能保留段落分隔/编号。请勿进行摘要、压缩或改写为简短要点。
  • 对于包含
    summary
    的彭博社条目,同时翻译
    summary
    字段;最终Markdown会在该条目下方展示翻译后的摘要。
  • 翻译必须由模型完成,而非在任何脚本内执行。
  • 将JSON对象写入
    <TRANSLATED_JSON_PATH>
    • 兼容格式(仍支持):URL到翻译后标题字符串的映射
    • 扩展格式(推荐):URL到包含
      title
      、可选引用字段及可选
      summary
      /
      summary_zh
      的对象的映射
  • items_to_translate
    为空,仍需向
    <TRANSLATED_JSON_PATH>
    写入
    {}
  1. 在finalize前验证翻译映射:
bash
python3 "<SKILL_ROOT>/scripts/run_incremental_news.py" validate-translations --incremental-json "<INCREMENTAL_JSON_PATH>" --translated-json "<TRANSLATED_JSON_PATH>"
验证工作流:
  • 若验证返回
    ok=true
    ,继续执行finalize。
  • 若验证返回
    ok=false
    ,生成唯一允许的修复计划;该计划仅包含仍缺失或非中文的必要字段,并按相同确定性规则拆分:
bash
python3 "<SKILL_ROOT>/scripts/run_incremental_news.py" plan-translations --incremental-json "<INCREMENTAL_JSON_PATH>" --translated-json "<TRANSLATED_JSON_PATH>" --out-json "<RUN_DIR>/translation-repair-plan.json" --phase repair
  • 使用
    merge-translation-batch
    翻译并合并每个修复批次,使用
    translation-repair-batch-NNN.json
    translation-repair-plan.json
    。请勿直接编辑累积映射或创建其他修复计划。
  • 修复后必须再执行一次
    validate-translations
  • 验证会自动将运行范围的隐藏状态记录在
    translation-validation.json
    中。请勿编辑该文件。若初始验证存在问题,
    finalize
    会拒绝该运行,直到完成一次修复计划和第二次验证。
  • 翻译诊断信息仅保留在隐藏的运行状态中,不会在Markdown或新闻阅读器附属文件
    errors
    中显示。
  • 请勿无限循环。即使第二次验证仍报告标题问题,仍需继续执行finalize以避免新闻收集中断;需将结果报告为部分翻译完成,而非完全翻译成功。诊断信息仅保留在隐藏运行状态中,不会在用户输出错误中显示。
  • validate-translations
    仅检查结构和必要字段覆盖情况;不评估翻译风格/质量。
json
{
  "https://example.com/story": "中文标题",
  "https://x.com/ivanalog_com/status/123?s=20": {
    "title": "中文正文标题",
    "quoted_text_zh": "引用推文中文翻译"
  },
  "https://www.bloomberg.com/news/articles/example": {
    "title": "中文标题",
    "summary_zh": "中文摘要"
  }
}
  1. 完成输出:
bash
python3 "<SKILL_ROOT>/scripts/run_incremental_news.py" finalize --incremental-json "<INCREMENTAL_JSON_PATH>" --translated-json "<TRANSLATED_JSON_PATH>" --state-dir "<STATE_DIR>" --out-dir "<WORKDIR>"
可选导出步骤(finalize之后):
bash
python3 "<SKILL_ROOT>/scripts/export_outputs.py" --daily "<daily_fresh_path>" --fresh "<run_fresh_path>" --target-root "<export_root>"
导出规则:
  • 导出会复制每日Markdown文件、单次运行新鲜新闻Markdown文件及每日
    .newsreader.json
    附属文件。单次运行新鲜新闻无附属文件,导出无需复制或要求该文件。
  • 导出根目录优先级:
    --target-root
    命令行参数 >
    NEWSFLOW_EXPORT_ROOT
    环境变量 > 旧版个人默认路径
    /Users/x/Library/Mobile Documents/iCloud~md~obsidian/Documents/DailyNews
  • 若使用旧版默认路径,命令会向标准错误输出打印兼容性警告;如需可移植性,请传递显式根目录。
  • 若根目录不存在,导出会失败并输出明确错误及根路径。
  • 月份子目录会自动创建为
    YYYY年M月
    ,由文件名解析而来。
  • dailyFreshNews_YYYY-MM-DD.md
    以及旧版
    YYYY-MM-DD-HH-mm_freshNews.md
    或新版
    YYYY-MM-DD-HH-mm-ss-<12hex>_freshNews.md
    必须属于同一年月。
  • 默认情况下,导出会覆盖同名文件。
  • 导出失败不会回滚已完成的本地输出。
Finalize恢复策略:
  • finalize
    执行成功,正常继续后续流程。
  • finalize
    执行失败,检查标准错误输出中带括号的
    FINALIZE_*
    错误码。
  • 对于
    FINALIZE_STATE_CHANGED_SINCE_PREPARE
    ,使用
    incremental.json.paths.current_json_path
    中存储的同一
    current_json_path
    、同一
    STATE_DIR
    和同一
    INCREMENTAL_JSON_PATH
    重新执行一次
    prepare
  • 重新执行
    prepare
    后,复用现有
    translated.json
    作为基础,仅翻译新缺失的
    items_to_translate
    字段,然后重新执行一次
    finalize
  • 请勿将重新运行流水线作为finalize恢复的一部分。若新的
    prepare
    拒绝同一
    current.json
    ,停止流程并报告当前产物无法适配最新状态。
  • 对于
    FINALIZE_OUTPUT_EXISTS
    FINALIZE_WRITE_FAILED
    、无效产物路径、无效JSON、无效元数据、无效状态或已完成的运行,无需重试。
  • 彭博社摘要翻译问题需在
    finalize
    前通过修复
    translated.json
    处理;若一次修复后仍未解决,
    finalize
    会使用原始摘要并将诊断信息保留在隐藏运行状态中。
可恢复的finalize错误码:
  • FINALIZE_STATE_CHANGED_SINCE_PREPARE
    prepare
    后每日状态发生变化;使用同一
    current.json
    重新执行
    prepare
不可恢复的finalize错误码:
  • FINALIZE_BAD_ARTIFACT_PATH
    :产物路径位于运行目录之外、跨运行目录混合或与存储的元数据不一致
  • FINALIZE_BAD_INCREMENTAL_JSON
    incremental.json
    缺失、无效或不是对象
  • FINALIZE_BAD_TRANSLATED_JSON
    translated.json
    缺失、无效或不是对象
  • FINALIZE_BAD_RUN_METADATA
    :运行标识、时区或时间戳元数据缺失或无效
  • FINALIZE_BAD_INCREMENTAL_METADATA
    :日期、运行时间戳、路径或状态快照元数据无效
  • FINALIZE_BAD_STATE
    :每日状态文件无效或无法安全解析
  • FINALIZE_RUN_ALREADY_FINALIZED
    :该运行ID或生成时间戳已完成
  • FINALIZE_OUTPUT_EXISTS
    :目标单次运行
    freshNews.md
    已存在
  • FINALIZE_WRITE_FAILED
    :无法写入输出Markdown或状态文件
  1. Finalize会生成两个面向用户的Markdown文件:
  • dailyFreshNews_YYYY-MM-DD.md
    :每日一份的滚动摘要文件
  • YYYY-MM-DD-HH-mm-ss-<sha256前12位>_freshNews.md
    :具有防冲突特性的单次运行新鲜新闻文件
  • 时区:默认使用
    Asia/Shanghai
    ,除非用户明确要求其他时区
  • Finalize还会生成
    dailyFreshNews_YYYY-MM-DD.newsreader.json
    ;不得生成单次运行的
    *_freshNews.newsreader.json
    文件
  • 若已存在旧版单次运行附属文件,请勿删除。旧状态条目可能包含
    run_sidecar_path
    字段;需接受并忽略该字段,同时新运行记录不得写入该字段
  1. 隐藏状态单独存储在状态目录中,每日一个JSON文件。
  2. 安全规则:
  • prepare
    finalize
    现在要求运行产物必须存储在
    <STATE_DIR>/runs/<run-dir>/
  • finalize
    绝不会覆盖已存在的单次运行新鲜新闻文件;无
    run_output_stem
    的旧版增量产物会使用新生成的标识完成处理
  • prepare
    发现
    current.json
    早于最新的已完成运行版本,会直接失败,而非返回误导性的
    0 条新增
  • 可恢复的
    prepare
    失败可触发一次从新运行目录开始的重试;不可恢复的失败必须直接停止流程
  • prepare
    后状态发生变化,使用同一
    current.json
    重新执行
    prepare
    ;不得强制执行
    finalize
    ,也不得自动重新运行流水线

State Schema Notes

状态架构说明

  • Daily state file path:
    <STATE_DIR>/YYYY-MM-DD.json
    .
  • Top-level keys are daily aggregates and metadata, for example:
    • date
      ,
      timezone
      ,
      section_order
    • today_seen_urls
      ,
      today_first_seen_items
    • daily_errors
    • runs
      (array of per-run summaries)
  • Run artifact directory:
    <STATE_DIR>/runs/<run-dir>/
    .
  • Pipeline payload now includes
    run_id
    ,
    started_at
    ,
    finished_at
    , and may include
    current_json_path
    .
  • Per-run counters are stored under
    runs[-1]
    (latest run), not at top level.
    • Read
      runs[-1].run_fresh_count
      for this run's fresh count.
    • Read
      runs[-1].daily_fresh_count
      for current day cumulative fresh count.
    • Read
      runs[-1].error_count
      for this run error count.
    • Read
      runs[-1].run_fresh_path
      /
      runs[-1].daily_fresh_path
      for output files.
    • New runs also record audit fields such as
      run_id
      ,
      current_json_path
      ,
      incremental_json_path
      ,
      translated_json_path
      ,
      prepared_at
      , and
      finalized_at
      .
  • If
    runs
    is empty, treat run-level stats as unavailable rather than
    0
    .
  • 每日状态文件路径:
    <STATE_DIR>/YYYY-MM-DD.json
  • 顶级键为每日聚合数据和元数据,例如:
    • date
      timezone
      section_order
    • today_seen_urls
      today_first_seen_items
    • daily_errors
    • runs
      (单次运行摘要数组)
  • 运行产物目录:
    <STATE_DIR>/runs/<run-dir>/
  • 流水线负载现在包含
    run_id
    started_at
    finished_at
    ,可能还包含
    current_json_path
  • 单次运行统计数据存储在
    runs[-1]
    (最新运行)下,而非顶级目录
    • 读取
      runs[-1].run_fresh_count
      获取本次运行的新增数量
    • 读取
      runs[-1].daily_fresh_count
      获取当日累计新增数量
    • 读取
      runs[-1].error_count
      获取本次运行的错误数量
    • 读取
      runs[-1].run_fresh_path
      /
      runs[-1].daily_fresh_path
      获取输出文件路径
    • 新运行还会记录审计字段,如
      run_id
      current_json_path
      incremental_json_path
      translated_json_path
      prepared_at
      finalized_at
  • runs
    为空,需将运行级统计视为不可用,而非
    0

commands.json Format

commands.json格式

The bundled v1.2.2 default config has 19 ordered source entries, including 11 Twitter accounts.
Use JSON array of objects:
json
[
  {
    "section": "middle-east",
    "display_name": "Reuters · Middle East",
    "source_type": "reuters",
    "source_name": "Reuters",
    "translation_policy": "always",
    "command": ["opencli", "ReutersBrowser", "news", "https://www.reuters.com/world/middle-east/", "--limit", "10", "--format", "json"]
  }
]
Rules:
  • Keep order as desired final processing order.
  • Add/remove sources by adding/removing objects only.
  • command
    supports string array (recommended) or shell string.
  • display_name
    ,
    source_type
    , and
    source_name
    define the source metadata snapshot consumed by prepare/finalize; Twitter also requires
    source_handle
    .
  • Optional reliability fields are supported per source:
    • retry_once
      : retry the primary command once before recording failure.
    • fallback_command
      : secondary command when primary still fails.
    • treat_empty_as_failure
      : treat zero valid rows as failure for retry/fallback.
    • min_valid_items
      : minimum valid rows required for success when empty-check is enabled.
    • translation_policy
      : translation requirement policy for this source (
      always
      ,
      auto
      ,
      never
      ).
  • Current policy in this skill:
    • News portal sources use
      retry_once
      ; most also use
      treat_empty_as_failure: true
      and
      min_valid_items: 1
      .
    • Twitter sources use
      retry_once
      only; do not force empty-as-failure by default.
    • Reuters/Bloomberg/TechCrunch/Ars should use
      translation_policy: "always"
      .
    • Twitter should use
      translation_policy: "auto"
      .
打包的v1.2.2默认配置包含19个有序数据源条目,其中包括11个Twitter账号。
使用JSON对象数组:
json
[
  {
    "section": "middle-east",
    "display_name": "Reuters · Middle East",
    "source_type": "reuters",
    "source_name": "Reuters",
    "translation_policy": "always",
    "command": ["opencli", "ReutersBrowser", "news", "https://www.reuters.com/world/middle-east/", "--limit", "10", "--format", "json"]
  }
]
规则:
  • 保持所需的最终处理顺序
  • 仅通过添加/移除对象来添加/移除数据源
  • command
    支持字符串数组(推荐)或shell字符串
  • display_name
    source_type
    source_name
    定义
    prepare/finalize
    使用的数据源元数据快照;Twitter还需
    source_handle
    字段
  • 每个数据源支持可选的可靠性字段:
    • retry_once
      :记录失败前重试主命令一次
    • fallback_command
      :主命令仍失败时使用的备用命令
    • treat_empty_as_failure
      :将有效行数为零视为失败,触发重试/备用逻辑
    • min_valid_items
      :启用空检查时,成功所需的最小有效行数
    • translation_policy
      :该数据源的翻译需求策略(
      always
      auto
      never
  • 本工具的当前策略:
    • 新闻门户数据源使用
      retry_once
      ;大多数还使用
      treat_empty_as_failure: true
      min_valid_items: 1
    • Twitter数据源仅使用
      retry_once
      ;默认不强制将空结果视为失败
    • Reuters/Bloomberg/TechCrunch/Ars应使用
      translation_policy: "always"
    • Twitter应使用
      translation_policy: "auto"

Output Contract

输出约定

For each output Markdown file:
  • Emit full
    ## section(N条)
    sections only for non-empty groups after filtering.
  • Use display-friendly section names when available, such as
    Reuters · World
    or
    TechCrunch
    .
  • Add a one-line summary blockquote under each non-empty section header:
    • Format:
      > N条|最新 ...|最早 ...|时间倒序
  • Separate adjacent non-empty sections with
    ---
    .
  • Do not emit standalone
    (0条)
    section headers for empty groups.
  • Instead, append a summary section at the end:
    • ## 本次无更新的分组(X个)
    • List each empty group as a bullet using its display name, or
      - 无
      when there are none.
Example non-empty section:
markdown
undefined
对于每个输出Markdown文件:
  • 仅为过滤后非空的分组生成完整的
    ## section(N条)
    章节
  • 使用友好的展示名称(如
    Reuters · World
    TechCrunch
  • 在每个非空章节标题下添加一行摘要块引用:
    • 格式:
      > N条|最新 ...|最早 ...|时间倒序
  • 相邻非空章节之间用
    ---
    分隔
  • 不为空分组生成单独的
    (0条)
    章节标题
  • 取而代之,在末尾添加摘要章节:
    • ## 本次无更新的分组(X个)
    • 使用展示名称将每个空分组列为项目符号;若无空分组则显示
      - 无
非空章节示例:
markdown
undefined

Reuters · World(3条)

Reuters · World(3条)

3条|最新 2026-04-09 10:00:00|最早 2026-04-09 08:00:00|时间倒序
3条|最新 2026-04-09 10:00:00|最早 2026-04-09 08:00:00|时间倒序
  • 发布时间:YYYY-MM-DD HH:MM:SS

Example empty-group summary:

```markdown
  • 发布时间:YYYY-MM-DD HH:MM:SS

空分组摘要示例:

```markdown

本次无更新的分组(2个)

本次无更新的分组(2个)

  • Bloomberg
  • Reuters · World

Constraints:
- Missing time must be `页面未显示`.
- Bloomberg summaries should be rendered in Chinese when translation is available. The translation map may use `summary` or `summary_zh`; `summary_zh` is preferred for clarity.
- If a non-Chinese Bloomberg summary is present but its translated summary is missing or still non-Chinese after one repair attempt, `finalize` writes the original source summary without adding a user-visible error.
- Preserve first-seen order: command order first, then source order.
- Global dedupe key is absolute URL exact match.
- Daily filtering removes yesterday's URLs.
- Per-run filtering removes yesterday's URLs and URLs seen earlier the same day.
- Twitter (`twitter user-posts --json`) is supported:
  - Every Twitter command in `references/commands.json` must declare `source_type: "twitter"`, `source_handle`, and `source_name`; these fields identify the configured collection account and flow into every normalized item and sidecar entry.
  - Keep collection identity separate from content authorship: `source_handle` / `source_name` identify whose timeline was collected, while `author_screen_name` / `author_name` and the item URL identify the actual content author for originals, reposts, replies, and quotes.
  - Add or remove Twitter accounts only through `commands.json`; do not maintain a section-name allowlist in scripts.
  - `text` -> output title.
  - `author.name` can be used as `section` via commands config.
  - URL auto-generated as `https://x.com/{screenName}/status/{id}?s=20`.
  - `createdAtLocal` -> 发布时间.
  - `quotedTweet.text` renders as blockquote.
  - The translation map must keep the same split: main tweet translation in `title`, quoted tweet translation in canonical `quoted_text_zh`.
  - `quoted_text` is accepted for legacy artifacts only when `quoted_text_zh` is absent. If both keys are present, the canonical key wins; distinct Chinese values are a validation conflict.
  - Long Twitter posts should be translated in full; do not collapse them into a short summary sentence.
  - If `quoted_text_zh` is provided, only Chinese quote text is rendered (no bilingual block).
  - Recommended translation policy: only translate non-Chinese text.
- Add final block:

```markdown
  • Bloomberg
  • Reuters · World

约束:
- 缺失的时间需显示为`页面未显示`
- 彭博社摘要在有翻译时需显示为中文。翻译映射可使用`summary`或`summary_zh`;优先使用`summary_zh`以保证清晰
- 若存在非中文彭博社摘要但翻译后的摘要缺失或经一次修复后仍为非中文,`finalize`会写入原始源摘要,且不添加用户可见的错误
- 保留首次出现顺序:先按命令顺序,再按数据源顺序
- 全局去重键为URL精确匹配
- 每日过滤会移除昨日的URL
- 单次运行过滤会移除昨日的URL及当日更早出现的URL
- 支持Twitter(`twitter user-posts --json`):
  - `references/commands.json`中的每个Twitter命令必须声明`source_type: "twitter"`、`source_handle`和`source_name`;这些字段用于标识配置的采集账号,并会流入每个标准化条目和附属文件
  - 将采集标识与内容作者分开:`source_handle` / `source_name`标识采集的时间线所属账号,而`author_screen_name` / `author_name`和条目URL标识原创、转发、回复和引用内容的实际作者
  - 仅通过`commands.json`添加或移除Twitter账号;请勿在脚本中维护章节名称白名单
  - `text`字段对应输出标题
  - `author.name`可通过命令配置用作`section`字段
  - URL自动生成为`https://x.com/{screenName}/status/{id}?s=20`
  - `createdAtLocal`对应发布时间
  - `quotedTweet.text`会渲染为块引用
  - 翻译映射必须保持拆分:主推文翻译在`title`中,引用推文翻译在标准`quoted_text_zh`中
  - 仅当`quoted_text_zh`缺失时,才接受旧版产物中的`quoted_text`。若两个键同时存在,标准键优先;不同的中文值会被视为验证冲突
  - 长Twitter帖子需完整翻译;请勿压缩为简短摘要
  - 若提供`quoted_text_zh`,仅渲染中文引用文本(无双语块)
  - 推荐翻译策略:仅翻译非中文文本
- 添加最终错误块:

```markdown

errors

errors

1. section

1. section

  • 命令:
    ...
  • 错误:...
undefined
  • 命令:
    ...
  • 错误:...
undefined

Validation Checklist

验证清单

  1. Pipeline runs all commands sequentially.
  2. Failed command does not stop later commands.
  3. Duplicate URLs are removed globally, keeping first occurrence.
  4. Translation is model-handled, not external translation API.
  5. Non-empty sections include display names and summary blockquotes; empty sections are grouped under
    本次无更新的分组
    .
  6. Finalize writes
    dailyFreshNews_YYYY-MM-DD.md
    and
    YYYY-MM-DD-HH-mm-ss-<sha256前12位>_freshNews.md
    , not
    *_fullNews.md
    .
  7. Reusing stale pipeline JSON or attempting to overwrite an existing run file must fail loudly.
  1. 流水线按顺序运行所有命令
  2. 失败的命令不会终止后续命令
  3. 重复URL会被全局移除,保留首次出现的条目
  4. 翻译由模型处理,而非外部翻译API
  5. 非空章节包含展示名称和摘要块引用;空分组归类到
    本次无更新的分组
  6. Finalize生成
    dailyFreshNews_YYYY-MM-DD.md
    YYYY-MM-DD-HH-mm-ss-<sha256前12位>_freshNews.md
    ,而非
    *_fullNews.md
  7. 复用过时的流水线JSON或尝试覆盖已存在的运行文件必须直接失败