Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ A minimal vBRIEF document has just four fields:

```json
{
"vBRIEFInfo": { "version": "0.5" },
"vBRIEFInfo": { "version": "0.7" },
"plan": {
"title": "My First Plan",
"status": "running",
Expand Down Expand Up @@ -47,12 +47,12 @@ Start simple. Add structure only when you need it.

| Document | Description |
|----------|-------------|
| [docs/vbrief-spec-0.5.md](docs/vbrief-spec-0.5.md) | Formal specification (RFC 2119) |
| [docs/vbrief-spec-0.7.md](docs/vbrief-spec-0.7.md) | Formal specification v0.7 (RFC 2119) |
| [docs/GUIDE.md](docs/GUIDE.md) | Reference guide with patterns and recipes |
| [docs/getting-started.md](docs/getting-started.md) | Tutorial for beginners |
| [docs/tron-encoding.md](docs/tron-encoding.md) | TRON format reference |
| [docs/vbrief-workflow-profile.md](docs/vbrief-workflow-profile.md) | Workflow Profile extension (flow-based programming) |
| [docs/MIGRATION.md](docs/MIGRATION.md) | v0.4 → v0.5 migration guide |
| [docs/MIGRATION.md](docs/MIGRATION.md) | Migration guide |
| [libvbrief-ts/README.md](libvbrief-ts/README.md) | TypeScript package usage and examples |

## Repo Structure
Expand Down Expand Up @@ -84,7 +84,7 @@ npm install ./libvbrief-ts
From a fresh clone:

```bash
git clone https://github.com/visionik/vBRIEF.git
git clone https://github.com/deftai/vBRIEF.git
cd vBRIEF
pip install -e .
npm install ./libvbrief-ts
Expand All @@ -98,7 +98,7 @@ TypeScript parsing and validation:
import { loads, validate } from "libvbrief-ts";

