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
20 changes: 18 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ jobs:
run: |
python -m py_compile $(git ls-files '*.py')

- name: Unit tests
run: |
python -m unittest discover -s tests -p 'test_*.py'

- name: Import and routing smoke check
run: |
python scripts/smoke_imports.py
Expand All @@ -34,15 +38,27 @@ jobs:
run: |
python scripts/verify_cta_resources.py

- name: Pipeline preflight dry-run
run: |
python scripts/preflight.py --input examples/example_article.md --skip-command-checks --json

- name: Orchestrator governance dry-run
run: |
python scripts/orchestrator.py --input examples/example_article.md --output-dir output/ci-dry-run --log /tmp/md2video-ci-pipeline.jsonl --dry-run --skip-command-checks --allow-dirty-output

- name: JSON validation
run: |
python -c "import json; json.load(open('harness/video-rules.json'))"
python -c "import json; json.load(open('rules/segment_types.json'))"
python -c "import json; json.load(open('rules/storyboard_rules.json'))"

- name: Self report dry-run
- name: Self report no-write dry-run
run: |
python harness/self_report.py --no-write --json

- name: Privacy gate
run: |
python harness/self_report.py
bash scripts/privacy-check.sh --full

- name: Check LESSONS_LEARNED frontmatter
run: |
Expand Down
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,14 @@ storyboard_from_article(article, output_dir='output')

# 运行自检
python harness/self_report.py

# 治理 dry-run(不调用外部素材/TTS 服务)
python scripts/orchestrator.py \
--input examples/example_article.md \
--output-dir output/dry-run \
--dry-run \
--skip-command-checks \
--allow-dirty-output
```

零外部 API 依赖即可运行语法检查和自检。即梦 CLI 仅在有 AI 素材需求时需要。
Expand All @@ -119,6 +127,31 @@ LESSONS_LEARNED.md 更新 + video-rules.json 规则演化
```

### Step -1:治理预检和运行证明

正式生成前先跑治理 dry-run,确认本仓入口、规则、CTA 资源和自检链路是自洽的:

```bash
python scripts/preflight.py \
--input examples/example_article.md \
--skip-command-checks \
--json

python scripts/orchestrator.py \
--input examples/example_article.md \
--output-dir output/dry-run \
--dry-run \
--skip-command-checks \
--allow-dirty-output
```

`scripts/orchestrator.py` 当前是治理外壳,不会调用付费或远程素材生成服务。它会写入:

- `.md2video-pipeline.jsonl`:每个治理步骤的结构化日志
- `output/dry-run/run-manifest.json`:输入 hash、仓库状态、环境版本、关键产物指纹和步骤结果

CI 也会跑这一套 dry-run,防止入口契约、QR registry、导入路由或 self-report no-write 行为漂移。

### Step 0:分镜拆解

**规则驱动,无需改代码。** 将文章输入 `storyboard_ai.py`,自动输出:
Expand Down
25 changes: 25 additions & 0 deletions SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,31 @@ harness.run("output/final_with_cta.mp4")

完整 pipeline 示例见 `examples/example_pipeline.py`。

### 3b. 治理 dry-run(推荐先跑)

在调用真实 TTS、即梦或完整视频拼接前,先运行治理入口,确认规则、CTA、
导入路由和 self-report no-write 行为没有漂移:

```bash
python scripts/orchestrator.py \
--input examples/example_article.md \
--output-dir output/dry-run \
--dry-run \
--skip-command-checks \
--allow-dirty-output
```

这个入口不调用付费或远程素材服务。它会生成:

- `.md2video-pipeline.jsonl`:结构化步骤日志
- `output/dry-run/run-manifest.json`:输入 hash、仓库状态、环境版本、关键产物指纹和步骤结果

只需要预检时可直接运行:

```bash
python scripts/preflight.py --input examples/example_article.md --skip-command-checks --json
```

## Pipeline 详解

### 数据流
Expand Down
83 changes: 83 additions & 0 deletions docs/tasks/2026-06-08-pipeline-orchestrator-governance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Task Card: Pipeline Orchestrator and Preflight Governance

Status: implemented
Created: 2026-06-08
Implemented: 2026-06-08

## Objective

Adopt the useful governance pattern from `md2wechat`: every pipeline run should
have a stable preflight envelope, structured JSONL execution log, run manifest,
no-write self-report validation, CI dry-run coverage, and a privacy gate.

## Boundary

In scope:

- Add `scripts/preflight.py` for machine-readable pipeline readiness checks.
- Add `scripts/orchestrator.py` as the governed dry-run entrypoint.
- Write `.md2video-pipeline.jsonl` and `output/run-manifest.json` style proof.
- Add `SelfReport.run(no_write=True)` and CLI `--no-write --json`.
- Add unit tests for the governance contracts.
- Add privacy scanning and CI coverage.

