From da9ab6e1191e8155722d6d28ad983849d560bb55 Mon Sep 17 00:00:00 2001 From: Brian Madison Date: Tue, 14 Apr 2026 13:27:44 -0500 Subject: [PATCH] fix: address review findings from code review - Fix merge_menu KeyError crash when menu items missing 'code' key - Fix _is_menu_array to check ALL elements, not just first - Remove unused import os from resolve-customization.py - Remove inject.after from agent activation (agents have no completion point; inject.after only makes sense for workflows) --- src/bmm-skills/1-analysis/bmad-agent-analyst/SKILL.md | 2 -- .../1-analysis/bmad-agent-analyst/customize.toml | 2 -- .../scripts/resolve-customization.py | 11 ++++++----- .../1-analysis/bmad-agent-tech-writer/SKILL.md | 2 -- .../1-analysis/bmad-agent-tech-writer/customize.toml | 2 -- .../scripts/resolve-customization.py | 11 ++++++----- .../scripts/resolve-customization.py | 11 ++++++----- .../bmad-prfaq/scripts/resolve-customization.py | 11 ++++++----- .../scripts/resolve-customization.py | 11 ++++++----- .../scripts/resolve-customization.py | 11 ++++++----- .../scripts/resolve-customization.py | 11 ++++++----- .../scripts/resolve-customization.py | 11 ++++++----- .../2-plan-workflows/bmad-agent-pm/SKILL.md | 2 -- .../2-plan-workflows/bmad-agent-pm/customize.toml | 2 -- .../bmad-agent-pm/scripts/resolve-customization.py | 11 ++++++----- .../2-plan-workflows/bmad-agent-ux-designer/SKILL.md | 2 -- .../bmad-agent-ux-designer/customize.toml | 2 -- .../scripts/resolve-customization.py | 11 ++++++----- .../bmad-create-prd/scripts/resolve-customization.py | 11 ++++++----- .../scripts/resolve-customization.py | 11 ++++++----- .../bmad-edit-prd/scripts/resolve-customization.py | 11 ++++++----- .../scripts/resolve-customization.py | 11 ++++++----- .../3-solutioning/bmad-agent-architect/SKILL.md | 2 -- .../3-solutioning/bmad-agent-architect/customize.toml | 2 -- .../scripts/resolve-customization.py | 11 ++++++----- .../scripts/resolve-customization.py | 11 ++++++----- .../scripts/resolve-customization.py | 11 ++++++----- .../scripts/resolve-customization.py | 11 ++++++----- .../scripts/resolve-customization.py | 11 ++++++----- .../4-implementation/bmad-agent-dev/SKILL.md | 2 -- .../4-implementation/bmad-agent-dev/customize.toml | 2 -- .../bmad-agent-dev/scripts/resolve-customization.py | 11 ++++++----- .../scripts/resolve-customization.py | 11 ++++++----- .../bmad-code-review/scripts/resolve-customization.py | 11 ++++++----- .../scripts/resolve-customization.py | 11 ++++++----- .../scripts/resolve-customization.py | 11 ++++++----- .../bmad-dev-story/scripts/resolve-customization.py | 11 ++++++----- .../scripts/resolve-customization.py | 11 ++++++----- .../bmad-quick-dev/scripts/resolve-customization.py | 11 ++++++----- .../scripts/resolve-customization.py | 11 ++++++----- .../scripts/resolve-customization.py | 11 ++++++----- .../scripts/resolve-customization.py | 11 ++++++----- .../_shared/scripts/resolve-customization.py | 11 ++++++----- .../scripts/resolve-customization.py | 11 ++++++----- .../scripts/resolve-customization.py | 11 ++++++----- .../bmad-distillator/scripts/resolve-customization.py | 11 ++++++----- .../scripts/resolve-customization.py | 11 ++++++----- .../scripts/resolve-customization.py | 11 ++++++----- .../bmad-help/scripts/resolve-customization.py | 11 ++++++----- .../bmad-index-docs/scripts/resolve-customization.py | 11 ++++++----- .../bmad-party-mode/scripts/resolve-customization.py | 11 ++++++----- .../scripts/resolve-customization.py | 11 ++++++----- .../scripts/resolve-customization.py | 11 ++++++----- .../bmad-shard-doc/scripts/resolve-customization.py | 11 ++++++----- 54 files changed, 252 insertions(+), 234 deletions(-) diff --git a/src/bmm-skills/1-analysis/bmad-agent-analyst/SKILL.md b/src/bmm-skills/1-analysis/bmad-agent-analyst/SKILL.md index 15b0c15ca..e1d1568d9 100644 --- a/src/bmm-skills/1-analysis/bmad-agent-analyst/SKILL.md +++ b/src/bmm-skills/1-analysis/bmad-agent-analyst/SKILL.md @@ -24,8 +24,6 @@ Use the JSON output as resolved values. incorporate its content as high-priority context. 3. **Load resources** -- If `additional_resources` is not empty, read each listed file and incorporate as reference context. -4. **Inject after** -- If `inject.after` is not empty, read and - incorporate its content as supplementary context. You must fully embody this persona so the user gets the best experience and help they need. Do not break character until the user dismisses this persona. When the user calls a skill, this persona must carry through and remain active. diff --git a/src/bmm-skills/1-analysis/bmad-agent-analyst/customize.toml b/src/bmm-skills/1-analysis/bmad-agent-analyst/customize.toml index 508a3ee36..a0062f457 100644 --- a/src/bmm-skills/1-analysis/bmad-agent-analyst/customize.toml +++ b/src/bmm-skills/1-analysis/bmad-agent-analyst/customize.toml @@ -77,8 +77,6 @@ Ensure all stakeholder voices heard.""" # ────────────────────────────────────────────────────────────────── # Injected prompts - content added to the agent's context on activation. # 'before' loads before the agent's core instructions. -# 'after' loads after the agent's core instructions. # ────────────────────────────────────────────────────────────────── [inject] before = "" -after = "" diff --git a/src/bmm-skills/1-analysis/bmad-agent-analyst/scripts/resolve-customization.py b/src/bmm-skills/1-analysis/bmad-agent-analyst/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/1-analysis/bmad-agent-analyst/scripts/resolve-customization.py +++ b/src/bmm-skills/1-analysis/bmad-agent-analyst/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/1-analysis/bmad-agent-tech-writer/SKILL.md b/src/bmm-skills/1-analysis/bmad-agent-tech-writer/SKILL.md index 721f3bd0a..6e1c83fc3 100644 --- a/src/bmm-skills/1-analysis/bmad-agent-tech-writer/SKILL.md +++ b/src/bmm-skills/1-analysis/bmad-agent-tech-writer/SKILL.md @@ -24,8 +24,6 @@ Use the JSON output as resolved values. incorporate its content as high-priority context. 3. **Load resources** -- If `additional_resources` is not empty, read each listed file and incorporate as reference context. -4. **Inject after** -- If `inject.after` is not empty, read and - incorporate its content as supplementary context. You must fully embody this persona so the user gets the best experience and help they need. Do not break character until the user dismisses this persona. When the user calls a skill, this persona must carry through and remain active. diff --git a/src/bmm-skills/1-analysis/bmad-agent-tech-writer/customize.toml b/src/bmm-skills/1-analysis/bmad-agent-tech-writer/customize.toml index dfe6a2d6a..c7e1ea7be 100644 --- a/src/bmm-skills/1-analysis/bmad-agent-tech-writer/customize.toml +++ b/src/bmm-skills/1-analysis/bmad-agent-tech-writer/customize.toml @@ -76,8 +76,6 @@ to be detailed.""" # ────────────────────────────────────────────────────────────────── # Injected prompts - content added to the agent's context on activation. # 'before' loads before the agent's core instructions. -# 'after' loads after the agent's core instructions. # ────────────────────────────────────────────────────────────────── [inject] before = "" -after = "" diff --git a/src/bmm-skills/1-analysis/bmad-agent-tech-writer/scripts/resolve-customization.py b/src/bmm-skills/1-analysis/bmad-agent-tech-writer/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/1-analysis/bmad-agent-tech-writer/scripts/resolve-customization.py +++ b/src/bmm-skills/1-analysis/bmad-agent-tech-writer/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/1-analysis/bmad-document-project/scripts/resolve-customization.py b/src/bmm-skills/1-analysis/bmad-document-project/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/1-analysis/bmad-document-project/scripts/resolve-customization.py +++ b/src/bmm-skills/1-analysis/bmad-document-project/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/1-analysis/bmad-prfaq/scripts/resolve-customization.py b/src/bmm-skills/1-analysis/bmad-prfaq/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/1-analysis/bmad-prfaq/scripts/resolve-customization.py +++ b/src/bmm-skills/1-analysis/bmad-prfaq/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/1-analysis/bmad-product-brief/scripts/resolve-customization.py b/src/bmm-skills/1-analysis/bmad-product-brief/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/1-analysis/bmad-product-brief/scripts/resolve-customization.py +++ b/src/bmm-skills/1-analysis/bmad-product-brief/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/1-analysis/research/bmad-domain-research/scripts/resolve-customization.py b/src/bmm-skills/1-analysis/research/bmad-domain-research/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/1-analysis/research/bmad-domain-research/scripts/resolve-customization.py +++ b/src/bmm-skills/1-analysis/research/bmad-domain-research/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/1-analysis/research/bmad-market-research/scripts/resolve-customization.py b/src/bmm-skills/1-analysis/research/bmad-market-research/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/1-analysis/research/bmad-market-research/scripts/resolve-customization.py +++ b/src/bmm-skills/1-analysis/research/bmad-market-research/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/1-analysis/research/bmad-technical-research/scripts/resolve-customization.py b/src/bmm-skills/1-analysis/research/bmad-technical-research/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/1-analysis/research/bmad-technical-research/scripts/resolve-customization.py +++ b/src/bmm-skills/1-analysis/research/bmad-technical-research/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/2-plan-workflows/bmad-agent-pm/SKILL.md b/src/bmm-skills/2-plan-workflows/bmad-agent-pm/SKILL.md index 24316a4c4..dfcaf600d 100644 --- a/src/bmm-skills/2-plan-workflows/bmad-agent-pm/SKILL.md +++ b/src/bmm-skills/2-plan-workflows/bmad-agent-pm/SKILL.md @@ -24,8 +24,6 @@ Use the JSON output as resolved values. incorporate its content as high-priority context. 3. **Load resources** -- If `additional_resources` is not empty, read each listed file and incorporate as reference context. -4. **Inject after** -- If `inject.after` is not empty, read and - incorporate its content as supplementary context. You must fully embody this persona so the user gets the best experience and help they need. Do not break character until the user dismisses this persona. When the user calls a skill, this persona must carry through and remain active. diff --git a/src/bmm-skills/2-plan-workflows/bmad-agent-pm/customize.toml b/src/bmm-skills/2-plan-workflows/bmad-agent-pm/customize.toml index 98df4a417..6b676879d 100644 --- a/src/bmm-skills/2-plan-workflows/bmad-agent-pm/customize.toml +++ b/src/bmm-skills/2-plan-workflows/bmad-agent-pm/customize.toml @@ -77,8 +77,6 @@ over perfection. Technical feasibility is a constraint, not the driver \ # ────────────────────────────────────────────────────────────────── # Injected prompts - content added to the agent's context on activation. # 'before' loads before the agent's core instructions. -# 'after' loads after the agent's core instructions. # ────────────────────────────────────────────────────────────────── [inject] before = "" -after = "" diff --git a/src/bmm-skills/2-plan-workflows/bmad-agent-pm/scripts/resolve-customization.py b/src/bmm-skills/2-plan-workflows/bmad-agent-pm/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/2-plan-workflows/bmad-agent-pm/scripts/resolve-customization.py +++ b/src/bmm-skills/2-plan-workflows/bmad-agent-pm/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/SKILL.md b/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/SKILL.md index 98aebc923..a5b635a52 100644 --- a/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/SKILL.md +++ b/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/SKILL.md @@ -24,8 +24,6 @@ Use the JSON output as resolved values. incorporate its content as high-priority context. 3. **Load resources** -- If `additional_resources` is not empty, read each listed file and incorporate as reference context. -4. **Inject after** -- If `inject.after` is not empty, read and - incorporate its content as supplementary context. You must fully embody this persona so the user gets the best experience and help they need. Do not break character until the user dismisses this persona. When the user calls a skill, this persona must carry through and remain active. diff --git a/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/customize.toml b/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/customize.toml index 9206656bc..9fe6d6bc8 100644 --- a/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/customize.toml +++ b/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/customize.toml @@ -73,8 +73,6 @@ accelerate human-centered design. Data-informed but always creative.""" # ────────────────────────────────────────────────────────────────── # Injected prompts - content added to the agent's context on activation. # 'before' loads before the agent's core instructions. -# 'after' loads after the agent's core instructions. # ────────────────────────────────────────────────────────────────── [inject] before = "" -after = "" diff --git a/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/scripts/resolve-customization.py b/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/scripts/resolve-customization.py +++ b/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/2-plan-workflows/bmad-create-prd/scripts/resolve-customization.py b/src/bmm-skills/2-plan-workflows/bmad-create-prd/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/2-plan-workflows/bmad-create-prd/scripts/resolve-customization.py +++ b/src/bmm-skills/2-plan-workflows/bmad-create-prd/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/scripts/resolve-customization.py b/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/scripts/resolve-customization.py +++ b/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/2-plan-workflows/bmad-edit-prd/scripts/resolve-customization.py b/src/bmm-skills/2-plan-workflows/bmad-edit-prd/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/2-plan-workflows/bmad-edit-prd/scripts/resolve-customization.py +++ b/src/bmm-skills/2-plan-workflows/bmad-edit-prd/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/2-plan-workflows/bmad-validate-prd/scripts/resolve-customization.py b/src/bmm-skills/2-plan-workflows/bmad-validate-prd/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/2-plan-workflows/bmad-validate-prd/scripts/resolve-customization.py +++ b/src/bmm-skills/2-plan-workflows/bmad-validate-prd/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/3-solutioning/bmad-agent-architect/SKILL.md b/src/bmm-skills/3-solutioning/bmad-agent-architect/SKILL.md index 2bb8eb30b..0804f0ee5 100644 --- a/src/bmm-skills/3-solutioning/bmad-agent-architect/SKILL.md +++ b/src/bmm-skills/3-solutioning/bmad-agent-architect/SKILL.md @@ -24,8 +24,6 @@ Use the JSON output as resolved values. incorporate its content as high-priority context. 3. **Load resources** -- If `additional_resources` is not empty, read each listed file and incorporate as reference context. -4. **Inject after** -- If `inject.after` is not empty, read and - incorporate its content as supplementary context. You must fully embody this persona so the user gets the best experience and help they need. Do not break character until the user dismisses this persona. When the user calls a skill, this persona must carry through and remain active. diff --git a/src/bmm-skills/3-solutioning/bmad-agent-architect/customize.toml b/src/bmm-skills/3-solutioning/bmad-agent-architect/customize.toml index 0719e9294..c50630126 100644 --- a/src/bmm-skills/3-solutioning/bmad-agent-architect/customize.toml +++ b/src/bmm-skills/3-solutioning/bmad-agent-architect/customize.toml @@ -77,8 +77,6 @@ impact.""" # ────────────────────────────────────────────────────────────────── # Injected prompts - content added to the agent's context on activation. # 'before' loads before the agent's core instructions. -# 'after' loads after the agent's core instructions. # ────────────────────────────────────────────────────────────────── [inject] before = "" -after = "" diff --git a/src/bmm-skills/3-solutioning/bmad-agent-architect/scripts/resolve-customization.py b/src/bmm-skills/3-solutioning/bmad-agent-architect/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/3-solutioning/bmad-agent-architect/scripts/resolve-customization.py +++ b/src/bmm-skills/3-solutioning/bmad-agent-architect/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/scripts/resolve-customization.py b/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/scripts/resolve-customization.py +++ b/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/3-solutioning/bmad-create-architecture/scripts/resolve-customization.py b/src/bmm-skills/3-solutioning/bmad-create-architecture/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/3-solutioning/bmad-create-architecture/scripts/resolve-customization.py +++ b/src/bmm-skills/3-solutioning/bmad-create-architecture/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/resolve-customization.py b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/resolve-customization.py +++ b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/3-solutioning/bmad-generate-project-context/scripts/resolve-customization.py b/src/bmm-skills/3-solutioning/bmad-generate-project-context/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/3-solutioning/bmad-generate-project-context/scripts/resolve-customization.py +++ b/src/bmm-skills/3-solutioning/bmad-generate-project-context/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md b/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md index 925dea6dc..2ef8dbed2 100644 --- a/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md +++ b/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md @@ -24,8 +24,6 @@ Use the JSON output as resolved values. incorporate its content as high-priority context. 3. **Load resources** -- If `additional_resources` is not empty, read each listed file and incorporate as reference context. -4. **Inject after** -- If `inject.after` is not empty, read and - incorporate its content as supplementary context. You must fully embody this persona so the user gets the best experience and help they need. Do not break character until the user dismisses this persona. When the user calls a skill, this persona must carry through and remain active. diff --git a/src/bmm-skills/4-implementation/bmad-agent-dev/customize.toml b/src/bmm-skills/4-implementation/bmad-agent-dev/customize.toml index 26a718afa..0a30b7d64 100644 --- a/src/bmm-skills/4-implementation/bmad-agent-dev/customize.toml +++ b/src/bmm-skills/4-implementation/bmad-agent-dev/customize.toml @@ -72,8 +72,6 @@ unit tests before marking an item complete.""" # ────────────────────────────────────────────────────────────────── # Injected prompts - content added to the agent's context on activation. # 'before' loads before the agent's core instructions. -# 'after' loads after the agent's core instructions. # ────────────────────────────────────────────────────────────────── [inject] before = "" -after = "" diff --git a/src/bmm-skills/4-implementation/bmad-agent-dev/scripts/resolve-customization.py b/src/bmm-skills/4-implementation/bmad-agent-dev/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/4-implementation/bmad-agent-dev/scripts/resolve-customization.py +++ b/src/bmm-skills/4-implementation/bmad-agent-dev/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/4-implementation/bmad-checkpoint-preview/scripts/resolve-customization.py b/src/bmm-skills/4-implementation/bmad-checkpoint-preview/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/4-implementation/bmad-checkpoint-preview/scripts/resolve-customization.py +++ b/src/bmm-skills/4-implementation/bmad-checkpoint-preview/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/4-implementation/bmad-code-review/scripts/resolve-customization.py b/src/bmm-skills/4-implementation/bmad-code-review/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/4-implementation/bmad-code-review/scripts/resolve-customization.py +++ b/src/bmm-skills/4-implementation/bmad-code-review/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/4-implementation/bmad-correct-course/scripts/resolve-customization.py b/src/bmm-skills/4-implementation/bmad-correct-course/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/4-implementation/bmad-correct-course/scripts/resolve-customization.py +++ b/src/bmm-skills/4-implementation/bmad-correct-course/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/4-implementation/bmad-create-story/scripts/resolve-customization.py b/src/bmm-skills/4-implementation/bmad-create-story/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/4-implementation/bmad-create-story/scripts/resolve-customization.py +++ b/src/bmm-skills/4-implementation/bmad-create-story/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/4-implementation/bmad-dev-story/scripts/resolve-customization.py b/src/bmm-skills/4-implementation/bmad-dev-story/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/4-implementation/bmad-dev-story/scripts/resolve-customization.py +++ b/src/bmm-skills/4-implementation/bmad-dev-story/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/scripts/resolve-customization.py b/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/scripts/resolve-customization.py +++ b/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/4-implementation/bmad-quick-dev/scripts/resolve-customization.py b/src/bmm-skills/4-implementation/bmad-quick-dev/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/4-implementation/bmad-quick-dev/scripts/resolve-customization.py +++ b/src/bmm-skills/4-implementation/bmad-quick-dev/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/4-implementation/bmad-retrospective/scripts/resolve-customization.py b/src/bmm-skills/4-implementation/bmad-retrospective/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/4-implementation/bmad-retrospective/scripts/resolve-customization.py +++ b/src/bmm-skills/4-implementation/bmad-retrospective/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/4-implementation/bmad-sprint-planning/scripts/resolve-customization.py b/src/bmm-skills/4-implementation/bmad-sprint-planning/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/4-implementation/bmad-sprint-planning/scripts/resolve-customization.py +++ b/src/bmm-skills/4-implementation/bmad-sprint-planning/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/4-implementation/bmad-sprint-status/scripts/resolve-customization.py b/src/bmm-skills/4-implementation/bmad-sprint-status/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/4-implementation/bmad-sprint-status/scripts/resolve-customization.py +++ b/src/bmm-skills/4-implementation/bmad-sprint-status/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/bmm-skills/_shared/scripts/resolve-customization.py b/src/bmm-skills/_shared/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/bmm-skills/_shared/scripts/resolve-customization.py +++ b/src/bmm-skills/_shared/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/core-skills/bmad-advanced-elicitation/scripts/resolve-customization.py b/src/core-skills/bmad-advanced-elicitation/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/core-skills/bmad-advanced-elicitation/scripts/resolve-customization.py +++ b/src/core-skills/bmad-advanced-elicitation/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/core-skills/bmad-brainstorming/scripts/resolve-customization.py b/src/core-skills/bmad-brainstorming/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/core-skills/bmad-brainstorming/scripts/resolve-customization.py +++ b/src/core-skills/bmad-brainstorming/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/core-skills/bmad-distillator/scripts/resolve-customization.py b/src/core-skills/bmad-distillator/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/core-skills/bmad-distillator/scripts/resolve-customization.py +++ b/src/core-skills/bmad-distillator/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/core-skills/bmad-editorial-review-prose/scripts/resolve-customization.py b/src/core-skills/bmad-editorial-review-prose/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/core-skills/bmad-editorial-review-prose/scripts/resolve-customization.py +++ b/src/core-skills/bmad-editorial-review-prose/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/core-skills/bmad-editorial-review-structure/scripts/resolve-customization.py b/src/core-skills/bmad-editorial-review-structure/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/core-skills/bmad-editorial-review-structure/scripts/resolve-customization.py +++ b/src/core-skills/bmad-editorial-review-structure/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/core-skills/bmad-help/scripts/resolve-customization.py b/src/core-skills/bmad-help/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/core-skills/bmad-help/scripts/resolve-customization.py +++ b/src/core-skills/bmad-help/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/core-skills/bmad-index-docs/scripts/resolve-customization.py b/src/core-skills/bmad-index-docs/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/core-skills/bmad-index-docs/scripts/resolve-customization.py +++ b/src/core-skills/bmad-index-docs/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/core-skills/bmad-party-mode/scripts/resolve-customization.py b/src/core-skills/bmad-party-mode/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/core-skills/bmad-party-mode/scripts/resolve-customization.py +++ b/src/core-skills/bmad-party-mode/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/core-skills/bmad-review-adversarial-general/scripts/resolve-customization.py b/src/core-skills/bmad-review-adversarial-general/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/core-skills/bmad-review-adversarial-general/scripts/resolve-customization.py +++ b/src/core-skills/bmad-review-adversarial-general/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/core-skills/bmad-review-edge-case-hunter/scripts/resolve-customization.py b/src/core-skills/bmad-review-edge-case-hunter/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/core-skills/bmad-review-edge-case-hunter/scripts/resolve-customization.py +++ b/src/core-skills/bmad-review-edge-case-hunter/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values()) diff --git a/src/core-skills/bmad-shard-doc/scripts/resolve-customization.py b/src/core-skills/bmad-shard-doc/scripts/resolve-customization.py index 6bbce66a4..c102b1561 100755 --- a/src/core-skills/bmad-shard-doc/scripts/resolve-customization.py +++ b/src/core-skills/bmad-shard-doc/scripts/resolve-customization.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse import json -import os import sys import tomllib from pathlib import Path @@ -57,19 +56,21 @@ def load_toml(path: Path) -> dict[str, Any]: # --------------------------------------------------------------------------- def _is_menu_array(value: Any) -> bool: - """True when *value* looks like a ``[[menu]]`` array of tables with ``code`` keys.""" + """True when *value* is a non-empty list where ALL items are dicts with a ``code`` key.""" return ( isinstance(value, list) and len(value) > 0 - and isinstance(value[0], dict) - and "code" in value[0] + and all(isinstance(item, dict) and "code" in item for item in value) ) def merge_menu(base: list[dict], override: list[dict]) -> list[dict]: """Merge-by-code: matching codes replace; new codes append.""" - result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base} + result_by_code: dict[str, dict] = {item["code"]: dict(item) for item in base if "code" in item} for item in override: + if "code" not in item: + print(f"warning: menu item missing 'code' key, skipping: {item}", file=sys.stderr) + continue result_by_code[item["code"]] = dict(item) return list(result_by_code.values())