Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,21 @@

## [Unreleased]

_本区记录 `[1.0.0]` 之后、尚未发布的所有改动;发布时把本区内容并入新的版本号。_
_本区记录 `[1.1.0]` 之后、尚未发布的所有改动;发布时把本区内容并入新的版本号。_

## [1.1.0] - 2026-10-24

自 `[1.0.0]` 以来的全部改动(含 2026-09-22 的 runner/测试批次与 2026-10-24 的第一性原理用户视角修复)。

### Added

- `tests/test_runner.py`:覆盖 pipeline runner 的参数转发、门禁拦截、运行状态文件等行为
- `tests/test_fetch_aihot.py`:覆盖 `fetch_aihot.py` 的 CLI 与 `fetch_json` 重试逻辑
- `tests/test_gate_adversarial.py`:8 个对抗式用例,固化“试图绕过用户确认闸门”的几种走法(跳过步骤 03、只传 `--confirm`、过期确认、过期活人感门禁、损坏的运行状态文件),任一步被改坏都会红
- `skills/tryworld-paper/references/workflow.md` §1.4:写明闸门的技术边界——`.prose_pass` / `.confirmed` 只绑 draft 的 sha256,防漂移不防同用户伪造,因此“等用户确认”必须是对话里真实发生的动作
- `check_prose.py --whitelist` 正式接入流水线:`step_02_check_prose.py` 与 `pipeline.runner` 新增 `--whitelist` 透传(路径按调用方 cwd 解析为绝对路径)。此前该参数只存在于脚本内、无任何文档、也无法从流水线使用。豁免是**可见退出**——命中内容不被检查,运行会打印 `NOTE:` 行
- 技术记法标点豁免:数字间的 `16:9`、`4:3`、`12:30`、`2024–2026` 不再判违规(降为提示)。此前这类写法被标点禁令硬拦(exit 1),而它们是这一内容品类绕不开的写法;`:` 提示性冒号、`——` 破折号、带空格的 `12 :30`、词间 `–` 照旧拦截
- `tests/test_prose.py`:新增 8 例覆盖豁免边界(比例/时间/区间放行;提示性冒号、破折号、词间连接号、带空格形式仍拦)与白名单行为(含非法正则不崩溃)

### Changed

Expand All @@ -21,6 +28,10 @@ _本区记录 `[1.0.0]` 之后、尚未发布的所有改动;发布时把本
- `README.md` / `README.zh-CN.md` / `CONTRIBUTING.md` / 各 `SKILL.md` / `references/`:统一指向 `.py` 脚本,移除 `.ps1` 作为规范路径的表述
- `pipeline/steps/step_04_tts.py`:澄清 `--theme-content` 帮助文本(品牌/视觉主题,默认 `themes/paper-algorithm.json`),并提示与 step 01 的“内容主题”同名不同义
- `CHANGELOG.md`:将此前 `[Unreleased]` 内容并入 `[1.0.0]`,改为真正的“未发布”区
- `pipeline/steps/step_01_optimize.py` → `step_01_ingest.py`:该步骤一直逐字复制输入,却命名为 optimize 并让用户“审阅 draft.md”,暗示稿子已被润色。改名并改正 docstring、argparse 描述与 `ACTION REQUIRED` 输出,明确“这是逐字副本,优化与净化由 agent 按 SKILL.md 完成,改完重跑步骤 02”;`step_02` 的提示语与 README×2 步骤清单同步为 `01 ingest`
- README(中英)「Pipeline Runner」:如实说明 Runner 是步骤 2–4 的门禁记账器,不写稿也不渲染;步骤 05/06 依赖没有任何步骤会生成的构图,只在项目已存在时执行
- README(中英)「活人感门禁」:删去“禁的是修辞动作不是字面,换词仍算命中”这一与实现不符的说法,改为写清——约 22 类信号中只有 5 类能判失败(标点、硬停词、绝对黑话、模型路标、翻案句式),其余为交给人判断的提示;规则是字面正则与词表,换词改写可能漏网;真正的活人感来自 §1.2.1 七遍改稿指令,无脚本强制
- `references/workflow.md` §1.2.1 第 5 条与 `references/titles.md`:标点口径与检查器同步(技术记法豁免、白名单用法),避免散文与代码漂移

