diff --git a/docs/verification.md b/docs/verification.md index e253050..f7d4e93 100644 --- a/docs/verification.md +++ b/docs/verification.md @@ -255,8 +255,10 @@ cycle and parses one explicit criteria section. Accepted headings are exactly Markdown heading (`#` through `######`) or bold label. A trailing colon or period is allowed inside a bold label or after it. The section is a flat list at column zero using `-`, `+`, `*`, `1.` or `1)` markers and optional Markdown -checkboxes; another heading or bold label ends it. Empty, missing, duplicate, -placeholder or unparseable sections produce deterministic `needs_refinement` +checkboxes; another heading or a bold label ends it. Punctuated labels may +include prose on the same line, such as `**Label.** text` or `**Label:** text`. +Bold markup inside a list item does not end the section. Empty, missing, +duplicate, placeholder or unparseable sections produce deterministic `needs_refinement` before any implementation, even with issue review off or difficulty 1. They are insufficient specification, never a failed test. Existing issues are not migrated. `.github/ISSUE_TEMPLATE/work-item.md` supplies the grammar for new work. diff --git a/scripts/verification_plan.py b/scripts/verification_plan.py index cf28e7e..b906e27 100644 --- a/scripts/verification_plan.py +++ b/scripts/verification_plan.py @@ -55,7 +55,7 @@ def from_dict(cls, value: Mapping[str, Any]) -> Criteria: _LABEL = r"(?:Acceptance criteria|Criterios de aceptación)" _HEADING = re.compile(rf"^\s*(?:#{{1,6}}\s+({_LABEL})\s*[:.]?\s*#*|\*\*({_LABEL})[:.]?\*\*[:.]?)\s*$", re.I) _ITEM = re.compile(r"^(?:[-+*]|\d+[.)])\s+(?:\[[ xX]\]\s+)?(\S.*)$") -_SECTION = re.compile(r"^\s*(?:#{1,6}\s+|\*\*[^*]+\*\*[:.]?\s*$)") +_SECTION = re.compile(r"^\s*(?:#{1,6}\s+|\*\*[^*]+\*\*[:.]?\s*$|\*\*[^*]+[.:]\*\*[:.]?\s+\S.*$)") def parse_criteria(provider: str, body: str | None) -> Criteria: diff --git a/tests/fixtures/issue-330.md b/tests/fixtures/issue-330.md new file mode 100644 index 0000000..34cc1b9 --- /dev/null +++ b/tests/fixtures/issue-330.md @@ -0,0 +1,38 @@ +**Prioridad:** P0. **Esfuerzo:** M. + +**Problema y justificación.** Si Orca interpreta perfiles y condiciones por separado, aparecerían dos políticas de ciclo con el mismo nombre. + +**Objetivo y alcance.** Consumir el mismo plan, resolver y validadores en la orquestación supervisada de Orca. + +**Componentes.** `cc-orca-orchestrator`, `OrcaDispatchContext`, `OrcaAdapter`, helpers de planificación y tests de instalación. + +**Orientación técnica.** Conservar Run, Task, worker y receipts de Orca. El adapter consume esas identidades; no las inventa. Las decisiones de política vienen del componente común. + +**Dependencia estricta.** #329. + +**Criterios de aceptación:** + +- Entradas equivalentes producen obligaciones equivalentes en Orca y nativos. +- El perfil efectivo y la revisión del plan viajan con la delegación. +- Un receipt de lanzamiento nunca se acepta como resultado funcional. +- Se mantienen workspaces de review y permisos de publicación. +- Cambios de HEAD y resultados parciales siguen las mismas reglas de invalidez. +- Los cinco modos tienen ejemplos y resultados terminales documentados. +- Versiones de Orca y limitaciones verificadas se registran en la evidencia. +- Rereview permanece condicional en todos los modos: sin modificaciones posteriores relevantes ni evidencia invalidada no se repite, tampoco en Full. Hallazgos abiertos sin correcciones mantienen sus bloqueos; Orca no lanza otra revisión idéntica para intentar otro resultado. Los cambios posteriores reactivan solo los controles cuya evidencia deba reevaluarse. + +**Tests.** Fixtures equivalentes entre transportes; errores de contexto, lanzamiento sin finalización, resume, cancelación y al menos una campaña controlada con Orca real. Incluir paridad de selección/omisión de rereview en los cinco modos. + +**Riesgos.** Suposiciones sobre capacidades no disponibles en la versión instalada. + +**Fuera de alcance.** Sustituir Orca, crear un coordinador nuevo, renderers o protocolo remoto universal. + +## Trazabilidad del roadmap aprobado + +**Referencia estratégica:** [docs/ROADMAP_V2.md](https://github.com/datacas/code-cycle-toolkit/blob/35b9f9cdced8d5da549d9a84290a4b260243a0df/docs/ROADMAP_V2.md) (versión aprobada; la ruta canónica se conserva en el índice del repositorio). + +**Bloqueos estrictos:** #329 + +Esta issue materializa la planificación aprobada el 26 de octubre de 2026; su existencia no acredita implementación. Las recomendaciones y condiciones no son bloqueos generales. No autoriza iniciar funcionalidades durante la materialización del roadmap. + + diff --git a/tests/test_verification_plan.py b/tests/test_verification_plan.py index 4f2dde1..c9f0b95 100644 --- a/tests/test_verification_plan.py +++ b/tests/test_verification_plan.py @@ -55,6 +55,40 @@ def test_closed_grammar_english_spanish_heading_bold_lists(self): self.assertEqual(parsed.status, "evaluable") self.assertEqual(parsed.items, ("Works",)) + def test_inline_bold_section_label_ends_criteria(self): + for label in ("**Tests.** Fixtures cover the parser.", + "**Tests:** Fixtures cover the parser."): + with self.subTest(label=label): + parsed = criteria(f"## Acceptance criteria\n- A\n- B\n\n{label}\nOther prose") + self.assertEqual(parsed.status, "evaluable") + self.assertEqual(parsed.items, ("A", "B")) + + def test_malformed_criteria_remain_needs_refinement(self): + for body, reason in ( + ("## Acceptance criteria\nprose", "criteria must be a flat Markdown list"), + ("## Acceptance criteria\n- Works\n**Prose** is still prose.", + "criteria must be a flat Markdown list"), + ("## Acceptance criteria\n- Works\n - nested", "criteria must be a flat Markdown list"), + ("## Acceptance criteria\n- TODO", "placeholder criterion"), + ("## Acceptance criteria\n", "acceptance-criteria list is empty"), + ): + with self.subTest(body=body): + parsed = criteria(body) + self.assertEqual(parsed.status, "needs_refinement") + self.assertEqual(parsed.reason, reason) + + def test_bold_markup_inside_criterion_does_not_end_section(self): + parsed = criteria("## Acceptance criteria\n- Keeps **bold markup** inside the criterion\n" + "**Tests.** Regression coverage follows.") + self.assertEqual(parsed.status, "evaluable") + self.assertEqual(parsed.items, ("Keeps **bold markup** inside the criterion",)) + + def test_issue_330_body_is_evaluable_without_remote_edit(self): + body = (ROOT / "tests" / "fixtures" / "issue-330.md").read_text(encoding="utf-8") + parsed = criteria(body) + self.assertEqual(parsed.status, "evaluable") + self.assertEqual(len(parsed.items), 8) + def test_missing_empty_duplicate_unparseable_and_placeholder_stay_open(self): for body in ("## Problem\nWorks", "## Acceptance criteria\n", "## Acceptance criteria\nprose", "## Acceptance criteria\n- Works\n - nested",