diff --git a/NEXT_AGENT_NOTES.md b/NEXT_AGENT_NOTES.md index ca40c02..39a148b 100644 --- a/NEXT_AGENT_NOTES.md +++ b/NEXT_AGENT_NOTES.md @@ -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 diff --git a/docs/progress_log_2026-02-26.md b/docs/progress_log_2026-02-26.md index d71b44c..7930145 100644 --- a/docs/progress_log_2026-02-26.md +++ b/docs/progress_log_2026-02-26.md @@ -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` diff --git a/docs/sprint232_execution_tracker_2026-02-26.md b/docs/sprint232_execution_tracker_2026-02-26.md new file mode 100644 index 0000000..aae3aaa --- /dev/null +++ b/docs/sprint232_execution_tracker_2026-02-26.md @@ -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). diff --git a/editor/src/Sprint232IntegrationSummary.h b/editor/src/Sprint232IntegrationSummary.h new file mode 100644 index 0000000..9f63b5c --- /dev/null +++ b/editor/src/Sprint232IntegrationSummary.h @@ -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. diff --git a/sprint232_plan.md b/sprint232_plan.md new file mode 100644 index 0000000..532d0aa --- /dev/null +++ b/sprint232_plan.md @@ -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`. diff --git a/tools/mcp/markdown_to_semantic_annotations.py b/tools/mcp/markdown_to_semantic_annotations.py new file mode 100755 index 0000000..c92e543 --- /dev/null +++ b/tools/mcp/markdown_to_semantic_annotations.py @@ -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() diff --git a/tools/mcp/run_sprint_taskitem_pipeline.sh b/tools/mcp/run_sprint_taskitem_pipeline.sh index af3485e..3ffb9f9 100755 --- a/tools/mcp/run_sprint_taskitem_pipeline.sh +++ b/tools/mcp/run_sprint_taskitem_pipeline.sh @@ -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),