docs(roadmap): add Apple-palette mermaid roadmap and the roadmap gate
Follow the mihaela-agents mermaid rule: add a README roadmap using the TileDown Apple-color status classes (done/review/epic/todo), legend block first, then a libxml2-parity roadmap showing shipped vs open work. Add docs/rules/mermaid.md and scripts/check-roadmap.sh (wired into check-all.sh) so the diagrams stay mechanically checkable.
This commit is contained in:
parent
e4b2e0127d
commit
0f3086c8e6
50
README.md
50
README.md
@ -19,6 +19,56 @@ intentionally strict about portability:
|
||||
It is a sibling project to [PureYAML](https://github.com/mihaelamj/PureYAML) and
|
||||
follows the same structure, rules, and verification gates.
|
||||
|
||||
## Roadmap
|
||||
|
||||
The roadmap uses the TileDown Mermaid palette: green for shipped work, yellow for
|
||||
review, purple for epic grouping, and gray for open work.
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
classDef done fill:#ddf9e4,stroke:#34c759,color:#111827
|
||||
classDef review fill:#fff7d6,stroke:#ffcc00,color:#111827
|
||||
classDef epic fill:#f2e5ff,stroke:#af52de,color:#111827
|
||||
classDef todo fill:#f2f4f7,stroke:#8e8e93,color:#111827
|
||||
LDone["In main now"]:::done
|
||||
LReview["PR in review"]:::review
|
||||
LEpic["Epic grouping"]:::epic
|
||||
LTodo["Open issue, no PR"]:::todo
|
||||
LDone --> LReview
|
||||
LReview --> LEpic
|
||||
LEpic --> LTodo
|
||||
```
|
||||
|
||||
Capability is being grown toward libxml2 parity in pure Swift, guided by the
|
||||
private PureXML-research analysis. Shipped vs open work:
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
classDef done fill:#ddf9e4,stroke:#34c759,color:#111827
|
||||
classDef review fill:#fff7d6,stroke:#ffcc00,color:#111827
|
||||
classDef epic fill:#f2e5ff,stroke:#af52de,color:#111827
|
||||
classDef todo fill:#f2f4f7,stroke:#8e8e93,color:#111827
|
||||
Model["Node model + emitter"]:::done
|
||||
Stream["Streaming pull parser"]:::done
|
||||
Escape["Iterative emitter + escaping"]:::done
|
||||
Limits["Bounded-by-default limits"]:::done
|
||||
Bytes["Byte input + encoding detection"]:::done
|
||||
Namespaces["Namespace resolution"]:::todo
|
||||
ByteStream["Streaming byte decode"]:::todo
|
||||
DTD["Optional DTD + entities"]:::todo
|
||||
Validation["Schema validation"]:::todo
|
||||
XPath["XPath 1.0"]:::todo
|
||||
Model --> Stream
|
||||
Stream --> Escape
|
||||
Escape --> Limits
|
||||
Limits --> Bytes
|
||||
Bytes --> Namespaces
|
||||
Namespaces --> ByteStream
|
||||
ByteStream --> DTD
|
||||
DTD --> Validation
|
||||
Validation --> XPath
|
||||
```
|
||||
|
||||
## Status
|
||||
|
||||
The node model, the emitter, and a streaming parser are implemented and usable
|
||||
|
||||
51
docs/rules/mermaid.md
Normal file
51
docs/rules/mermaid.md
Normal file
@ -0,0 +1,51 @@
|
||||
# Mermaid Rules
|
||||
|
||||
## Rule 1: Use the TileDown Mermaid palette
|
||||
|
||||
Every Mermaid roadmap diagram in this repository must use TileDown's Mermaid
|
||||
status classes and exact colors.
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
classDef done fill:#ddf9e4,stroke:#34c759,color:#111827
|
||||
classDef review fill:#fff7d6,stroke:#ffcc00,color:#111827
|
||||
classDef epic fill:#f2e5ff,stroke:#af52de,color:#111827
|
||||
classDef todo fill:#f2f4f7,stroke:#8e8e93,color:#111827
|
||||
Done["In main now"]:::done
|
||||
Review["PR in review"]:::review
|
||||
Epic["Epic grouping"]:::epic
|
||||
Todo["Open issue, no PR"]:::todo
|
||||
Done ~~~ Review
|
||||
Review ~~~ Epic
|
||||
Epic ~~~ Todo
|
||||
```
|
||||
|
||||
Allowed classes:
|
||||
|
||||
- `done`: merged or shipped work
|
||||
- `review`: pull request in review
|
||||
- `epic`: grouping issue or roadmap parent
|
||||
- `todo`: open work with no PR
|
||||
|
||||
Do not add ad-hoc Mermaid status colors. Add a new class only after TileDown's
|
||||
Mermaid palette adds the same class.
|
||||
|
||||
## Rule 2: Keep roadmap Mermaid mechanically checkable
|
||||
|
||||
Roadmap Mermaid blocks must keep `classDef` lines before nodes and edges. Every
|
||||
node must use one of the allowed classes, and every edge must reference nodes
|
||||
already defined in that block.
|
||||
|
||||
The README must start its Mermaid section with the shared legend. Run
|
||||
`bash scripts/check-roadmap.sh` after changing roadmap diagrams.
|
||||
|
||||
## Rule 3: Collapse completed epics
|
||||
|
||||
Completed epics must not be expanded into their child issues in Mermaid
|
||||
roadmaps. Once every child in an epic is `done`, remove that detailed Mermaid
|
||||
block and show the completed epic as one `done` node in its parent or
|
||||
super-epic roadmap.
|
||||
|
||||
Do not keep all-green detailed roadmap diagrams. The issue checklist can retain
|
||||
the historical child issue list, but Mermaid must stay focused on active or
|
||||
upcoming work.
|
||||
@ -18,6 +18,7 @@ run_gate() {
|
||||
run_gate "style" bash scripts/check-style.sh
|
||||
run_gate "namespacing" bash scripts/check-namespacing.sh
|
||||
run_gate "forbidden patterns" bash scripts/check-forbidden-patterns.sh
|
||||
run_gate "roadmap" bash scripts/check-roadmap.sh
|
||||
run_gate "swiftformat" swiftformat . --config .swiftformat --lint
|
||||
run_gate "swiftlint" swiftlint --config .swiftlint.yml --strict
|
||||
run_gate "swift build" swift build
|
||||
|
||||
211
scripts/check-roadmap.sh
Executable file
211
scripts/check-roadmap.sh
Executable file
@ -0,0 +1,211 @@
|
||||
#!/usr/bin/env bash
|
||||
# Mermaid roadmap gate. Keeps README roadmap diagrams vertical, legend-first,
|
||||
# TileDown-palette colored, and covered by live epic issues.
|
||||
# See docs/rules/mermaid.md.
|
||||
|
||||
set -u
|
||||
|
||||
README="README.md"
|
||||
FAIL=0
|
||||
|
||||
fail() {
|
||||
echo "roadmap: $1" >&2
|
||||
FAIL=1
|
||||
}
|
||||
|
||||
expected_classes() {
|
||||
cat <<'EOF'
|
||||
classDef done fill:#ddf9e4,stroke:#34c759,color:#111827
|
||||
classDef review fill:#fff7d6,stroke:#ffcc00,color:#111827
|
||||
classDef epic fill:#f2e5ff,stroke:#af52de,color:#111827
|
||||
classDef todo fill:#f2f4f7,stroke:#8e8e93,color:#111827
|
||||
EOF
|
||||
}
|
||||
|
||||
known_class() {
|
||||
case "$1" in
|
||||
done|review|epic|todo) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
extract_blocks() {
|
||||
awk -v dir="$1" '
|
||||
/^```mermaid$/ {
|
||||
inside = 1
|
||||
count += 1
|
||||
file = sprintf("%s/block-%03d.mmd", dir, count)
|
||||
next
|
||||
}
|
||||
/^```$/ {
|
||||
if (inside == 1) {
|
||||
inside = 0
|
||||
file = ""
|
||||
}
|
||||
next
|
||||
}
|
||||
inside == 1 {
|
||||
print > file
|
||||
}
|
||||
END {
|
||||
count_file = dir "/count"
|
||||
print count + 0 > count_file
|
||||
}
|
||||
' "$README"
|
||||
}
|
||||
|
||||
node_defined() {
|
||||
grep -Fxq "$1" "$2"
|
||||
}
|
||||
|
||||
validate_block() {
|
||||
block="$1"
|
||||
index="$2"
|
||||
defined_nodes="$3"
|
||||
expected="$4"
|
||||
defs="$5"
|
||||
|
||||
grep '^ classDef ' "$block" > "$defs" || true
|
||||
if ! diff -u "$expected" "$defs" >/dev/null; then
|
||||
fail "mermaid block $index must use the TileDown Mermaid color classDefs"
|
||||
fi
|
||||
|
||||
: > "$defined_nodes"
|
||||
saw_non_class_def=0
|
||||
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
trimmed=$(printf '%s' "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
||||
case "$trimmed" in
|
||||
""|"flowchart "*|"%%"*) continue ;;
|
||||
esac
|
||||
|
||||
case "$trimmed" in
|
||||
classDef\ *)
|
||||
if [ "$saw_non_class_def" -ne 0 ]; then
|
||||
fail "mermaid block $index has classDef after nodes or edges"
|
||||
fi
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
|
||||
saw_non_class_def=1
|
||||
|
||||
if [[ "$line" =~ ^[[:space:]]*([A-Za-z][A-Za-z0-9_]*)[[:space:]]*(\[.*\]|\(.*\)|\{.*\})[[:space:]]*:::[[:space:]]*([A-Za-z][A-Za-z0-9_]*)[[:space:]]*$ ]]; then
|
||||
node="${BASH_REMATCH[1]}"
|
||||
node_class="${BASH_REMATCH[3]}"
|
||||
if ! known_class "$node_class"; then
|
||||
fail "mermaid block $index uses unknown class $node_class"
|
||||
fi
|
||||
echo "$node" >> "$defined_nodes"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$line" =~ (--\>|~~~) ]]; then
|
||||
if [[ "$line" =~ ^[[:space:]]*([A-Za-z][A-Za-z0-9_]*)[[:space:]]*(--\>|~~~)[[:space:]]*([A-Za-z][A-Za-z0-9_]*)[[:space:]]*$ ]]; then
|
||||
left="${BASH_REMATCH[1]}"
|
||||
right="${BASH_REMATCH[3]}"
|
||||
if ! node_defined "$left" "$defined_nodes" || ! node_defined "$right" "$defined_nodes"; then
|
||||
fail "mermaid block $index has edge with undefined node: $left to $right"
|
||||
fi
|
||||
else
|
||||
fail "mermaid block $index has unsupported edge syntax: $trimmed"
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$line" =~ ^[[:space:]]*[A-Za-z][A-Za-z0-9_]*[[:space:]]*(\[.*\]|\(.*\)|\{.*\})[[:space:]]*$ ]]; then
|
||||
fail "mermaid block $index has unclassed node: $trimmed"
|
||||
fi
|
||||
done < "$block"
|
||||
|
||||
if [ "$index" -gt 1 ] &&
|
||||
grep -Fq ':::done' "$block" &&
|
||||
! grep -Eq ':::(todo|review|epic)' "$block"; then
|
||||
fail "mermaid block $index expands completed work; collapse completed epics to one done node"
|
||||
fi
|
||||
}
|
||||
|
||||
repo_name() {
|
||||
if [ -n "${GITHUB_REPOSITORY:-}" ]; then
|
||||
printf '%s\n' "$GITHUB_REPOSITORY"
|
||||
return 0
|
||||
fi
|
||||
command -v gh >/dev/null 2>&1 || return 1
|
||||
gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null
|
||||
}
|
||||
|
||||
check_epic_coverage() {
|
||||
repo=$(repo_name || true)
|
||||
if [ -z "$repo" ] || ! command -v gh >/dev/null 2>&1; then
|
||||
if [ -n "${GITHUB_ACTIONS:-}" ]; then
|
||||
fail "gh is required in CI to verify epic coverage"
|
||||
else
|
||||
echo "roadmap: skipping GitHub epic coverage because gh is unavailable" >&2
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
numbers=$(gh issue list \
|
||||
--repo "$repo" \
|
||||
--label epic \
|
||||
--state all \
|
||||
--limit 1000 \
|
||||
--json number \
|
||||
--jq '.[].number' 2>/dev/null)
|
||||
if [ $? -ne 0 ]; then
|
||||
if [ -n "${GITHUB_ACTIONS:-}" ]; then
|
||||
fail "failed to query epic issues"
|
||||
else
|
||||
echo "roadmap: skipping GitHub epic coverage because gh issue list failed" >&2
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
combined="$1"
|
||||
for number in $numbers; do
|
||||
if ! grep -Fq "#$number" "$combined"; then
|
||||
fail "epic issue #$number is missing from README Mermaid diagrams"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
tmp=$(mktemp -d "${TMPDIR:-/tmp}/purexml-roadmap.XXXXXX") || exit 1
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
|
||||
extract_blocks "$tmp"
|
||||
count=$(cat "$tmp/count")
|
||||
if [ "$count" -eq 0 ]; then
|
||||
fail "README has no Mermaid blocks"
|
||||
exit "$FAIL"
|
||||
fi
|
||||
|
||||
first="$tmp/block-001.mmd"
|
||||
if ! grep -Fq 'LDone["In main now"]' "$first" || ! grep -Fq 'LTodo["Open issue, no PR"]' "$first"; then
|
||||
fail "first Mermaid block must be the shared legend"
|
||||
fi
|
||||
|
||||
expected="$tmp/expected-classes.txt"
|
||||
expected_classes > "$expected"
|
||||
|
||||
combined="$tmp/all-blocks.mmd"
|
||||
: > "$combined"
|
||||
index=1
|
||||
while [ "$index" -le "$count" ]; do
|
||||
block=$(printf '%s/block-%03d.mmd' "$tmp" "$index")
|
||||
cat "$block" >> "$combined"
|
||||
validate_block \
|
||||
"$block" \
|
||||
"$index" \
|
||||
"$tmp/defined-$index.txt" \
|
||||
"$expected" \
|
||||
"$tmp/defs-$index.txt"
|
||||
index=$((index + 1))
|
||||
done
|
||||
|
||||
check_epic_coverage "$combined"
|
||||
|
||||
if [ "$FAIL" -eq 0 ]; then
|
||||
echo "roadmap: OK"
|
||||
fi
|
||||
|
||||
exit "$FAIL"
|
||||
Loading…
Reference in New Issue
Block a user