Implement sprints 166-168 production code generation gates and loop
This commit is contained in:
119
tools/mcp/evaluate_generated_code_gates.py
Executable file
119
tools/mcp/evaluate_generated_code_gates.py
Executable file
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Evaluate production gates for generated code.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
|
||||
|
||||
PLACEHOLDER_PATTERNS = [
|
||||
r"\bTODO\b",
|
||||
r"\bFIXME\b",
|
||||
r"\bplaceholder\b",
|
||||
r"auto\s*/\*\s*TODO",
|
||||
r"/\*\s*missing",
|
||||
]
|
||||
|
||||
|
||||
def detect_placeholders(code: str) -> Dict[str, object]:
|
||||
findings: List[Dict[str, object]] = []
|
||||
for pat in PLACEHOLDER_PATTERNS:
|
||||
for m in re.finditer(pat, code, re.IGNORECASE):
|
||||
findings.append({"pattern": pat, "start": m.start(), "end": m.end()})
|
||||
return {"passed": len(findings) == 0, "count": len(findings), "findings": findings}
|
||||
|
||||
|
||||
def compile_check(code: str, language: str) -> Dict[str, object]:
|
||||
lang = language.lower()
|
||||
with tempfile.TemporaryDirectory(prefix="whetstone_gate_") as td:
|
||||
td_path = Path(td)
|
||||
if lang in ("cpp", "c++"):
|
||||
src = td_path / "generated.cpp"
|
||||
src.write_text(code)
|
||||
cmd = ["g++", "-std=c++20", "-fsyntax-only", str(src)]
|
||||
elif lang in ("python", "py"):
|
||||
src = td_path / "generated.py"
|
||||
src.write_text(code)
|
||||
cmd = ["python3", "-m", "py_compile", str(src)]
|
||||
else:
|
||||
return {"passed": False, "skipped": True, "reason": f"compile gate not implemented for {language}"}
|
||||
|
||||
try:
|
||||
p = subprocess.run(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
timeout=20,
|
||||
check=False,
|
||||
)
|
||||
except FileNotFoundError:
|
||||
return {"passed": False, "skipped": True, "reason": f"compiler missing for {language}"}
|
||||
except subprocess.TimeoutExpired:
|
||||
return {"passed": False, "skipped": False, "reason": "compile timeout"}
|
||||
|
||||
return {
|
||||
"passed": p.returncode == 0,
|
||||
"skipped": False,
|
||||
"return_code": p.returncode,
|
||||
"stdout": p.stdout[-4000:],
|
||||
"stderr": p.stderr[-4000:],
|
||||
}
|
||||
|
||||
|
||||
def test_check(code: str, language: str) -> Dict[str, object]:
|
||||
text = code.lower()
|
||||
if "priorityqueue" in text:
|
||||
required = ["enqueue", "dequeue", "peek", "size", "empty"]
|
||||
missing = [x for x in required if x not in text]
|
||||
return {"passed": not missing, "missing": missing}
|
||||
return {"passed": True, "missing": []}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--code-file", help="Path to generated code file", default="")
|
||||
ap.add_argument("--code", help="Inline generated code", default="")
|
||||
ap.add_argument("--language", required=True)
|
||||
ap.add_argument("--out", default="")
|
||||
args = ap.parse_args()
|
||||
|
||||
code = args.code
|
||||
if args.code_file:
|
||||
code = Path(args.code_file).read_text()
|
||||
|
||||
placeholders = detect_placeholders(code)
|
||||
compile_res = compile_check(code, args.language)
|
||||
test_res = test_check(code, args.language)
|
||||
|
||||
overall = bool(placeholders["passed"] and compile_res.get("passed", False) and test_res.get("passed", False))
|
||||
payload = {
|
||||
"language": args.language,
|
||||
"gates": {
|
||||
"compile": compile_res,
|
||||
"tests": test_res,
|
||||
"placeholder": placeholders,
|
||||
"overall_ready": overall,
|
||||
},
|
||||
}
|
||||
|
||||
text = json.dumps(payload, indent=2, sort_keys=True)
|
||||
if args.out:
|
||||
out = Path(args.out)
|
||||
out.parent.mkdir(parents=True, exist_ok=True)
|
||||
out.write_text(text + "\n")
|
||||
print(text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
90
tools/mcp/run_production_completion_loop.sh
Executable file
90
tools/mcp/run_production_completion_loop.sh
Executable file
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
BIN="${WSTONE_MCP_BIN:-$ROOT_DIR/editor/build-native/whetstone_mcp_stable}"
|
||||
WORKSPACE="${WSTONE_WORKSPACE:-$ROOT_DIR}"
|
||||
LANGUAGE="${WSTONE_LANGUAGE:-cpp}"
|
||||
MAX_ITERS="${MAX_ITERS:-3}"
|
||||
SPEC="${1:-}"
|
||||
|
||||
if [[ -z "$SPEC" ]]; then
|
||||
echo "usage: $0 \"<generation spec>\""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v jq >/dev/null 2>&1; then
|
||||
echo "error: jq required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
OUT_DIR="${OUT_DIR:-$ROOT_DIR/logs/taskitem_runs/production_loop_$(date +%Y%m%d_%H%M%S)}"
|
||||
mkdir -p "$OUT_DIR"
|
||||
|
||||
call_tool() {
|
||||
local tool_name="$1"
|
||||
local args_json="$2"
|
||||
local init_req tool_req responses tool_resp
|
||||
init_req='{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"production-loop","version":"1.0"}}}'
|
||||
tool_req="$(jq -nc --arg t "$tool_name" --argjson a "$args_json" '{jsonrpc:"2.0",id:2,method:"tools/call",params:{name:$t,arguments:$a}}')"
|
||||
responses="$(printf '%s\n%s\n' "$init_req" "$tool_req" | "$BIN" --workspace "$WORKSPACE" --language "$LANGUAGE" 2>/dev/null || true)"
|
||||
tool_resp="$(printf '%s\n' "$responses" | tail -n1)"
|
||||
printf '%s' "$tool_resp"
|
||||
}
|
||||
|
||||
extract_json() {
|
||||
local raw="$1"
|
||||
printf '%s' "$raw" | jq -r '.result.content[0].text // "{}"' | jq '.'
|
||||
}
|
||||
|
||||
loop_spec="$SPEC"
|
||||
final_ready=0
|
||||
|
||||
for i in $(seq 1 "$MAX_ITERS"); do
|
||||
args="$(jq -nc --arg s "$loop_spec" '{spec:$s,preferImports:true}')"
|
||||
raw="$(call_tool whetstone_generate_code "$args")"
|
||||
printf '%s\n' "$raw" > "$OUT_DIR/iter_${i}_generate_raw.json"
|
||||
parsed="$(extract_json "$raw")"
|
||||
printf '%s\n' "$parsed" > "$OUT_DIR/iter_${i}_generate.json"
|
||||
|
||||
code="$(printf '%s' "$parsed" | jq -r '.generatedCode // ""')"
|
||||
if [[ -z "$code" ]]; then
|
||||
code="$(printf '%s' "$parsed" | jq -r '.note // ""')"
|
||||
fi
|
||||
printf '%s\n' "$code" > "$OUT_DIR/iter_${i}_generated_code.txt"
|
||||
|
||||
python3 "$ROOT_DIR/tools/mcp/evaluate_generated_code_gates.py" \
|
||||
--code-file "$OUT_DIR/iter_${i}_generated_code.txt" \
|
||||
--language "$LANGUAGE" \
|
||||
--out "$OUT_DIR/iter_${i}_gates.json" >/tmp/production_loop_gate_${i}.json
|
||||
|
||||
ready="$(jq -r '.gates.overall_ready' "$OUT_DIR/iter_${i}_gates.json")"
|
||||
if [[ "$ready" == "true" ]]; then
|
||||
final_ready=1
|
||||
break
|
||||
fi
|
||||
|
||||
# Simple remediation strategy: force class/module details + no placeholders.
|
||||
loop_spec="$loop_spec
|
||||
Return production-ready code only:
|
||||
- no TODO/FIXME/placeholder markers
|
||||
- concrete types for fields and methods
|
||||
- class bodies for queue operations
|
||||
- compile-clean output."
|
||||
done
|
||||
|
||||
jq -nc \
|
||||
--arg out_dir "$OUT_DIR" \
|
||||
--arg spec "$SPEC" \
|
||||
--argjson max_iters "$MAX_ITERS" \
|
||||
--argjson overall_ready "$final_ready" \
|
||||
'{
|
||||
out_dir:$out_dir,
|
||||
spec:$spec,
|
||||
max_iters:$max_iters,
|
||||
overall_ready:($overall_ready==1)
|
||||
}' > "$OUT_DIR/00_summary.json"
|
||||
|
||||
echo "Production completion loop output: $OUT_DIR"
|
||||
cat "$OUT_DIR/00_summary.json"
|
||||
|
||||
Reference in New Issue
Block a user