Out of scope:

- Paid or external video/material generation.
- Replacing `examples/example_pipeline.py` with a full production runner.
- Live E2E rendering with TTS or remote services.
- Automatic self-evolution rule generation beyond the existing `SelfReport`
behavior.

## Implementation Notes

- `scripts/preflight.py` checks:
- required command availability (`ffmpeg`, `ffprobe`) unless skipped
- governance JSON parseability
- storyboard animation routing
- CTA registry consistency
- TTS-sensitive input characters
- stale output artifacts
- `scripts/orchestrator.py` runs the governance chain:
- preflight
- CTA registry verification
- import/routing smoke
- self-report no-write validation
- run manifest write
- `harness/self_report.py --no-write --json` is safe for CI and local contract
checks because it does not write `LESSONS_LEARNED.md`, `video-rules.json`, or
`output/self_report.json`.
- `scripts/privacy-check.sh --full` is wired into CI to block common secret
patterns and local user paths in tracked text.

## Validation

Run:

```bash
python -m unittest discover -s tests -p 'test_*.py'
python scripts/preflight.py --input examples/example_article.md --skip-command-checks --json
python scripts/orchestrator.py --input examples/example_article.md --output-dir /tmp/md2video-dry-run-output --log /tmp/md2video-pipeline.jsonl --dry-run --skip-command-checks --allow-dirty-output
python harness/self_report.py --no-write --json
bash scripts/privacy-check.sh --full
python -m py_compile $(git ls-files '*.py')
python scripts/smoke_imports.py
```

Expected:

- unit tests pass
- preflight JSON reports `ok: true`
- orchestrator exits `0` and writes a JSONL log plus `run-manifest.json`
- self-report no-write exits `0` without mutating governance files
- privacy gate has no blocking findings
- syntax and import smoke checks pass

## Residual Risks

- The orchestrator is currently a governance dry-run wrapper, not a full
article-to-video runner.
- Full paid-service E2E remains outside this task and should stay explicit.
- Future self-evolution hardening should add observation-layer generated checks,
companion tests, audit records, and rollback snapshots before any generated
rule can be promoted.
30 changes: 19 additions & 11 deletions harness/self_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ def _generate_rule_id(self, category: str) -> str:
safe_cat = category.lower().replace(" ", "_").replace("/", "_").replace("-", "_")
return mapping.get(category, f"auto_{safe_cat}")

def auto_encode(self):
def auto_encode(self, write: bool = True):
"""
自动将未编码的摩擦点加入 video-rules.json

Expand Down Expand Up @@ -203,7 +203,8 @@ def auto_encode(self):
"last_evolution": datetime.now().isoformat(),
"evolution_count": self.rules.get("autopoiesis", {}).get("evolution_count", 0) + new_rules_count,
}
self._save_json(self.rules_path, self.rules)
if write:
self._save_json(self.rules_path, self.rules)

return new_rules_count

Expand Down Expand Up @@ -405,21 +406,23 @@ def print_report(self):

print("\n" + "=" * 60)

def run(self) -> Tuple[Path, Dict]:
def run(self, no_write: bool = False, print_human: bool = True) -> Tuple[Optional[Path], Dict]:
"""
完整自检流程:
1. 加载系统状态
2. 自动编码摩擦点
3. 写入活记忆
4. 生成并保存报告
5. 打印报告
3. 写入活记忆(no_write=False 时)
4. 生成并保存报告(no_write=False 时)
5. 打印报告(print_human=True 时)
"""
self.load_system_state()
self.auto_encode()
self.write_lessons()
self.auto_encode(write=not no_write)
if not no_write:
self.write_lessons()
self.generate_report()
report_path = self.save_report()
self.print_report()
report_path = None if no_write else self.save_report()
if print_human:
self.print_report()
return report_path, self.report


Expand All @@ -431,14 +434,19 @@ def main():
parser.add_argument("--project-dir", default=".", help="项目根目录")
parser.add_argument("--capture", nargs=3, metavar=("CATEGORY", "DESC", "RESOLUTION"),
help="捕获一个摩擦点:--capture '素材遗漏' 's22缺失' '补充生成'")
parser.add_argument("--no-write", action="store_true",
help="只生成内存报告,不写 LESSONS_LEARNED.md、video-rules.json 或 output/self_report.json")
parser.add_argument("--json", action="store_true", help="输出 machine-readable JSON")
args = parser.parse_args()

report = SelfReport(project_dir=args.project_dir)

if args.capture:
report.capture_friction(args.capture[0], args.capture[1], args.capture[2])

report.run()
_, data = report.run(no_write=args.no_write, print_human=not args.json)
if args.json:
print(json.dumps(data, ensure_ascii=False, indent=2))


if __name__ == "__main__":
Expand Down
Loading
Loading