Start sprint 232 semantic planning annotation bridge
This commit is contained in:
@@ -20,9 +20,11 @@
|
||||
- `logs/taskitem_runs/challenging_fullstack_multifile_20260226_r7/summary.json`
|
||||
- First-class planning tranche (new):
|
||||
- `docs/sprint228_231_execution_tracker_2026-02-26.md`
|
||||
- `docs/sprint232_execution_tracker_2026-02-26.md`
|
||||
- `tools/mcp/spec_planning_readiness.py`
|
||||
- `tools/mcp/spec_planning_hardener.py`
|
||||
- `tools/mcp/run_spec_hardening_gate.sh`
|
||||
- `tools/mcp/markdown_to_semantic_annotations.py`
|
||||
- `docs/spec_hardening_batch_report_2026-02-26.md`
|
||||
|
||||
## Dated Handoff
|
||||
|
||||
@@ -75,3 +75,13 @@
|
||||
- Artifact isolation update:
|
||||
- moved planning/hardening outputs to `logs/taskitem_runs/TEST_ONLY_*` paths.
|
||||
- wrapper default now writes to `TEST_ONLY_spec_hardening_gate_*`.
|
||||
|
||||
## Sprint 232 Added (Same Day)
|
||||
|
||||
- Added semantic planning bridge:
|
||||
- `tools/mcp/markdown_to_semantic_annotations.py`
|
||||
- Integrated into taskitem pipeline:
|
||||
- `tools/mcp/run_sprint_taskitem_pipeline.sh`
|
||||
- env toggle: `WSTONE_SEMANTIC_PLANNING_BRIDGE` (default `1`)
|
||||
- emits `00b_semantic_planning_annotations.json`
|
||||
- summary now includes `semantic_planning_annotations`
|
||||
|
||||
27
docs/sprint232_execution_tracker_2026-02-26.md
Normal file
27
docs/sprint232_execution_tracker_2026-02-26.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Sprint 232 Execution Tracker - 2026-02-26
|
||||
|
||||
## Scope
|
||||
- `sprint232_plan.md`
|
||||
|
||||
## Implemented
|
||||
|
||||
- Added semantic planning bridge tool:
|
||||
- `tools/mcp/markdown_to_semantic_annotations.py`
|
||||
- Pipeline integration:
|
||||
- `tools/mcp/run_sprint_taskitem_pipeline.sh`
|
||||
- new env control: `WSTONE_SEMANTIC_PLANNING_BRIDGE` (default `1`)
|
||||
- emits `00b_semantic_planning_annotations.json`
|
||||
- summary includes `semantic_planning_annotations`
|
||||
|
||||
## Baseline Artifact
|
||||
|
||||
- `logs/taskitem_runs/TEST_ONLY_sprint232_semantic_bridge_20260226/semantic_packet.json`
|
||||
|
||||
## Explicit Completion Signal
|
||||
|
||||
- Sprint 232: `DONE` (implemented + integrated + baseline artifact generated)
|
||||
|
||||
## Next
|
||||
|
||||
- Bind semantic packet classes to stronger taskitem decomposition decisions.
|
||||
- Add semantic coverage gates (required annotation classes by project category).
|
||||
5
editor/src/Sprint232IntegrationSummary.h
Normal file
5
editor/src/Sprint232IntegrationSummary.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
// Sprint 232 integration summary:
|
||||
// - Added markdown-to-semantic-planning annotation bridge for spec preprocessing.
|
||||
// - Integrated semantic planning packet output into sprint taskitem pipeline summaries.
|
||||
12
sprint232_plan.md
Normal file
12
sprint232_plan.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Sprint 232 Plan: Semantic Annotation Bridge For Planning
|
||||
|
||||
## Goal
|
||||
Make semantic annotations first-class in spec-to-taskitem planning by bridging markdown specs into typed semantic planning packets.
|
||||
|
||||
## Steps
|
||||
- Step 2199: Add `markdown_to_semantic_annotations` bridge tool.
|
||||
- Step 2200: Emit semantic annotation packet types aligned with existing semantic annotation vocabulary (`intent`, `complexity`, `risk`, `contract`, `tags`, `capability_requirement`).
|
||||
- Step 2201: Integrate bridge output into sprint taskitem pipeline summary artifacts.
|
||||
- Step 2202: Add env control to enable/disable semantic planning bridge in pipeline.
|
||||
- Step 2203: Produce baseline packet artifacts and update tracker.
|
||||
- Step 2204: Add `Sprint232IntegrationSummary.h`.
|
||||
206
tools/mcp/markdown_to_semantic_annotations.py
Executable file
206
tools/mcp/markdown_to_semantic_annotations.py
Executable 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()
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user