Start sprint 232 semantic planning annotation bridge

This commit is contained in:
Bill
2026-02-26 14:28:16 -07:00
parent 9198a70d35
commit 815abc3b65
7 changed files with 273 additions and 0 deletions

View File

@@ -0,0 +1,206 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
import re
from pathlib import Path
from typing import Dict, List
def first_heading(text: str) -> str:
for line in text.splitlines():
s = line.strip()
if s.startswith("#"):
return s.lstrip("#").strip()
return ""
def collect_bullets(text: str, limit: int = 12) -> List[str]:
out: List[str] = []
for line in text.splitlines():
s = line.strip()
if s.startswith("- "):
out.append(s[2:].strip())
if len(out) >= limit:
break
return out
def infer_complexity(text: str) -> Dict[str, object]:
lower = text.lower()
score = 0
markers = {
"multi_file": ["multi-file", "multi file", "artifact", "cross-file"],
"concurrency": ["concurrency", "thread", "lock", "mutex", "async"],
"migration": ["migration", "rollback", "backfill"],
"fullstack": ["fullstack", "frontend", "backend", "openapi", "sdk"],
"projection": ["projection", "target", "cross-target", "environment"],
}
active: List[str] = []
for k, pats in markers.items():
if any(p in lower for p in pats):
score += 1
active.append(k)
level = "low"
if score >= 4:
level = "high"
elif score >= 2:
level = "medium"
return {"level": level, "score": score, "markers": active}
def infer_risks(text: str) -> List[str]:
lower = text.lower()
risks: List[str] = []
if any(k in lower for k in ["rollback", "migration", "data_loss", "backfill"]):
risks.append("data_integrity")
if any(k in lower for k in ["security", "auth", "deny_by_default", "permission"]):
risks.append("security_regression")
if any(k in lower for k in ["p95", "latency", "throughput", "slo", "performance"]):
risks.append("performance_regression")
if any(k in lower for k in ["concurrency", "thread", "race", "lock"]):
risks.append("concurrency_bug")
if any(k in lower for k in ["compatibility", "backward", "deprecation", "rollout"]):
risks.append("compatibility_break")
return sorted(set(risks))
def extract_contract_fields(text: str) -> Dict[str, object]:
lines = text.splitlines()
artifacts: List[str] = []
budgets: List[str] = []
for line in lines:
s = line.strip()
if not s:
continue
if re.search(r"[A-Za-z0-9_./*-]+", s) and any(tok in s for tok in ["/", "*.", ".md", ".yaml", ".sql", ".go", ".rs", ".cpp", ".py"]):
if s.startswith("-"):
artifacts.append(s.lstrip("- ").strip())
if re.search(r"\b\d+\s*(ms|s|sec|mb|gb|rps|qps|%)\b", s, re.IGNORECASE):
budgets.append(s)
constraints: List[str] = []
lower = text.lower()
for c in ["rollback_required", "deny_by_default", "staged", "auto_abort", "deterministic", "replay"]:
if c in lower:
constraints.append(c)
return {
"required_artifacts": artifacts[:24],
"budget_clauses": budgets[:24],
"constraints": constraints,
}
def infer_tags(text: str) -> List[str]:
lower = text.lower()
tags: List[str] = []
mapping = {
"@network": ["api", "http", "gateway", "service"],
"@io": ["file", "storage", "database", "migration"],
"@concurrency": ["thread", "async", "worker", "queue", "lock"],
"@crypto": ["crypto", "encrypt", "hash", "sign"],
"@test": ["test", "acceptance", "validation"],
"@ui": ["ui", "frontend", "compose", "react"],
}
for tag, keys in mapping.items():
if any(k in lower for k in keys):
tags.append(tag)
return sorted(set(tags))
def infer_capability_requirements(text: str) -> List[Dict[str, str]]:
lower = text.lower()
reqs: List[Dict[str, str]] = []
if "arduino" in lower or "embedded" in lower:
reqs.append({"capability": "embedded_profile", "reason": "embedded target constraints detected"})
if "fullstack" in lower or ("frontend" in lower and "backend" in lower):
reqs.append({"capability": "fullstack_contract_propagation", "reason": "cross-layer requirements detected"})
if "migration" in lower or "rollback" in lower:
reqs.append({"capability": "state_migration_guardrails", "reason": "migration/rollback requirements detected"})
if "concurrency" in lower or "thread" in lower or "lock" in lower:
reqs.append({"capability": "concurrency_safety", "reason": "concurrency semantics detected"})
return reqs
def build_packet(path: Path) -> Dict[str, object]:
text = path.read_text(encoding="utf-8")
heading = first_heading(text)
bullets = collect_bullets(text)
complexity = infer_complexity(text)
risks = infer_risks(text)
contract = extract_contract_fields(text)
tags = infer_tags(text)
caps = infer_capability_requirements(text)
annotations: List[Dict[str, object]] = [
{
"type": "intent",
"fields": {
"summary": heading if heading else "spec_without_heading",
"key_points": bullets[:8],
},
},
{
"type": "complexity",
"fields": complexity,
},
{
"type": "risk",
"fields": {
"risks": risks,
"risk_level": "high" if len(risks) >= 3 else ("medium" if len(risks) >= 1 else "low"),
},
},
{
"type": "contract",
"fields": contract,
},
{
"type": "tags",
"fields": {
"tags": tags,
},
},
]
for req in caps:
annotations.append({"type": "capability_requirement", "fields": req})
required_types = {"intent", "complexity", "risk", "contract", "tags"}
present_types = {a["type"] for a in annotations}
missing_types = sorted(required_types - present_types)
coverage = {
"required_types": sorted(required_types),
"present_types": sorted(present_types),
"missing_types": missing_types,
"coverage_ratio": round((len(required_types) - len(missing_types)) / len(required_types), 4),
}
return {
"source_file": str(path),
"packet_version": "planning_semantic_packet_v1",
"annotations": annotations,
"coverage": coverage,
}
def main() -> None:
ap = argparse.ArgumentParser(description="Convert markdown plan/spec into semantic planning annotation packet.")
ap.add_argument("--spec", required=True)
ap.add_argument("--out", required=True)
args = ap.parse_args()
packet = build_packet(Path(args.spec))
out = Path(args.out)
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(json.dumps(packet, indent=2) + "\n", encoding="utf-8")
print(json.dumps(packet, indent=2))
if __name__ == "__main__":
main()

