CI Pipeline / best-practices (push) Successful in 8s
Auto-version and Release / release (push) Failing after 14s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / lint (push) Failing after 34s
CI Pipeline / canary (push) Successful in 37s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / unit-tests (push) Successful in 1m4s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 48s
CORRECTION 1: #314 reopened — recall_with_associations mode field defect CONFIRMED in current source and FIXED (mode: association_merged). CORRECTION 3: Gitea credential removed from remote URL (credential helper). TOKEN EXPOSED: yes (in diagnostic output). TOKEN ROTATED: not yet (requires Gitea web UI access). Remote URL now shows username only, no secret. CORRECTION 4: duplicate-release-commit defect ROOT CAUSE: convergence computed warranted bump from commits-since-tag without checking if master version already satisfies the bump. FIX: already-converged guard in version_convergence.py (master version > last tag → no commit). Repo-aware (args.repo passed to read_version/latest_tag). CORRECTION 6: lifecycle terminalization — execution exception now guarantees task terminalizes as failed with evidence (no immortal RUNNING). CORRECTION 7: route instruction task-scoped on RuneTask.route_instruction (NOT shared operator state). Read-only leading-verb override. Isolation tests prove no cross-contamination between interleaved tasks. PL1-01/02/03/05: pl1_records.py (route resolution, proof level, failure class, candidate status), metrics with honest NOT_YET_MEASURABLE semantics, route-instruction learning loop (PE-1789102658-0269 SEED). 1253 tests green; ratchet 352/80 (shrink); henxels clean.
99 lines
3.4 KiB
Python
99 lines
3.4 KiB
Python
#!/usr/bin/env python3
|
|
"""research_prompt_compiler.py — dynamic stage prompt compiler (RSC2).
|
|
|
|
Takes the ResearchTaskTree, the current stage, and the stage policy template,
|
|
and compiles a task-specific prompt that answers:
|
|
|
|
What exactly am I researching?
|
|
What has already been established?
|
|
What evidence is authoritative?
|
|
What remains unknown?
|
|
What am I allowed to do now?
|
|
What am I forbidden from doing yet?
|
|
What output must I return?
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
STAGE_POLICY_DIR = Path(__file__).parent.parent / "prompts" / "research-stages"
|
|
|
|
|
|
def compile_research_prompt(
|
|
tree, # ResearchTaskTree
|
|
stage: str,
|
|
stage_policy: str | None = None,
|
|
) -> str:
|
|
"""Compile a task-specific prompt for the current research stage."""
|
|
capsule = tree.capsule()
|
|
prior = tree.get_prior_artifacts(stage)
|
|
unknowns = tree.state.get("blocking_unknowns", [])
|
|
|
|
# Load the stage policy template
|
|
template_path = STAGE_POLICY_DIR / f"{stage.lower()}.md"
|
|
if stage_policy:
|
|
template = stage_policy
|
|
elif template_path.exists():
|
|
template = template_path.read_text()
|
|
else:
|
|
template = f"Stage: {stage}\nPerform the {stage} research task."
|
|
|
|
# Build the prior artifacts digest (only what this stage needs)
|
|
prior_digest = ""
|
|
if prior:
|
|
prior_lines = []
|
|
for stage_name, artifact in prior.items():
|
|
if not artifact:
|
|
continue
|
|
keys = list(artifact.keys())[:5]
|
|
prior_lines.append(f" {stage_name}: {json.dumps(artifact, default=str)[:200]}")
|
|
prior_digest = "\n".join(prior_lines)
|
|
|
|
# Build the blocking unknowns section
|
|
unknowns_section = ""
|
|
if unknowns:
|
|
unknowns_section = "\n## Blocking unknowns\n"
|
|
for u in unknowns:
|
|
unknowns_section += f"- {u.get('id', '?')}: {u.get('question', '?')}\n"
|
|
|
|
# Substitute placeholders
|
|
prompt = template
|
|
prompt = prompt.replace("{{ISSUE_NUMBER}}", str(capsule["issue"]))
|
|
prompt = prompt.replace("{{ROUND}}", str(capsule["round"]))
|
|
prompt = prompt.replace("{{PRIOR_ARTIFACTS}}", prior_digest or "(none yet)")
|
|
prompt = prompt.replace("{{BLOCKING_UNKNOWNS}}", unknowns_section or "(none)")
|
|
|
|
# Append the process capsule (§14: MiMo must not infer from history)
|
|
prompt += "\n\n---\n## Process capsule (authoritative — do not infer from history)\n"
|
|
prompt += f"```json\n{json.dumps(capsule, indent=2, default=str)}\n```"
|
|
|
|
return prompt
|
|
|
|
|
|
def main_with(argv: list[str]) -> int:
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(description="Compile a stage-scoped research prompt")
|
|
parser.add_argument("--issue", type=int, required=True)
|
|
parser.add_argument("--round", type=int, default=1)
|
|
parser.add_argument("--stage", required=True)
|
|
parser.add_argument("--artifact-dir", required=True)
|
|
parser.add_argument("--out", required=True)
|
|
args = parser.parse_args(argv[1:])
|
|
|
|
tree = __import__("research_task_tree").ResearchTaskTree(
|
|
issue=args.issue, round_no=args.round, artifact_dir=args.artifact_dir
|
|
)
|
|
prompt = compile_research_prompt(tree, args.stage)
|
|
Path(args.out).write_text(prompt)
|
|
print(f"prompt compiled: {len(prompt)} chars for {args.stage} (issue #{args.issue}, round {args.round})")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
|
|
sys.exit(main_with(sys.argv))
|