release: v0.20 Codex-ready skill-driven core

This commit is contained in:
kai
2026-05-07 08:21:28 +08:00
parent 0644a68ecc
commit 68e45bcf41
45 changed files with 3005 additions and 157 deletions
+170 -15
View File
@@ -30,11 +30,12 @@ from scripts.runtime.assembly import build_chapter_briefs, build_compressed_find
from scripts.runtime.orchestrator import create_phase2_task_cards, write_placeholder_packets
from scripts.runtime.methods import ResearchMethodRegistry
from scripts.runtime.phase1 import create_project, render_framework, write_material_brief
from scripts.runtime.review import build_phase3_critique
from scripts.runtime.review import build_phase3_critique, build_phase3_model_critique
from scripts.runtime.roles import resolve_runtime_profile
from scripts.runtime.source_cache import cache_sources
from scripts.runtime.sources import rebuild_sources_from_packets
from scripts.runtime.skills import SkillRegistry, default_adapter_skill_dirs
from scripts.runtime.tasks import TaskCard
from scripts.runtime.tasks import TaskCard, load_task_cards, validate_packet, write_task_cards
from scripts.runtime.workers import run_packet_workers
@@ -156,7 +157,12 @@ def cmd_frame(args: argparse.Namespace) -> int:
print(f"Research method: {method.key}")
print(f"Chapters: {args.chapters}")
return 0
path = render_framework(project_root, method_key=args.method, chapter_count=args.chapters)
path = render_framework(
project_root,
method_key=args.method,
chapter_count=args.chapters,
preserve_existing_outline=args.preserve_existing_outline,
)
print(f"Project: {project_root.name}")
print(f"Wrote: {path.relative_to(project_root)}")
print("Pause: review and approve the framework before Phase 2.")
@@ -222,12 +228,62 @@ def cmd_research(args: argparse.Namespace) -> int:
"Phase 1 is not approved. Review phase1/material_brief.md and phase1/framework.md, "
"then run `uv run python scripts/dr.py approve <project>` or pass --force."
)
runtime = resolve_runtime_profile(profile=args.profile)
card_dicts = create_phase2_task_cards(
project_root,
axes=args.axis,
dry_run=args.dry_run,
if args.profile == "codex_native" and (args.execute_packets or args.assemble_chapters):
raise SystemExit(
"`codex_native` cannot be used for Python-core model execution: scripts/dr.py currently calls external "
"API clients, not Codex App built-in models. Use a clearly external profile such as `medium`, or run "
"Codex-native execution through the surface adapter/manual task workflow."
)
runtime = resolve_runtime_profile(
profile=args.profile,
overrides=parse_model_overrides(args.model_override),
)
if args.append_task_cards:
generated = create_phase2_task_cards(
project_root,
axes=args.axis,
dry_run=True,
)
existing_path = project_root / "phase2" / "task_cards.json"
existing_cards = load_task_cards(existing_path) if existing_path.exists() else []
seen = {card.task_id for card in existing_cards}
appended_cards = [TaskCard(**item) for item in generated if item["task_id"] not in seen]
runnable_existing_cards: list[TaskCard] = []
if args.execute_packets and args.axis:
axis_set = set(args.axis)
for card in existing_cards:
if card.topic_axis not in axis_set:
continue
packet_path = project_root / card.output_packet
try:
validate_packet(json.loads(packet_path.read_text(encoding="utf-8")))
except Exception:
runnable_existing_cards.append(card)
merged_cards = [*existing_cards, *appended_cards]
if not args.dry_run:
write_task_cards(existing_path, merged_cards)
phase2 = manifest.setdefault("phase2", {})
phase2.update(
{
"status": "in_progress",
"runtime": "python-core-v0.20",
"task_cards_path": "phase2/task_cards.json",
"task_cards_total": len(merged_cards),
"task_cards_appended": len(appended_cards),
"updated_at": datetime.now(timezone.utc).replace(microsecond=0).isoformat(),
}
)
(project_root / "manifest.json").write_text(
json.dumps(manifest, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
card_dicts = [card.to_dict() for card in [*appended_cards, *runnable_existing_cards]]
else:
card_dicts = create_phase2_task_cards(
project_root,
axes=args.axis,
dry_run=args.dry_run,
)
if args.execute_packets and args.dry_run:
raise SystemExit("--execute-packets cannot be combined with --dry-run")
if args.assemble_chapters and args.dry_run:
@@ -350,7 +406,9 @@ def cmd_run(args: argparse.Namespace) -> int:
workers=args.workers,
axis=None,
profile=args.profile,
model_override=[],
execute_packets=False,
append_task_cards=False,
allow_search_fallback=False,
build_briefs=False,
assemble_chapters=False,
@@ -362,7 +420,26 @@ def cmd_run(args: argparse.Namespace) -> int:
def cmd_review(args: argparse.Namespace) -> int:
project_root = resolve_project(args.project)
path = build_phase3_critique(project_root)
if args.model_review:
if args.dry_run:
print(f"Project: {project_root.name}")
print("Phase 3 model review plan:")
print(f" model: {args.model}")
print(" context: phase3/review_context_opus_4_7.md")
print(" output: phase3/critique.md")
return 0
from scripts.lib.zenmux_client import ZenMuxClient, load_secrets
load_secrets()
with ZenMuxClient(log_file=project_root / "phase3" / "logs" / "review.jsonl") as client:
path = build_phase3_model_critique(
project_root,
client=client,
model=args.model,
max_context_chars=args.max_context_chars,
)
else:
path = build_phase3_critique(project_root)
print(f"Project: {project_root.name}")
print(f"Wrote: {path.relative_to(project_root)}")
print("Pause: review critique before Phase 4.")
@@ -482,12 +559,31 @@ def cmd_finalize(args: argparse.Namespace) -> int:
roles = resolved["roles"]
if not args.legacy_translate:
final_input = args.input
if args.number_citations and not args.dry_run:
rc = run_cmd(
[
sys.executable,
str(REPO_ROOT / "scripts" / "number_citations.py"),
str(project_root),
"--input",
args.input,
"--output",
args.numbered_output,
],
dry_run=False,
)
if rc != 0:
return rc
final_input = args.numbered_output
elif args.number_citations:
final_input = args.numbered_output
cmd: list[str] = [
sys.executable,
str(REPO_ROOT / "scripts" / "build_report.py"),
str(project_root),
"--input",
args.input,
final_input,
]
if args.report_engine:
cmd += ["--engine", args.report_engine]
@@ -497,6 +593,21 @@ def cmd_finalize(args: argparse.Namespace) -> int:
cmd.append("--no-pdf")
if args.dry_run:
print("Chinese-native finalize plan:")
if args.number_citations:
print(
"$ "
+ " ".join(
[
sys.executable,
str(REPO_ROOT / "scripts" / "number_citations.py"),
str(project_root),
"--input",
args.input,
"--output",
args.numbered_output,
]
)
)
print("$ " + " ".join(cmd))
if args.polish:
print(
@@ -506,8 +617,8 @@ def cmd_finalize(args: argparse.Namespace) -> int:
sys.executable,
str(REPO_ROOT / "scripts" / "polish.py"),
str(project_root),
"--input",
args.input,
"--source",
final_input,
"--workers",
str(args.polish_workers),
"--model",
@@ -522,8 +633,8 @@ def cmd_finalize(args: argparse.Namespace) -> int:
sys.executable,
str(REPO_ROOT / "scripts" / "polish.py"),
str(project_root),
"--input",
args.input,
"--source",
final_input,
"--workers",
str(args.polish_workers),
"--model",
@@ -633,6 +744,30 @@ def cmd_models(args: argparse.Namespace) -> int:
return 0
def cmd_sources(args: argparse.Namespace) -> int:
project_root = resolve_project(args.project)
if args.sources_cmd == "cache":
if args.dry_run:
print(f"Project: {project_root.name}")
print(f"Would cache sources from: {args.sources}")
print(f"Important only: {not args.all}")
print(f"Limit: {args.limit}")
return 0
results = cache_sources(
project_root,
sources_rel=args.sources,
important_only=not args.all,
limit=args.limit,
force=args.force,
)
print(f"Project: {project_root.name}")
print(f"Cached source snapshots: {len(results)}")
print("Wrote: phase2/source_cache/md/*.md")
print("Updated: phase2/sources.jsonl")
return 0
raise SystemExit(f"unknown sources command: {args.sources_cmd}")
def cmd_apply_models(args: argparse.Namespace) -> int:
cmd = [
sys.executable,
@@ -672,6 +807,7 @@ def build_parser() -> argparse.ArgumentParser:
frame.add_argument("project", help="Project slug or path")
frame.add_argument("--method", help="Override research method key")
frame.add_argument("--chapters", type=int, default=10)
frame.add_argument("--preserve-existing-outline", action="store_true", help="Keep current framework chapter titles and enrich Phase 1 planning")
frame.add_argument("--dry-run", action="store_true")
frame.set_defaults(func=cmd_frame)
@@ -694,7 +830,9 @@ def build_parser() -> argparse.ArgumentParser:
research.add_argument("--workers", type=int, default=6)
research.add_argument("--axis", action="append", help="Restrict generated task axes; repeatable")
research.add_argument("--profile", help="Model profile name from configs/models.yaml")
research.add_argument("--model-override", action="append", default=[], metavar="ROLE=MODEL", help="Override a role model for this run; repeatable")
research.add_argument("--execute-packets", action="store_true", help="Call model workers to fill evidence packets")
research.add_argument("--append-task-cards", action="store_true", help="Append newly generated task cards instead of replacing phase2/task_cards.json")
research.add_argument("--allow-search-fallback", action="store_true", help="Allow generic search fallback for specialized routes")
research.add_argument("--build-briefs", action="store_true", help="Aggregate packets into chapter briefs")
research.add_argument("--assemble-chapters", action="store_true", help="Call model workers to write Chinese chapter drafts")
@@ -721,8 +859,12 @@ def build_parser() -> argparse.ArgumentParser:
status.add_argument("project", nargs="?", help="Project slug or path")
status.set_defaults(func=cmd_status)
review = sub.add_parser("review", help="Run deterministic Phase 3 review")
review = sub.add_parser("review", help="Run Phase 3 review")
review.add_argument("project", help="Project slug or path")
review.add_argument("--model-review", action="store_true", help="Run independent model-based Phase 3 review")
review.add_argument("--model", default="zenmux-anthropic/claude-opus-4-7", help="Model for --model-review")
review.add_argument("--max-context-chars", type=int, default=650_000, help="Bounded context size for model review")
review.add_argument("--dry-run", action="store_true")
review.set_defaults(func=cmd_review)
prompt = sub.add_parser("prompt", help="Print a Codex command prompt template")
@@ -745,6 +887,8 @@ def build_parser() -> argparse.ArgumentParser:
finalize.add_argument("--input", default="phase4/final_zh.md", help="Chinese Markdown source for default v0.20 finalization")
finalize.add_argument("--legacy-translate", action="store_true", help="Use legacy final_en -> translate -> polish pipeline")
finalize.add_argument("--polish", action="store_true", help="Run optional Chinese polish step before rendering")
finalize.add_argument("--number-citations", action="store_true", help="Convert [src_xxx] citations to numeric references before rendering")
finalize.add_argument("--numbered-output", default="phase4/final_zh_numbered.md", help="Output path for numeric citation Markdown")
finalize.add_argument("--report-engine", choices=["reportlab", "quarto"], default=None)
finalize.add_argument("--no-docx", action="store_true")
finalize.add_argument("--no-pdf", action="store_true")
@@ -781,6 +925,17 @@ def build_parser() -> argparse.ArgumentParser:
models.add_argument("--json", action="store_true", help="Emit JSON")
models.set_defaults(func=cmd_models)
sources = sub.add_parser("sources", help="Manage source snapshots and source registry")
sources_sub = sources.add_subparsers(dest="sources_cmd", required=True)
sources_cache = sources_sub.add_parser("cache", help="Cache important sources as local Markdown snapshots")
sources_cache.add_argument("project", help="Project slug or path")
sources_cache.add_argument("--sources", default="phase2/sources.jsonl", help="Source registry path relative to project")
sources_cache.add_argument("--all", action="store_true", help="Cache all remote sources, not only important official/Tier 1 sources")
sources_cache.add_argument("--limit", type=int, help="Maximum sources to cache in this run")
sources_cache.add_argument("--force", action="store_true", help="Refetch even if cached_text_path already exists")
sources_cache.add_argument("--dry-run", action="store_true")
sources_cache.set_defaults(func=cmd_sources)
apply_models = sub.add_parser("apply-models", help="Apply profile to agent files")
apply_models.add_argument("--profile", required=True, help="Profile name from configs/models.yaml")
apply_models.add_argument("--target", choices=["opencode", "codex", "both"], default="both")