const document = loads(`{
"vBRIEFInfo": { "version": "0.5" },
"vBRIEFInfo": { "version": "0.7" },
"plan": {
"title": "Release Checklist",
"status": "running",
Expand Down Expand Up @@ -137,7 +137,7 @@ python validation/vbrief_validator.py your-plan.vbrief.json

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md). Feedback and issues welcome at [GitHub Issues](https://github.com/visionik/vBRIEF/issues).
See [CONTRIBUTING.md](CONTRIBUTING.md). Feedback and issues welcome at [GitHub Issues](https://github.com/deftai/vBRIEF/issues).

## License

Expand Down
663 changes: 663 additions & 0 deletions docs/vbrief-spec-0.7.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion examples/construction-project-gantt.vbrief.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"vBRIEFInfo": {
"version": "0.5",
"version": "0.7",
"created": "2026-02-07T04:48:00Z"
},
"plan": {
Expand Down
2 changes: 1 addition & 1 deletion examples/dag-plan.vbrief.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"vBRIEFInfo": {
"version": "0.5",
"version": "0.7",
"created": "2026-02-03T09:00:00Z"
},
"plan": {
Expand Down
2 changes: 1 addition & 1 deletion examples/invalid-cycle.vbrief.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"vBRIEFInfo": {
"version": "0.5"
"version": "0.7"
},
"plan": {
"title": "Invalid Plan with Cycle",
Expand Down
2 changes: 1 addition & 1 deletion examples/minimal-plan.vbrief.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"vBRIEFInfo": {
"version": "0.5"
"version": "0.7"
},
"plan": {
"title": "Daily Tasks",
Expand Down
2 changes: 1 addition & 1 deletion examples/prd.vbrief.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"vBRIEFInfo": {
"version": "0.5"
"version": "0.7"
},
"plan": {
"title": "SQLite UI PRD",
Expand Down
2 changes: 1 addition & 1 deletion examples/retrospective-plan.vbrief.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"vBRIEFInfo": {
"version": "0.5",
"version": "0.7",
"created": "2026-02-03T09:00:00Z"
},
"plan": {
Expand Down
2 changes: 1 addition & 1 deletion examples/rfc854.vbrief.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"vBRIEFInfo": {
"version": "0.5"
"version": "0.7"
},
"plan": {
"title": "RFC 854: TELNET Protocol Specification",
Expand Down
2 changes: 1 addition & 1 deletion examples/software-development-gantt.vbrief.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"vBRIEFInfo": {
"version": "0.5",
"version": "0.7",
"created": "2026-02-07T04:48:00Z"
},
"plan": {
Expand Down
2 changes: 1 addition & 1 deletion examples/structured-plan.vbrief.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"vBRIEFInfo": {
"version": "0.5",
"version": "0.7",
"created": "2026-02-03T09:00:00Z"
},
"plan": {
Expand Down
2 changes: 1 addition & 1 deletion examples/workflow-invoice-processing.vbrief.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"vBRIEFInfo": { "version": "0.5" },
"vBRIEFInfo": { "version": "0.7" },
"plan": {
"id": "wf:invoice-processing-v4",
"title": "Invoice Processing with Error Handling",
Expand Down
5 changes: 5 additions & 0 deletions libvbrief/compat/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Compatibility helpers and policy constants."""

from libvbrief.compat.policy import (
EXTENSION_PROPERTY_PATTERN,
HIERARCHICAL_ID_PATTERN,
ISSUE_DAG_CYCLE,
ISSUE_DANGLING_EDGE_REF,
Expand All @@ -10,7 +11,9 @@
ISSUE_INVALID_ID_FORMAT,
ISSUE_INVALID_ITEM_STATUS,
ISSUE_INVALID_ITEM_TYPE,
ISSUE_INVALID_ITEM_TYPE_VALUE,
ISSUE_INVALID_PLANREF,
ISSUE_INVALID_PLANREFS,
ISSUE_INVALID_PLAN_FIELD_TYPE,
ISSUE_INVALID_PLAN_STATUS,
ISSUE_INVALID_ROOT_FIELD_TYPE,
Expand All @@ -20,7 +23,9 @@
ISSUE_MISSING_PLAN_FIELD,
ISSUE_MISSING_ROOT_FIELD,
PLAN_REF_PATTERN,
VALID_ITEM_TYPES,
VALID_STATUSES,
VALID_VERSIONS,
)

__all__ = [
Expand Down
21 changes: 21 additions & 0 deletions libvbrief/compat/policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,28 @@
"running",
"completed",
"blocked",
"failed",
"cancelled",
"auto",
}

VALID_ITEM_TYPES: Final[set[str]] = {
"task",
Comment on lines 13 to +22

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 "auto" in VALID_STATUSES makes plan.status = "auto" pass validation

VALID_STATUSES is shared between plan-level and item-level status checks. Adding "auto" here means _validate_plan (validation.py line 109) will accept plan.status = "auto", which has no defined semantics at the Plan level and directly violates the spec's constraint that "auto" is valid only on container PlanItems (group, milestone, epic) with children.

Additionally, no validation enforces the per-item constraint (MUST NOT on type: "task" or childless items), but the plan-level bleed-through is the most concrete present defect since it produces no error on clearly invalid documents.

Prompt To Fix With AI
This is a comment left during a code review.
Path: libvbrief/compat/policy.py
Line: 13-22

Comment:
**`"auto"` in `VALID_STATUSES` makes `plan.status = "auto"` pass validation**

`VALID_STATUSES` is shared between plan-level and item-level status checks. Adding `"auto"` here means `_validate_plan` (`validation.py` line 109) will accept `plan.status = "auto"`, which has no defined semantics at the Plan level and directly violates the spec's constraint that `"auto"` is valid **only on container PlanItems** (`group`, `milestone`, `epic`) with children.

Additionally, no validation enforces the per-item constraint (`MUST NOT` on `type: "task"` or childless items), but the plan-level bleed-through is the most concrete present defect since it produces no error on clearly invalid documents.

How can I resolve this? If you propose a fix, please make it concise.

"group",
"milestone",
"epic",
}

VALID_VERSIONS: Final[set[str]] = {
"0.5",
"0.6",
"0.7",
}

EXTENSION_PROPERTY_PATTERN: Final[re.Pattern[str]] = re.compile(
r"^x-[a-z0-9-]+/"
)

HIERARCHICAL_ID_PATTERN: Final[re.Pattern[str]] = re.compile(
r"^[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)*$"
)
Expand Down Expand Up @@ -43,3 +62,5 @@
ISSUE_INVALID_EDGE_STRUCTURE: Final[str] = "invalid_edge_structure"
ISSUE_DANGLING_EDGE_REF: Final[str] = "dangling_edge_ref"
ISSUE_DAG_CYCLE: Final[str] = "dag_cycle"
ISSUE_INVALID_ITEM_TYPE_VALUE: Final[str] = "invalid_item_type_value"
ISSUE_INVALID_PLANREFS: Final[str] = "invalid_planrefs"
23 changes: 22 additions & 1 deletion libvbrief/models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Dataclass object model for vBRIEF v0.5 documents."""
"""Dataclass object model for vBRIEF v0.7 documents."""

from __future__ import annotations

Expand All @@ -13,11 +13,15 @@
_PLAN_ITEM_FIELD_ORDER = [
"id",
"uid",
"type",
"summary",
"title",
"status",
"narrative",
"items",
"subItems",
"planRef",
"planRefs",
"tags",
"metadata",
"created",
Expand Down Expand Up @@ -76,9 +80,13 @@ class PlanItem:
status: str = ""
id: Any = None
uid: Any = None
type: Any = None
summary: Any = None
narrative: Any = None
items: list[PlanItem] = field(default_factory=list)
subItems: list[PlanItem] = field(default_factory=list)
planRef: Any = None
planRefs: Any = None
tags: Any = None
metadata: Any = None
created: Any = None
Expand Down Expand Up @@ -141,10 +149,13 @@ def from_dict(cls, data: Mapping[str, Any]) -> PlanItem:
item = cls(
id=data.get("id"),
uid=data.get("uid"),
type=data.get("type"),
summary=data.get("summary"),
title=data.get("title", ""),
status=data.get("status", ""),
narrative=data.get("narrative"),
planRef=data.get("planRef"),
planRefs=data.get("planRefs"),
tags=data.get("tags"),
metadata=data.get("metadata"),
created=data.get("created"),
Expand All @@ -170,6 +181,10 @@ def from_dict(cls, data: Mapping[str, Any]) -> PlanItem:
_field_order=list(data.keys()),
)

items_field = data.get("items")
if isinstance(items_field, list):
item.items = [cls.from_dict(x) for x in items_field if isinstance(x, Mapping)]

sub_items = data.get("subItems")
if isinstance(sub_items, list):
# Non-Mapping entries are intentionally skipped (lenient parse);
Expand Down Expand Up @@ -376,11 +391,17 @@ def _known_item_values(item: PlanItem, *, preserve_order: bool) -> dict[str, Any
optional_pairs = {
"id": item.id,
"uid": item.uid,
"type": item.type,
"summary": item.summary,
"narrative": item.narrative,
"items": [sub.to_dict(preserve_order=preserve_order) for sub in item.items]
if item.items
else None,
"subItems": [sub.to_dict(preserve_order=preserve_order) for sub in item.subItems]
if item.subItems
else None,
"planRef": item.planRef,
"planRefs": item.planRefs,
"tags": item.tags,
"metadata": item.metadata,
"created": item.created,
Expand Down
35 changes: 32 additions & 3 deletions libvbrief/validation.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Core conformance validation for vBRIEF v0.5 JSON documents."""
"""Core conformance validation for vBRIEF v0.7 JSON documents."""

from __future__ import annotations

Expand All @@ -11,7 +11,9 @@
ISSUE_INVALID_ID_FORMAT,
ISSUE_INVALID_ITEM_STATUS,
ISSUE_INVALID_ITEM_TYPE,
ISSUE_INVALID_ITEM_TYPE_VALUE,
ISSUE_INVALID_PLANREF,
ISSUE_INVALID_PLANREFS,
ISSUE_INVALID_PLAN_FIELD_TYPE,
ISSUE_INVALID_PLAN_STATUS,
ISSUE_INVALID_ROOT_FIELD_TYPE,
Expand All @@ -21,7 +23,9 @@
ISSUE_MISSING_PLAN_FIELD,
ISSUE_MISSING_ROOT_FIELD,
PLAN_REF_PATTERN,
VALID_ITEM_TYPES,
VALID_STATUSES,
VALID_VERSIONS,
)
from libvbrief.issues import ValidationReport

Expand Down Expand Up @@ -73,11 +77,11 @@ def _validate_root(data: Mapping[str, Any], report: ValidationReport) -> None:

if vbrief_info is not None:
version = vbrief_info.get("version")
if version != "0.5":
if version not in VALID_VERSIONS:
report.add_error(
ISSUE_INVALID_VERSION,
"vBRIEFInfo.version",
f"Expected version '0.5', got {version!r}",
f"Expected version in {sorted(VALID_VERSIONS)}, got {version!r}",
)

if "plan" not in data:
Expand Down Expand Up @@ -188,6 +192,14 @@ def _validate_items(
else:
seen_ids.add(item_id)

item_type = item.get("type")
if item_type is not None and item_type not in VALID_ITEM_TYPES:
report.add_error(
ISSUE_INVALID_ITEM_TYPE_VALUE,
f"{item_path}.type",
f"Invalid item type {item_type!r}; expected one of {sorted(VALID_ITEM_TYPES)}",
)

plan_ref = item.get("planRef")
if plan_ref is not None and (not isinstance(plan_ref, str) or not PLAN_REF_PATTERN.match(plan_ref)):
report.add_error(
Expand All @@ -196,6 +208,23 @@ def _validate_items(
"planRef must match #..., file://..., or https://...",
)

plan_refs = item.get("planRefs")
if plan_refs is not None:
if not isinstance(plan_refs, list):
report.add_error(
ISSUE_INVALID_PLANREFS,
f"{item_path}.planRefs",
"planRefs must be an array",
)
else:
for idx, ref in enumerate(plan_refs):
if not isinstance(ref, str) or not PLAN_REF_PATTERN.match(ref):
report.add_error(
ISSUE_INVALID_PLANREFS,
f"{item_path}.planRefs[{idx}]",
"planRefs entries must match #..., file://..., or https://...",
)

sub_items = item.get("subItems")
if sub_items is None:
continue
Expand Down
Loading
Loading