View File

@@ -24,6 +24,7 @@ RUN_READINESS_SUITE="${WSTONE_RUN_READINESS_SUITE:-1}"
SPEC_READINESS_PRECHECK="${WSTONE_SPEC_READINESS_PRECHECK:-1}"
SPEC_READINESS_HARD_GATE="${WSTONE_SPEC_READINESS_HARD_GATE:-0}"
SPEC_READINESS_MIN_SCORE="${WSTONE_SPEC_READINESS_MIN_SCORE:-65}"
SEMANTIC_PLANNING_BRIDGE="${WSTONE_SEMANTIC_PLANNING_BRIDGE:-1}"
CAPABILITY_SIGNALS_JSON="${WSTONE_CAPABILITY_SIGNALS_JSON:-}"
if [[ -z "$CAPABILITY_SIGNALS_JSON" ]]; then
CAPABILITY_SIGNALS_JSON='{}'
@@ -70,6 +71,14 @@ if [[ "$SPEC_READINESS_PRECHECK" == "1" ]]; then
fi
fi
SEMANTIC_PLANNING_JSON='{}'
if [[ "$SEMANTIC_PLANNING_BRIDGE" == "1" ]]; then
python3 "$ROOT_DIR/tools/mcp/markdown_to_semantic_annotations.py" \
--spec "$INPUT_FILE" \
--out "$OUT_DIR/00b_semantic_planning_annotations.json" >/dev/null
SEMANTIC_PLANNING_JSON="$(cat "$OUT_DIR/00b_semantic_planning_annotations.json")"
fi
call_tool() {
local tool_name="$1"
local args_json="$2"
@@ -240,6 +249,7 @@ SUMMARY_JSON="$(jq -nc \
--arg bin "$BIN" \
--arg workspace "$WORKSPACE" \
--argjson planning_readiness "$SPEC_READINESS_JSON" \
--argjson semantic_planning "$SEMANTIC_PLANNING_JSON" \
--argjson intake "$INTAKE_JSON" \
--argjson generated "$GEN_JSON" \
--argjson queue "$QUEUE_JSON" \
@@ -252,6 +262,7 @@ SUMMARY_JSON="$(jq -nc \
workspace: $workspace,
output_dir: $outDir,
planning_readiness: $planning_readiness,
semantic_planning_annotations: $semantic_planning,
intake: {
success: ($intake.success // false),
normalized_requirement_count: (($intake.normalizedRequirements // [])|length),