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
911 changes: 911 additions & 0 deletions .github/skills/module-phase-tracker/SKILL.md

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions process/_assets/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,32 @@
.score-grid .sd-card.card-ml4 .sd-card-footer a {
color: var(--card-body-text-color) !important;
}

/* Compact process area overview charts */
.compact-overview-wrapper {
overflow-x: auto;
}

.compact-overview-table td:nth-child(1),
.compact-overview-table th:nth-child(1),
.compact-overview-table td:nth-child(5),
.compact-overview-table th:nth-child(5),
.compact-overview-table td:nth-child(6),
.compact-overview-table th:nth-child(6) {
text-align: center;
vertical-align: middle;
}

.compact-overview-table td:has(img[id^="needpie-"]) {
min-width: 240px;
vertical-align: middle;
text-align: center;
}

.compact-overview-table td:has(img[id^="needpie-"]) img {
width: 240px;
max-width: none;
height: auto;
display: block;
margin: 0 auto;
}
59 changes: 59 additions & 0 deletions process/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,64 @@
html_static_path = ["_assets"]
html_css_files = ["custom.css"]

# Hide the "On this page" secondary sidebar for wide-content pages
html_theme_options = {
"secondary_sidebar_items": {
"**": ["page-toc"],
"standards/process_reqs_list/process_area_status_summary": [],
"standards/process_reqs_list/process_status_overview": [],
}
}

# :need:`{title}` is used in the needs templates to display the title of the need
needs_role_need_template = "{title}"

# Make the process/ directory importable so that filter functions
# referenced via :filter-func: in sphinx-needs directives can be found.
import sys as _sys
import os as _os
_sys.path.insert(0, _os.path.dirname(_os.path.abspath(__file__)))


def _patch_needpie_suppress_legend() -> None:
"""Suppress all in-chart legends in sphinx-needs needpie charts.

needpie auto-adds a legend whenever a slice is <5% or zero, even without
the :legend: option. Since a static legend is displayed above the table,
we replace axes.legend() with a no-op so no legend appears inside the charts.

We also patch save_matplotlib_figure to always overwrite existing SVG files,
bypassing the sphinx-needs env.images cache which would otherwise skip
regenerating images when the Sphinx environment pickle exists from a prior build.
"""
try:
import matplotlib.axes
matplotlib.axes.Axes.legend = lambda self, *args, **kwargs: None
except Exception:
pass

try:
import sphinx_needs.utils as _sn_utils
import sphinx_needs.directives.needpie as _needpie_mod
import os

_orig_save = _sn_utils.save_matplotlib_figure

def _save_always(app, figure, basename, fromdocname): # type: ignore[no-untyped-def]
# Remove cached entry so figure.savefig() is always called
builder = app.builder
for ext in ("svg", "png", "pdf"):
path = os.path.join(builder.outdir, builder.imagedir, f"{basename}.{ext}")
try:
del app.env.images[path] # type: ignore[attr-defined]
except (KeyError, TypeError, AttributeError):
pass
return _orig_save(app, figure, basename, fromdocname)

_sn_utils.save_matplotlib_figure = _save_always
_needpie_mod.save_matplotlib_figure = _save_always
except Exception:
pass


_patch_needpie_suppress_legend()
153 changes: 153 additions & 0 deletions process/needs_filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# *******************************************************************************
# Copyright (c) 2025 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

"""
Custom filter functions for use with sphinx-needs :filter-func: option.
"""


def std_req_status_for_area(needs, results, arg1=""):
"""
filter_func for needpie: counts the tag-based compliance status distribution
of std_req needs referenced via `complies` from gd_req needs tagged with the
given process area tag.

Recognized tags (in priority order): ok, recommendation, open, action,
deviation, n/a. Needs with none of these tags are counted as "other".

arg1 = process area tag (e.g. requirements_engineering)
"""
area_tag = arg1.strip()
std_req_ids = set()
needs_by_id = {n["id"]: n for n in needs}
for need in needs:
if (
need.get("type") == "gd_req"
and not need.get("is_external", False)
and area_tag in need.get("tags", [])
):
for ref_id in need.get("complies", []):
if ref_id.startswith("std_req__iso26262__"):
std_req_ids.add(ref_id)
ok = recommendation = open_ = action = deviation = na = other = 0
for sid in std_req_ids:
n = needs_by_id.get(sid)
if n:
t = set(n.get("tags", []))
if "deviation" in t:
deviation += 1
elif "action" in t:
action += 1
elif "open" in t:
open_ += 1
elif "ok" in t:
ok += 1
elif "recommendation" in t:
recommendation += 1
elif "n/a" in t:
na += 1
else:
other += 1
results += [ok, recommendation, open_, action, deviation, na, other]


def wp_tag_status(needs, results, arg1=""):
"""
filter_func for needpie: counts the tag-based verification status distribution
of gd_req needs associated with one or more workflows (pipe-separated in arg1).

Tags (in priority order):
- done_automation → Automated
- prio_<X>_automation → Waiting for automation
- manual_prio_<X> → Inspection list
- (none of the above) → Other

arg1 = workflow id(s), pipe-separated (e.g. wf__foo or wf__foo|wf__bar)
"""
workflow_ids = [w.strip() for w in arg1.split("|") if w.strip()]
automated = waiting = inspection = other = 0
for need in needs:
if need.get("type") == "gd_req" and not need.get("is_external", False):
satisfies = need.get("satisfies", [])
if any(wf in satisfies for wf in workflow_ids):
tags = set(need.get("tags", []))
if "done_automation" in tags:
automated += 1
elif any(
t.startswith("prio_") and t.endswith("_automation")
for t in tags
):
waiting += 1
elif any(t.startswith("manual_prio_") for t in tags):
inspection += 1
else:
other += 1
results += [automated, waiting, inspection, other]


def area_verification_status(needs, results, arg1=""):
"""
filter_func for needpie: counts the tag-based verification status distribution
of gd_req needs tagged with the given process area tag, aggregated across all
workflows for that area.

Tags (in priority order):
- done_automation → Automated
- prio_<X>_automation → Waiting for automation
- manual_prio_<X> → Inspection list
- (none of the above) → Other

arg1 = process area tag (e.g. requirements_engineering)
"""
area_tag = arg1.strip()
automated = waiting = inspection = other = 0
for need in needs:
if (
need.get("type") == "gd_req"
and not need.get("is_external", False)
and area_tag in need.get("tags", [])
):
tags = set(need.get("tags", []))
if "done_automation" in tags:
automated += 1
elif any(
t.startswith("prio_") and t.endswith("_automation")
for t in tags
):
waiting += 1
elif any(t.startswith("manual_prio_") for t in tags):
inspection += 1
else:
other += 1
results += [automated, waiting, inspection, other]


def wp_done_automation_status(needs, results, arg1=""):
"""
filter_func for needpie: counts gd_req needs associated with one or more
workflows and splits them into done_automation vs. remaining requirements.

arg1 = workflow id(s), pipe-separated (e.g. wf__foo or wf__foo|wf__bar)
"""
workflow_ids = [w.strip() for w in arg1.split("|") if w.strip()]
done = rest = 0
for need in needs:
if need.get("type") == "gd_req" and not need.get("is_external", False):
satisfies = need.get("satisfies", [])
if any(wf in satisfies for wf in workflow_ids):
tags = set(need.get("tags", []))
if "done_automation" in tags:
done += 1
else:
rest += 1
results += [done, rest]
Loading
Loading