### Fixed

Expand All @@ -29,6 +40,7 @@ _本区记录 `[1.0.0]` 之后、尚未发布的所有改动;发布时把本
- `pipeline/runner.py`:在仓库根目录以外启动时,用户写的相对路径不再解析错位(步骤以 `cwd=REPO` 运行,runner 现在先把 `--project-dir` / `--script` / `--theme-content` / `--video` 解析为绝对路径);`--steps` 容忍空格与空 token(如 `"1, 2,"`),非法 token 的报错直接指出问题值
- `fetch_aihot.py`:`--base-url` 归一化去掉尾部斜杠,避免拼出 `//api/...`
- `notify_delivery.ps1` / `fetch_aihot.ps1`:DEPRECATED 头中的规范路径改为仓库相对路径,避免与 `scripts/` 混淆
- `skills/tryworld-koubo/SKILL.md:21` 与 `skills/tryworld-topics/SKILL.md:12`:修复被转义吃成制表符的路径与字段名(`\tryworld-paper/...`、`\topic_sources`、`\topic_rules`)。这些引用自 `6f81f86`(2026-09-01)起在任何安装环境中都无法解析,本次在 git blob 层修复

## [1.0.0] - 2026-09-05

Expand Down
25 changes: 19 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ That's it. The request is recognized and routed through the whole pipeline autom

### Pipeline Runner (Optional)

After installation, use the pipeline runner instead of manually executing steps. The runner runs 6 step scripts in order, checks preconditions at each step, and stops on failure:
The runner is a **gate bookkeeper for steps 2–4**, not a replacement for the agent's work. It runs the preconditions in order and stops on the first failure, but the content itself (optimising and sanitising the script, authoring the HyperFrames composition, covers, titles, publish plan, email) stays in the SKILL.md prose — the runner never writes or renders it. Step 01 copies the input verbatim to `work/draft.md`; the agent then optimises it, re-runs step 02, and step 03 blocks until the user confirms. Steps 05/06 require a composition that no step produces, so run them only once that project exists.

```bash
# List all steps
Expand All @@ -92,7 +92,7 @@ python -m pipeline.runner --project-dir <project-dir> --steps 1,2
python -m pipeline.runner --project-dir <project-dir> --steps 3,4,5,6
```

Six steps: `01 optimize` → `02 check_prose` → `03 confirm` (hard gate) → `04 tts` → `05 render` → `06 verify` (hard gate).
Six steps: `01 ingest` → `02 check_prose` → `03 confirm` (hard gate) → `04 tts` → `05 render` → `06 verify` (hard gate).

---
## 📑 Contents
Expand Down Expand Up @@ -315,16 +315,29 @@ Full edge-tts voice ids are accepted too. By default the pipeline never asks abo

## 🔒 The Aliveness Gate

Before delivery, every polished script and platform title passes a machine gate — **the ban targets rhetorical moves, not literal strings**. Repeating the same move with different words still counts.
Before delivery, every polished script and platform title passes a machine gate. Be clear about what that gate is and what it is not.

The banned moves (flip-flop rhetoric, triple+ parallel structure, lyric metaphor, nominalization, punctuation tiers, hard stop-words, model signposts, jargon tiers) are defined in exactly two places, so the list cannot drift between copies:
**What blocks.** `check_prose.py` runs ~22 signal families, but only five of them can fail the run (exit 1): punctuation (colons / dashes), hard stop-words (3 terms), absolute jargon (27 terms), model insight signposts (6 phrases), and the flip-flop construction (`不是……而是……` and 7 regex variants). Everything else — parallel structure, lyric metaphor, nominalization, conjunction density, 「」-quote density, sentence-length variance, context jargon — is reported as a **warning for a human to judge** and never blocks delivery.

**What it cannot do.** These are surface-form regex and word-list rules, not semantic analysis. The rules catch a wide set of *literal fingerprints* of model prose, and rewriting the same rhetorical move in different words can slip past them. The gate is a formatting-and-cliché linter, not an aliveness detector. The real anti-AI writing quality lives in the instructions addressed to the agent — the seven-pass rewrite in `references/workflow.md` §1.2.1 and `writing-methodology.md` — which no script enforces. Treat a green gate as "no fingerprints found", not as "this reads like a human".

**Known trade-off, and how to override.** The five blocking rules are aggressive by design, so some legitimate prose trips them (a plain causal correction like `不是因为A,而是B` is flagged). Technical notation is exempt: `16:9`, `4:3`, `12:30`, `2024–2026` pass as a warning, because that notation is unavoidable in this genre. For anything else you judge to be a false positive, `--whitelist <file>` (one regex per line) strips matched text before analysis:

```bash
python -X utf8 skills/tryworld-paper/scripts/check_prose.py draft.md --whitelist exemptions.txt
python -m pipeline.runner --project-dir <dir> --steps 1,2 --script draft.md --whitelist exemptions.txt
```

The whitelist deletes matched text, so exempted content is *not checked at all* — it is an explicit, visible opt-out (the run prints a `NOTE:` line), not a silent bypass. Keep it to specific terms; a too-broad pattern empties the text and the checker exits 2.

The banned moves are defined in exactly two places, so the list cannot drift between copies:

| Source | What it holds |
|---|---|
| `skills/tryworld-paper/references/workflow.md` §1.2.1 (item 5) | The prose rules, with action-level definitions |
| `skills/tryworld-paper/scripts/check_prose.py` | The checker that enforces them — absolute bans + context-sensitive words, plus statistical signals (sentence-length variance, conjunction density, model-favorite lyric words, 「」-quote density) |
| `skills/tryworld-paper/scripts/check_prose.py` | The checker: the five blocking rules plus ~17 advisory statistical signals |

`check_prose.py` (from [human-writing](https://github.com/KKKKhazix/human-writing) v1.1.0, MIT) is the authority when the two disagree in wording. **Zero hard violations required before the user-confirmation gate; failing means no delivery.**
`check_prose.py` (from [human-writing](https://github.com/KKKKhazix/human-writing) v1.1.0, MIT) is the authority when the two disagree in wording. **Zero blocking violations required before the user-confirmation gate; failing means no delivery.**

---

Expand Down
25 changes: 19 additions & 6 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ Copy-Item -Path .\skills\* -Destination "$env:USERPROFILE\.agents\skills" -Recur

### Pipeline Runner(可选)

安装后可以用 pipeline runner 代替手动执行流程。Runner 按编号顺序运行 6 个步骤脚本,每步检查前置条件,失败即停:
Runner 是**步骤 2–4 的门禁记账器**,不是手动流程的替代品。它按编号顺序检查前置条件、失败即停,但内容本身(优化与净化稿子、编写 HyperFrames 构图、封面、标题、发布计划、邮件通知)仍由 SKILL.md 的散文流程负责——Runner 不写稿也不渲染。步骤 01 只把输入逐字复制成 `work/draft.md`;由 agent 完成优化后重跑步骤 02,步骤 03 在用户确认前一直拦截。步骤 05/06 依赖一个没有任何步骤会生成的构图,只有该项目已存在时才执行:

```bash
# 查看所有步骤
Expand All @@ -92,7 +92,7 @@ python -m pipeline.runner --project-dir <项目目录> --steps 1,2
python -m pipeline.runner --project-dir <项目目录> --steps 3,4,5,6
```

六个步骤:`01 optimize` → `02 check_prose` → `03 confirm`(硬闸门)→ `04 tts` → `05 render` → `06 verify`(硬闸门)。
六个步骤:`01 ingest` → `02 check_prose` → `03 confirm`(硬闸门)→ `04 tts` → `05 render` → `06 verify`(硬闸门)。

---
## 📑 目录
Expand Down Expand Up @@ -315,16 +315,29 @@ python tryworld-paper/scripts/tts_yunxi.py script.txt --out work/audio --voice x

## 🔒 活人感门禁

成片之前,每一版口播稿与平台标题都要过一道机器门禁——**禁的是修辞动作,不是字面**。换一套字做同一个动作,仍然算命中。
成片之前,每一版口播稿与平台标题都要过一道机器门禁。这道门禁是什么、不是什么,先说清楚。

禁项(翻案腔、同构排比、抒情借喻、动词名词化、标点分级、硬停词、模型路标、黑话两档)只在两处定义,避免多份拷贝互相漂移:
**会拦截的(exit 1)。** `check_prose.py` 共约 22 类信号,但只有 5 类能判失败:标点(冒号/破折号)、硬停词(3 个)、绝对黑话(27 个)、模型洞察路标(6 个短语)、翻案句式(`不是……而是……` 等 7 个正则变体)。其余全部——同构排比、抒情借喻、名词化、连词密度、「」金句密度、句长变异系数、语境判断词——只作为**警告交给人判断**,不拦交付。

**拦不住的。** 这些是字面正则和词表规则,不是语义分析。它能抓住大量模型腔的*字面指纹*,但同一个修辞动作换一套字,可能查不出来。这道门禁是格式与套话检查器,不是活人感检测器。真正决定"像不像人写的",是给 agent 的写作指令——`references/workflow.md` §1.2.1 的七遍改稿和 `writing-methodology.md`——那部分没有任何脚本在强制。门禁通过请理解为"未发现字面指纹",不等于"读起来像人写的"。

**已知代价与豁免。** 5 条拦截规则刻意从严,正常行文偶尔误伤(平实因果句"不是因为A,而是B"会被判翻案)。技术记法已豁免:`16:9`、`4:3`、`12:30`、`2024–2026` 只提示不拦截,因为这类内容绕不开这些写法。其余误伤可用 `--whitelist <文件>`(每行一个正则)在分析前剔除匹配文本:

```bash
python -X utf8 skills/tryworld-paper/scripts/check_prose.py draft.md --whitelist exemptions.txt
python -m pipeline.runner --project-dir <项目目录> --steps 1,2 --script draft.md --whitelist exemptions.txt
```

白名单是删除文本,被豁免的内容*完全没有被检查*——所以它是显式、可见的退出(运行会打印 `NOTE:` 行),不是静默绕过。只写具体词条;过宽的模式会把全文清空,检查器直接 exit 2。

禁项只在两处定义,避免多份拷贝互相漂移:

| 来源 | 内容 |
|---|---|
| `skills/tryworld-paper/references/workflow.md` §1.2.1 第 5 条 | 改稿规则与动作级定义 |
| `skills/tryworld-paper/scripts/check_prose.py` | 执行检测的脚本——绝对禁词 + 语境判断词,并给出统计层提示(句长变异系数、连词密度、模型偏爱抒情词、「」金句密度) |
| `skills/tryworld-paper/scripts/check_prose.py` | 执行检测的脚本——5 类拦截规则 + 约 17 类咨询性统计信号 |

措辞不一致时以 `check_prose.py`(源自 [human-writing](https://github.com/KKKKhazix/human-writing) v1.1.0,MIT)为准。**硬禁项清零才允许进入用户确认闸门,失败不交付。**
措辞不一致时以 `check_prose.py`(源自 [human-writing](https://github.com/KKKKhazix/human-writing) v1.1.0,MIT)为准。**拦截项清零才允许进入用户确认闸门,失败不交付。**

---

Expand Down
6 changes: 5 additions & 1 deletion pipeline/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
# (or, for --confirm, when the flag is present).
STEP_ARGS: dict[int, list[str]] = {
1: ["--script", "--theme-content"],
2: ["--whitelist"],
3: ["--confirm"],
4: ["--theme-content"],
5: ["--quality"],
Expand Down Expand Up @@ -111,6 +112,9 @@ def main() -> int:
help="Theme JSON forwarded to step 01 (content theme) / step 04 (brand/visual theme)")
parser.add_argument("--confirm", action="store_true",
help="Forward the confirmation flag to step 03; without it the gate blocks")
parser.add_argument("--whitelist", type=Path, default=None,
help="Regex-per-line file forwarded to step 02, exempting literal terms "
"from the prose gate. Visible opt-out, recorded in the run output.")
parser.add_argument("--quality", choices=["draft", "high"], default=None,
help="Render quality forwarded to step 05 (default: the step's own default, high)")
parser.add_argument("--video", type=Path, default=None,
Expand Down Expand Up @@ -146,7 +150,7 @@ def main() -> int:
# shell must be made absolute here — otherwise a runner started outside the
# repo root would hand the step a path that resolves somewhere else entirely.
args.project_dir = args.project_dir.expanduser().resolve()
for flag in ("script", "theme_content", "video"):
for flag in ("script", "theme_content", "video", "whitelist"):
value = getattr(args, flag)
if value is not None:
setattr(args, flag, value.expanduser().resolve())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
#!/usr/bin/env python3
"""Step 01: Read script, load content theme, output draft.md for user confirmation."""
"""Step 01: Ingest the input script into work/draft.md (verbatim copy).

This step does NOT optimize the script. Optimization (traffic first-principles +
the seven-pass aliveness rewrite + script sanitising) is the agent's job, per
tryworld-koubo / tryworld-paper SKILL.md. The runner is a gate bookkeeper, not a
writer: it copies the input verbatim and binds the approval markers to that
exact content, so a later edit to draft.md invalidates any prior confirmation.
"""
import argparse
import json
import sys
Expand All @@ -10,7 +17,7 @@


def main() -> int:
parser = argparse.ArgumentParser(description="Step 01: Optimize script")
parser = argparse.ArgumentParser(description="Step 01: Ingest input script to draft.md (no optimization)")
parser.add_argument("--project-dir", type=Path, default=Path.cwd())
parser.add_argument("--script", type=Path, required=True, help="Path to input script (.md/.txt)")
parser.add_argument("--theme-content", type=Path, default=None, help="Path to content theme JSON (default: themes/content-default.json)")
Expand Down Expand Up @@ -47,7 +54,9 @@ def main() -> int:
han_count = sum(1 for c in text if "\u4e00" <= c <= "\u9fff")
print(f"Input script: {args.script.name} ({han_count} hanzi)")

# For now, pass through. In production, Agent would optimize here.
# Verbatim ingest. Optimization belongs to the agent (SKILL.md workflow), not
# to this step — draft.md is the exact text that the prose gate and the user
# confirmation are bound to.
work_dir = args.project_dir / "work"
work_dir.mkdir(parents=True, exist_ok=True)
draft = work_dir / "draft.md"
Expand All @@ -57,8 +66,11 @@ def main() -> int:
(work_dir / marker_name).unlink(missing_ok=True)
print(f"Draft written to: {draft}")

print("\nACTION REQUIRED: Review draft.md and confirm before proceeding to step 02.")
print("This step does NOT auto-confirm. User must explicitly approve.")
print("\nACTION REQUIRED: this is a verbatim copy of the input, NOT an optimized script.")
print("Per tryworld-koubo / tryworld-paper SKILL.md, the agent must now optimize +")
print("sanitise the script (traffic first-principles, seven-pass aliveness rewrite),")
print("overwrite work/draft.md with the optimized text, then run step 02 (prose gate).")
print("Step 03 blocks until the user confirms; this step does NOT auto-confirm.")
return 0


Expand Down
Loading
Loading