ma-agents 3.2.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.opencode/skills/.ma-agents.json +99 -99
- package/.roo/rules/00-ma-agents.md +13 -0
- package/.roo/skills/.ma-agents.json +241 -0
- package/.roo/skills/MANIFEST.yaml +254 -0
- package/.roo/skills/ai-audit-trail/SKILL.md +23 -0
- package/.roo/skills/auto-bug-detection/SKILL.md +169 -0
- package/.roo/skills/cmake-best-practices/SKILL.md +64 -0
- package/.roo/skills/cmake-best-practices/examples/cmake.md +59 -0
- package/.roo/skills/code-documentation/SKILL.md +57 -0
- package/.roo/skills/code-documentation/examples/cpp.md +29 -0
- package/.roo/skills/code-documentation/examples/csharp.md +28 -0
- package/.roo/skills/code-documentation/examples/javascript_typescript.md +28 -0
- package/.roo/skills/code-documentation/examples/python.md +57 -0
- package/.roo/skills/code-review/SKILL.md +43 -0
- package/.roo/skills/commit-message/SKILL.md +79 -0
- package/.roo/skills/cpp-best-practices/SKILL.md +234 -0
- package/.roo/skills/cpp-best-practices/examples/modern-idioms.md +189 -0
- package/.roo/skills/cpp-best-practices/examples/naming-and-organization.md +102 -0
- package/.roo/skills/cpp-concurrency-safety/SKILL.md +60 -0
- package/.roo/skills/cpp-concurrency-safety/examples/concurrency.md +73 -0
- package/.roo/skills/cpp-const-correctness/SKILL.md +63 -0
- package/.roo/skills/cpp-const-correctness/examples/const_correctness.md +54 -0
- package/.roo/skills/cpp-memory-handling/SKILL.md +42 -0
- package/.roo/skills/cpp-memory-handling/examples/modern-cpp.md +49 -0
- package/.roo/skills/cpp-memory-handling/examples/smart-pointers.md +46 -0
- package/.roo/skills/cpp-modern-composition/SKILL.md +64 -0
- package/.roo/skills/cpp-modern-composition/examples/composition.md +51 -0
- package/.roo/skills/cpp-robust-interfaces/SKILL.md +55 -0
- package/.roo/skills/cpp-robust-interfaces/examples/interfaces.md +56 -0
- package/.roo/skills/create-hardened-docker-skill/SKILL.md +637 -0
- package/.roo/skills/create-hardened-docker-skill/scripts/create-all.sh +489 -0
- package/.roo/skills/csharp-best-practices/SKILL.md +278 -0
- package/.roo/skills/docker-hardening-verification/SKILL.md +28 -0
- package/.roo/skills/docker-hardening-verification/scripts/verify-hardening.sh +39 -0
- package/.roo/skills/docker-image-signing/SKILL.md +28 -0
- package/.roo/skills/docker-image-signing/scripts/sign-image.sh +33 -0
- package/.roo/skills/document-revision-history/SKILL.md +104 -0
- package/.roo/skills/git-workflow-skill/SKILL.md +194 -0
- package/.roo/skills/git-workflow-skill/hooks/commit-msg +61 -0
- package/.roo/skills/git-workflow-skill/hooks/pre-commit +38 -0
- package/.roo/skills/git-workflow-skill/hooks/prepare-commit-msg +56 -0
- package/.roo/skills/git-workflow-skill/scripts/finish-feature.sh +192 -0
- package/.roo/skills/git-workflow-skill/scripts/install-hooks.sh +55 -0
- package/.roo/skills/git-workflow-skill/scripts/start-feature.sh +110 -0
- package/.roo/skills/git-workflow-skill/scripts/validate-workflow.sh +229 -0
- package/.roo/skills/js-ts-dependency-mgmt/SKILL.md +49 -0
- package/.roo/skills/js-ts-dependency-mgmt/examples/dependency_mgmt.md +60 -0
- package/.roo/skills/js-ts-security-skill/SKILL.md +64 -0
- package/.roo/skills/js-ts-security-skill/scripts/verify-security.sh +136 -0
- package/.roo/skills/logging-best-practices/SKILL.md +50 -0
- package/.roo/skills/logging-best-practices/examples/cpp.md +36 -0
- package/.roo/skills/logging-best-practices/examples/csharp.md +49 -0
- package/.roo/skills/logging-best-practices/examples/javascript.md +77 -0
- package/.roo/skills/logging-best-practices/examples/python.md +57 -0
- package/.roo/skills/logging-best-practices/references/logging-standards.md +29 -0
- package/.roo/skills/open-presentation/SKILL.md +35 -0
- package/.roo/skills/opentelemetry-best-practices/SKILL.md +34 -0
- package/.roo/skills/opentelemetry-best-practices/examples/go.md +32 -0
- package/.roo/skills/opentelemetry-best-practices/examples/javascript.md +58 -0
- package/.roo/skills/opentelemetry-best-practices/examples/python.md +37 -0
- package/.roo/skills/opentelemetry-best-practices/references/otel-standards.md +37 -0
- package/.roo/skills/python-best-practices/SKILL.md +385 -0
- package/.roo/skills/python-dependency-mgmt/SKILL.md +42 -0
- package/.roo/skills/python-dependency-mgmt/examples/dependency_mgmt.md +67 -0
- package/.roo/skills/python-security-skill/SKILL.md +56 -0
- package/.roo/skills/python-security-skill/examples/security.md +56 -0
- package/.roo/skills/self-signed-cert/SKILL.md +42 -0
- package/.roo/skills/self-signed-cert/scripts/generate-cert.ps1 +45 -0
- package/.roo/skills/self-signed-cert/scripts/generate-cert.sh +43 -0
- package/.roo/skills/skill-creator/SKILL.md +196 -0
- package/.roo/skills/skill-creator/references/output-patterns.md +82 -0
- package/.roo/skills/skill-creator/references/workflows.md +28 -0
- package/.roo/skills/skill-creator/scripts/init_skill.py +208 -0
- package/.roo/skills/skill-creator/scripts/package_skill.py +99 -0
- package/.roo/skills/skill-creator/scripts/quick_validate.py +113 -0
- package/.roo/skills/story-status-lookup/SKILL.md +78 -0
- package/.roo/skills/test-accompanied-development/SKILL.md +50 -0
- package/.roo/skills/test-generator/SKILL.md +65 -0
- package/.roo/skills/vercel-react-best-practices/SKILL.md +109 -0
- package/.roo/skills/verify-hardened-docker-skill/SKILL.md +442 -0
- package/.roo/skills/verify-hardened-docker-skill/scripts/verify-docker-hardening.sh +439 -0
- package/README.md +21 -2
- package/bin/cli.js +55 -0
- package/lib/agents.js +46 -0
- package/lib/bmad-cache/cache-manifest.json +1 -1
- package/lib/bmad-customizations/bmm-demerzel.customize.yaml +36 -0
- package/lib/bmad-customizations/demerzel.md +32 -0
- package/lib/bmad-extension/module-help.csv +13 -0
- package/lib/bmad-extension/skills/bmad-ma-agent-ml/.gitkeep +0 -0
- package/lib/bmad-extension/skills/bmad-ma-agent-ml/SKILL.md +59 -0
- package/lib/bmad-extension/skills/bmad-ma-agent-ml/bmad-skill-manifest.yaml +11 -0
- package/lib/bmad-extension/skills/generate-backlog/.gitkeep +0 -0
- package/lib/bmad-extension/skills/ml-advise/.gitkeep +0 -0
- package/lib/bmad-extension/skills/ml-advise/SKILL.md +76 -0
- package/lib/bmad-extension/skills/ml-advise/bmad-skill-manifest.yaml +3 -0
- package/lib/bmad-extension/skills/ml-advise/skill.json +7 -0
- package/lib/bmad-extension/skills/ml-analysis/.gitkeep +0 -0
- package/lib/bmad-extension/skills/ml-analysis/SKILL.md +60 -0
- package/lib/bmad-extension/skills/ml-analysis/bmad-skill-manifest.yaml +3 -0
- package/lib/bmad-extension/skills/ml-analysis/skill.json +7 -0
- package/lib/bmad-extension/skills/ml-architecture/.gitkeep +0 -0
- package/lib/bmad-extension/skills/ml-architecture/SKILL.md +55 -0
- package/lib/bmad-extension/skills/ml-architecture/bmad-skill-manifest.yaml +3 -0
- package/lib/bmad-extension/skills/ml-architecture/skill.json +7 -0
- package/lib/bmad-extension/skills/ml-detailed-design/.gitkeep +0 -0
- package/lib/bmad-extension/skills/ml-detailed-design/SKILL.md +67 -0
- package/lib/bmad-extension/skills/ml-detailed-design/bmad-skill-manifest.yaml +3 -0
- package/lib/bmad-extension/skills/ml-detailed-design/skill.json +7 -0
- package/lib/bmad-extension/skills/ml-eda/.gitkeep +0 -0
- package/lib/bmad-extension/skills/ml-eda/SKILL.md +56 -0
- package/lib/bmad-extension/skills/ml-eda/bmad-skill-manifest.yaml +3 -0
- package/lib/bmad-extension/skills/ml-eda/scripts/baseline_classifier.py +522 -0
- package/lib/bmad-extension/skills/ml-eda/scripts/class_weights_calculator.py +295 -0
- package/lib/bmad-extension/skills/ml-eda/scripts/clustering_explorer.py +383 -0
- package/lib/bmad-extension/skills/ml-eda/scripts/eda_analyzer.py +654 -0
- package/lib/bmad-extension/skills/ml-eda/skill.json +7 -0
- package/lib/bmad-extension/skills/ml-experiment/.gitkeep +0 -0
- package/lib/bmad-extension/skills/ml-experiment/SKILL.md +74 -0
- package/lib/bmad-extension/skills/ml-experiment/assets/advanced_trainer_configs.py +430 -0
- package/lib/bmad-extension/skills/ml-experiment/assets/quick_trainer_setup.py +233 -0
- package/lib/bmad-extension/skills/ml-experiment/assets/template_datamodule.py +219 -0
- package/lib/bmad-extension/skills/ml-experiment/assets/template_gnn_module.py +341 -0
- package/lib/bmad-extension/skills/ml-experiment/assets/template_lightning_module.py +158 -0
- package/lib/bmad-extension/skills/ml-experiment/bmad-skill-manifest.yaml +3 -0
- package/lib/bmad-extension/skills/ml-experiment/skill.json +7 -0
- package/lib/bmad-extension/skills/ml-hparam/.gitkeep +0 -0
- package/lib/bmad-extension/skills/ml-hparam/SKILL.md +81 -0
- package/lib/bmad-extension/skills/ml-hparam/bmad-skill-manifest.yaml +3 -0
- package/lib/bmad-extension/skills/ml-hparam/skill.json +7 -0
- package/lib/bmad-extension/skills/ml-ideation/.gitkeep +0 -0
- package/lib/bmad-extension/skills/ml-ideation/SKILL.md +50 -0
- package/lib/bmad-extension/skills/ml-ideation/bmad-skill-manifest.yaml +3 -0
- package/lib/bmad-extension/skills/ml-ideation/scripts/validate_ml_prd.py +287 -0
- package/lib/bmad-extension/skills/ml-ideation/skill.json +7 -0
- package/lib/bmad-extension/skills/ml-infra/.gitkeep +0 -0
- package/lib/bmad-extension/skills/ml-infra/SKILL.md +58 -0
- package/lib/bmad-extension/skills/ml-infra/bmad-skill-manifest.yaml +3 -0
- package/lib/bmad-extension/skills/ml-infra/skill.json +7 -0
- package/lib/bmad-extension/skills/ml-retrospective/.gitkeep +0 -0
- package/lib/bmad-extension/skills/ml-retrospective/SKILL.md +63 -0
- package/lib/bmad-extension/skills/ml-retrospective/bmad-skill-manifest.yaml +3 -0
- package/lib/bmad-extension/skills/ml-retrospective/skill.json +7 -0
- package/lib/bmad-extension/skills/ml-revision/.gitkeep +0 -0
- package/lib/bmad-extension/skills/ml-revision/SKILL.md +82 -0
- package/lib/bmad-extension/skills/ml-revision/bmad-skill-manifest.yaml +3 -0
- package/lib/bmad-extension/skills/ml-revision/skill.json +7 -0
- package/lib/bmad-extension/skills/ml-techspec/.gitkeep +0 -0
- package/lib/bmad-extension/skills/ml-techspec/SKILL.md +80 -0
- package/lib/bmad-extension/skills/ml-techspec/bmad-skill-manifest.yaml +3 -0
- package/lib/bmad-extension/skills/ml-techspec/skill.json +7 -0
- package/lib/bmad.js +85 -8
- package/lib/skill-authoring.js +1 -1
- package/package.json +5 -4
- package/test/agent-injection-strategy.test.js +4 -4
- package/test/bmad-version-bump.test.js +34 -34
- package/test/build-bmad-args.test.js +13 -6
- package/test/convert-agents-to-skills.test.js +11 -1
- package/test/extension-module-restructure.test.js +31 -7
- package/test/migration-validation.test.js +14 -11
- package/test/roo-code-agent.test.js +166 -0
- package/test/roo-code-injection.test.js +172 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
"""
|
|
2
|
+
template_gnn_module.py — BMAD DL Lifecycle
|
|
3
|
+
(Inspired by K-Dense claude-scientific-skills/pytorch-geometric/)
|
|
4
|
+
|
|
5
|
+
PyTorch Geometric (PyG) template for Graph Neural Network tasks.
|
|
6
|
+
Covers GCN, GAT, GraphSAGE, and GIN architectures for:
|
|
7
|
+
- Node classification (predicting labels for each node in a graph)
|
|
8
|
+
- Graph classification (predicting a label for an entire graph)
|
|
9
|
+
|
|
10
|
+
Use this for defect detection on circuit graphs, molecular property prediction,
|
|
11
|
+
social network analysis, or any graph-structured data.
|
|
12
|
+
|
|
13
|
+
Requires: pip install torch-geometric
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
Copy this file to src/models/your_gnn.py and:
|
|
17
|
+
1. Choose an architecture (GCN / GAT / GraphSAGE / GIN)
|
|
18
|
+
2. Set in_channels to your node feature dimension
|
|
19
|
+
3. Set out_channels to number of classes
|
|
20
|
+
4. Wrap with your LightningModule or use train/test helpers directly
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from typing import Optional
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
import torch
|
|
29
|
+
import torch.nn as nn
|
|
30
|
+
import torch.nn.functional as F
|
|
31
|
+
HAS_TORCH = True
|
|
32
|
+
except ImportError:
|
|
33
|
+
HAS_TORCH = False
|
|
34
|
+
raise ImportError("Install PyTorch: pip install torch")
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
from torch_geometric.nn import (
|
|
38
|
+
GCNConv, GATConv, SAGEConv, GINConv,
|
|
39
|
+
global_mean_pool, global_max_pool, global_add_pool,
|
|
40
|
+
)
|
|
41
|
+
from torch_geometric.data import Data, DataLoader
|
|
42
|
+
HAS_PYG = True
|
|
43
|
+
except ImportError:
|
|
44
|
+
HAS_PYG = False
|
|
45
|
+
raise ImportError(
|
|
46
|
+
"Install PyTorch Geometric: pip install torch-geometric\n"
|
|
47
|
+
"See: https://pytorch-geometric.readthedocs.io/en/latest/install/installation.html"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# ══════════════════════════════════════════════════════════════════════════════
|
|
52
|
+
# Architecture 1: GCN — Graph Convolutional Network
|
|
53
|
+
# Best for: Homophilic graphs (connected nodes tend to share labels)
|
|
54
|
+
# ══════════════════════════════════════════════════════════════════════════════
|
|
55
|
+
|
|
56
|
+
class GCN(nn.Module):
|
|
57
|
+
"""
|
|
58
|
+
Graph Convolutional Network (Kipf & Welling, 2017).
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
in_channels: Dimension of input node features.
|
|
62
|
+
hidden_channels: Hidden layer dimension.
|
|
63
|
+
out_channels: Number of output classes.
|
|
64
|
+
num_layers: Number of GCN layers (2-4 recommended).
|
|
65
|
+
dropout: Dropout rate.
|
|
66
|
+
task: 'node' or 'graph' classification.
|
|
67
|
+
"""
|
|
68
|
+
def __init__(
|
|
69
|
+
self, in_channels: int, hidden_channels: int, out_channels: int,
|
|
70
|
+
num_layers: int = 3, dropout: float = 0.5, task: str = "node",
|
|
71
|
+
):
|
|
72
|
+
super().__init__()
|
|
73
|
+
self.task = task
|
|
74
|
+
self.dropout = dropout
|
|
75
|
+
|
|
76
|
+
self.convs = nn.ModuleList()
|
|
77
|
+
self.bns = nn.ModuleList()
|
|
78
|
+
|
|
79
|
+
for i in range(num_layers):
|
|
80
|
+
in_ch = in_channels if i == 0 else hidden_channels
|
|
81
|
+
self.convs.append(GCNConv(in_ch, hidden_channels))
|
|
82
|
+
self.bns.append(nn.BatchNorm1d(hidden_channels))
|
|
83
|
+
|
|
84
|
+
self.classifier = nn.Linear(hidden_channels, out_channels)
|
|
85
|
+
|
|
86
|
+
def forward(self, x: torch.Tensor, edge_index: torch.Tensor,
|
|
87
|
+
batch: Optional[torch.Tensor] = None) -> torch.Tensor:
|
|
88
|
+
for conv, bn in zip(self.convs[:-1], self.bns[:-1]):
|
|
89
|
+
x = F.relu(bn(conv(x, edge_index)))
|
|
90
|
+
x = F.dropout(x, p=self.dropout, training=self.training)
|
|
91
|
+
|
|
92
|
+
x = self.convs[-1](x, edge_index)
|
|
93
|
+
|
|
94
|
+
if self.task == "graph":
|
|
95
|
+
x = global_mean_pool(x, batch)
|
|
96
|
+
|
|
97
|
+
return self.classifier(x)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# ══════════════════════════════════════════════════════════════════════════════
|
|
101
|
+
# Architecture 2: GAT — Graph Attention Network
|
|
102
|
+
# Best for: Graphs where some neighbors are more important than others
|
|
103
|
+
# ══════════════════════════════════════════════════════════════════════════════
|
|
104
|
+
|
|
105
|
+
class GAT(nn.Module):
|
|
106
|
+
"""
|
|
107
|
+
Graph Attention Network (Veličković et al., 2018).
|
|
108
|
+
Multi-head attention assigns different importance to each neighbor.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
in_channels: Dimension of input node features.
|
|
112
|
+
hidden_channels: Hidden layer dimension per head.
|
|
113
|
+
out_channels: Number of output classes.
|
|
114
|
+
heads: Number of attention heads (4-8 recommended).
|
|
115
|
+
dropout: Dropout rate (applied to attention weights too).
|
|
116
|
+
task: 'node' or 'graph' classification.
|
|
117
|
+
"""
|
|
118
|
+
def __init__(
|
|
119
|
+
self, in_channels: int, hidden_channels: int, out_channels: int,
|
|
120
|
+
heads: int = 4, dropout: float = 0.5, task: str = "node",
|
|
121
|
+
):
|
|
122
|
+
super().__init__()
|
|
123
|
+
self.task = task
|
|
124
|
+
self.dropout = dropout
|
|
125
|
+
|
|
126
|
+
self.conv1 = GATConv(in_channels, hidden_channels, heads=heads, dropout=dropout)
|
|
127
|
+
self.conv2 = GATConv(hidden_channels * heads, out_channels, heads=1,
|
|
128
|
+
concat=False, dropout=dropout)
|
|
129
|
+
self.bn1 = nn.BatchNorm1d(hidden_channels * heads)
|
|
130
|
+
|
|
131
|
+
def forward(self, x: torch.Tensor, edge_index: torch.Tensor,
|
|
132
|
+
batch: Optional[torch.Tensor] = None) -> torch.Tensor:
|
|
133
|
+
x = F.dropout(x, p=self.dropout, training=self.training)
|
|
134
|
+
x = F.elu(self.bn1(self.conv1(x, edge_index)))
|
|
135
|
+
x = F.dropout(x, p=self.dropout, training=self.training)
|
|
136
|
+
x = self.conv2(x, edge_index)
|
|
137
|
+
|
|
138
|
+
if self.task == "graph":
|
|
139
|
+
x = global_mean_pool(x, batch)
|
|
140
|
+
|
|
141
|
+
return x # Raw logits — apply softmax/sigmoid in loss
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# ══════════════════════════════════════════════════════════════════════════════
|
|
145
|
+
# Architecture 3: GraphSAGE — Inductive / large-graph friendly
|
|
146
|
+
# Best for: Large graphs, inductive settings (unseen nodes at test time)
|
|
147
|
+
# ══════════════════════════════════════════════════════════════════════════════
|
|
148
|
+
|
|
149
|
+
class GraphSAGE(nn.Module):
|
|
150
|
+
"""
|
|
151
|
+
GraphSAGE (Hamilton et al., 2017).
|
|
152
|
+
Aggregates neighbor features via mean/max/LSTM — scales to large graphs.
|
|
153
|
+
Inductive: can generalize to unseen nodes not in the training graph.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
in_channels: Dimension of input node features.
|
|
157
|
+
hidden_channels: Hidden layer dimension.
|
|
158
|
+
out_channels: Number of output classes.
|
|
159
|
+
num_layers: Number of SAGE layers.
|
|
160
|
+
dropout: Dropout rate.
|
|
161
|
+
task: 'node' or 'graph' classification.
|
|
162
|
+
"""
|
|
163
|
+
def __init__(
|
|
164
|
+
self, in_channels: int, hidden_channels: int, out_channels: int,
|
|
165
|
+
num_layers: int = 3, dropout: float = 0.5, task: str = "node",
|
|
166
|
+
):
|
|
167
|
+
super().__init__()
|
|
168
|
+
self.task = task
|
|
169
|
+
self.dropout = dropout
|
|
170
|
+
|
|
171
|
+
self.convs = nn.ModuleList()
|
|
172
|
+
self.bns = nn.ModuleList()
|
|
173
|
+
|
|
174
|
+
for i in range(num_layers):
|
|
175
|
+
in_ch = in_channels if i == 0 else hidden_channels
|
|
176
|
+
self.convs.append(SAGEConv(in_ch, hidden_channels))
|
|
177
|
+
self.bns.append(nn.BatchNorm1d(hidden_channels))
|
|
178
|
+
|
|
179
|
+
self.classifier = nn.Linear(hidden_channels, out_channels)
|
|
180
|
+
|
|
181
|
+
def forward(self, x: torch.Tensor, edge_index: torch.Tensor,
|
|
182
|
+
batch: Optional[torch.Tensor] = None) -> torch.Tensor:
|
|
183
|
+
for conv, bn in zip(self.convs, self.bns):
|
|
184
|
+
x = F.relu(bn(conv(x, edge_index)))
|
|
185
|
+
x = F.dropout(x, p=self.dropout, training=self.training)
|
|
186
|
+
|
|
187
|
+
if self.task == "graph":
|
|
188
|
+
x = global_mean_pool(x, batch)
|
|
189
|
+
|
|
190
|
+
return self.classifier(x)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
# ══════════════════════════════════════════════════════════════════════════════
|
|
194
|
+
# Architecture 4: GIN — Graph Isomorphism Network
|
|
195
|
+
# Best for: Graph classification, maximally expressive (Weisfeiler-Leman equiv.)
|
|
196
|
+
# ══════════════════════════════════════════════════════════════════════════════
|
|
197
|
+
|
|
198
|
+
class GIN(nn.Module):
|
|
199
|
+
"""
|
|
200
|
+
Graph Isomorphism Network (Xu et al., 2019).
|
|
201
|
+
Most expressive GNN for graph-level tasks in the WL hierarchy.
|
|
202
|
+
Aggregates by: h_v = MLP((1 + eps) * h_v + sum(neighbors))
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
in_channels: Dimension of input node features.
|
|
206
|
+
hidden_channels: Hidden dimension for each MLP layer.
|
|
207
|
+
out_channels: Number of output classes.
|
|
208
|
+
num_layers: Number of GIN layers (3-5 for graph classification).
|
|
209
|
+
dropout: Dropout rate.
|
|
210
|
+
"""
|
|
211
|
+
def __init__(
|
|
212
|
+
self, in_channels: int, hidden_channels: int, out_channels: int,
|
|
213
|
+
num_layers: int = 4, dropout: float = 0.5,
|
|
214
|
+
):
|
|
215
|
+
super().__init__()
|
|
216
|
+
self.dropout = dropout
|
|
217
|
+
self.convs = nn.ModuleList()
|
|
218
|
+
self.bns = nn.ModuleList()
|
|
219
|
+
|
|
220
|
+
for i in range(num_layers):
|
|
221
|
+
in_ch = in_channels if i == 0 else hidden_channels
|
|
222
|
+
mlp = nn.Sequential(
|
|
223
|
+
nn.Linear(in_ch, hidden_channels),
|
|
224
|
+
nn.BatchNorm1d(hidden_channels),
|
|
225
|
+
nn.ReLU(),
|
|
226
|
+
nn.Linear(hidden_channels, hidden_channels),
|
|
227
|
+
)
|
|
228
|
+
self.convs.append(GINConv(mlp, train_eps=True))
|
|
229
|
+
self.bns.append(nn.BatchNorm1d(hidden_channels))
|
|
230
|
+
|
|
231
|
+
# Jumping Knowledge: concat all layer outputs before classifier
|
|
232
|
+
self.classifier = nn.Sequential(
|
|
233
|
+
nn.Linear(hidden_channels * num_layers, hidden_channels),
|
|
234
|
+
nn.ReLU(),
|
|
235
|
+
nn.Dropout(dropout),
|
|
236
|
+
nn.Linear(hidden_channels, out_channels),
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
def forward(self, x: torch.Tensor, edge_index: torch.Tensor,
|
|
240
|
+
batch: torch.Tensor) -> torch.Tensor:
|
|
241
|
+
layer_outputs: list[torch.Tensor] = []
|
|
242
|
+
for conv, bn in zip(self.convs, self.bns):
|
|
243
|
+
x = F.relu(bn(conv(x, edge_index)))
|
|
244
|
+
x = F.dropout(x, p=self.dropout, training=self.training)
|
|
245
|
+
# Global pooling at each layer (Jumping Knowledge)
|
|
246
|
+
layer_outputs.append(global_add_pool(x, batch))
|
|
247
|
+
|
|
248
|
+
# Concatenate all layers' pooled outputs
|
|
249
|
+
x = torch.cat(layer_outputs, dim=1)
|
|
250
|
+
return self.classifier(x)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
# ══════════════════════════════════════════════════════════════════════════════
|
|
254
|
+
# Training helpers
|
|
255
|
+
# ══════════════════════════════════════════════════════════════════════════════
|
|
256
|
+
|
|
257
|
+
def train_epoch(model: nn.Module, loader: "DataLoader",
|
|
258
|
+
optimizer: "torch.optim.Optimizer",
|
|
259
|
+
criterion: nn.Module, device: str) -> float:
|
|
260
|
+
model.train()
|
|
261
|
+
total_loss = 0.0
|
|
262
|
+
for data in loader:
|
|
263
|
+
data = data.to(device)
|
|
264
|
+
optimizer.zero_grad()
|
|
265
|
+
if model.__class__.__name__ == "GIN" or getattr(model, "task", "") == "graph":
|
|
266
|
+
out = model(data.x, data.edge_index, data.batch)
|
|
267
|
+
else:
|
|
268
|
+
out = model(data.x, data.edge_index)
|
|
269
|
+
if hasattr(data, "train_mask"):
|
|
270
|
+
out = out[data.train_mask]
|
|
271
|
+
target = data.y[data.train_mask]
|
|
272
|
+
else:
|
|
273
|
+
target = data.y
|
|
274
|
+
loss = criterion(out, target)
|
|
275
|
+
loss.backward()
|
|
276
|
+
optimizer.step()
|
|
277
|
+
total_loss += float(loss)
|
|
278
|
+
continue
|
|
279
|
+
loss = criterion(out, data.y)
|
|
280
|
+
loss.backward()
|
|
281
|
+
optimizer.step()
|
|
282
|
+
total_loss += float(loss)
|
|
283
|
+
return total_loss / len(loader)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
@torch.no_grad()
|
|
287
|
+
def evaluate(model: nn.Module, loader: "DataLoader", device: str) -> float:
|
|
288
|
+
model.eval()
|
|
289
|
+
correct = total = 0
|
|
290
|
+
for data in loader:
|
|
291
|
+
data = data.to(device)
|
|
292
|
+
if model.__class__.__name__ == "GIN" or getattr(model, "task", "") == "graph":
|
|
293
|
+
out = model(data.x, data.edge_index, data.batch)
|
|
294
|
+
pred = out.argmax(dim=1)
|
|
295
|
+
correct += int((pred == data.y).sum())
|
|
296
|
+
total += data.y.size(0)
|
|
297
|
+
else:
|
|
298
|
+
out = model(data.x, data.edge_index)
|
|
299
|
+
if hasattr(data, "test_mask"):
|
|
300
|
+
pred = out[data.test_mask].argmax(dim=1)
|
|
301
|
+
correct += int((pred == data.y[data.test_mask]).sum())
|
|
302
|
+
total += int(data.test_mask.sum())
|
|
303
|
+
else:
|
|
304
|
+
pred = out.argmax(dim=1)
|
|
305
|
+
correct += int((pred == data.y).sum())
|
|
306
|
+
total += data.y.size(0)
|
|
307
|
+
return correct / total if total > 0 else 0.0
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
# ══════════════════════════════════════════════════════════════════════════════
|
|
311
|
+
# Architecture selection guide
|
|
312
|
+
# ══════════════════════════════════════════════════════════════════════════════
|
|
313
|
+
|
|
314
|
+
ARCHITECTURE_GUIDE = """
|
|
315
|
+
GNN Architecture Selection Guide — BMAD DL Lifecycle
|
|
316
|
+
─────────────────────────────────────────────────────
|
|
317
|
+
Task: Node classification
|
|
318
|
+
Homophilic graph (similar nodes connected) → GCN
|
|
319
|
+
Attention needed (noisy neighbors) → GAT
|
|
320
|
+
Large / dynamic / inductive graph → GraphSAGE
|
|
321
|
+
|
|
322
|
+
Task: Graph classification
|
|
323
|
+
Standard accuracy → GraphSAGE or GCN + global pool
|
|
324
|
+
Maximum expressiveness → GIN (recommended)
|
|
325
|
+
Edge features matter → GAT with edge_attr
|
|
326
|
+
|
|
327
|
+
Quick model size guide:
|
|
328
|
+
Small dataset (<1K graphs) → 2 layers, hidden=64
|
|
329
|
+
Medium dataset (1K–50K) → 3 layers, hidden=128
|
|
330
|
+
Large dataset (50K+) → 4-5 layers, hidden=256, mini-batch DataLoader
|
|
331
|
+
|
|
332
|
+
Typical hyperparameter ranges:
|
|
333
|
+
hidden_channels: 64, 128, 256
|
|
334
|
+
num_layers: 2, 3, 4
|
|
335
|
+
dropout: 0.3 – 0.6
|
|
336
|
+
heads (GAT): 4, 8
|
|
337
|
+
learning_rate: 0.001 – 0.01
|
|
338
|
+
"""
|
|
339
|
+
|
|
340
|
+
if __name__ == "__main__":
|
|
341
|
+
print(ARCHITECTURE_GUIDE)
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""
|
|
2
|
+
template_lightning_module.py — BMAD DL Lifecycle
|
|
3
|
+
PyTorch Lightning LightningModule template for supervised classification/regression.
|
|
4
|
+
|
|
5
|
+
Drop this file into your project and fill in the TODO sections.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
Copy to src/models/your_model.py and implement:
|
|
9
|
+
- __init__: define layers
|
|
10
|
+
- forward: define forward pass
|
|
11
|
+
- _shared_step: compute loss + metrics for any split
|
|
12
|
+
The training/validation/test steps call _shared_step automatically.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
import torch
|
|
20
|
+
import torch.nn as nn
|
|
21
|
+
import torch.nn.functional as F
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
import lightning as L
|
|
25
|
+
LightningModule = L.LightningModule
|
|
26
|
+
except ImportError:
|
|
27
|
+
try:
|
|
28
|
+
import pytorch_lightning as pl
|
|
29
|
+
LightningModule = pl.LightningModule
|
|
30
|
+
except ImportError:
|
|
31
|
+
raise ImportError(
|
|
32
|
+
"Install PyTorch Lightning: pip install lightning\n"
|
|
33
|
+
" or: pip install pytorch-lightning"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class YourModel(LightningModule):
|
|
38
|
+
"""
|
|
39
|
+
Template LightningModule for image/tabular classification or regression.
|
|
40
|
+
|
|
41
|
+
Replace 'YourModel' with a descriptive name (e.g. DefectClassifier, FruitNet).
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
num_classes: Number of output classes (use 1 for binary/regression).
|
|
45
|
+
learning_rate: Initial learning rate for the optimizer.
|
|
46
|
+
weight_decay: L2 regularization weight.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
num_classes: int = 2,
|
|
52
|
+
learning_rate: float = 1e-3,
|
|
53
|
+
weight_decay: float = 1e-4,
|
|
54
|
+
):
|
|
55
|
+
super().__init__()
|
|
56
|
+
# Saves all __init__ args to self.hparams (enables checkpointing)
|
|
57
|
+
self.save_hyperparameters()
|
|
58
|
+
|
|
59
|
+
# ── TODO: Define your model architecture ──────────────────────────────
|
|
60
|
+
# Example: simple two-layer MLP for tabular data
|
|
61
|
+
self.encoder = nn.Sequential(
|
|
62
|
+
nn.Linear(128, 64), # TODO: replace 128 with your input dim
|
|
63
|
+
nn.BatchNorm1d(64),
|
|
64
|
+
nn.ReLU(),
|
|
65
|
+
nn.Dropout(0.3),
|
|
66
|
+
)
|
|
67
|
+
self.classifier = nn.Linear(64, num_classes)
|
|
68
|
+
# ── END TODO ──────────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
# Loss function
|
|
71
|
+
if num_classes == 1:
|
|
72
|
+
self.loss_fn = nn.BCEWithLogitsLoss()
|
|
73
|
+
else:
|
|
74
|
+
self.loss_fn = nn.CrossEntropyLoss()
|
|
75
|
+
|
|
76
|
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
77
|
+
"""
|
|
78
|
+
Forward pass.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
x: Input tensor, shape depends on your architecture.
|
|
82
|
+
Returns:
|
|
83
|
+
Logits tensor of shape (batch, num_classes) or (batch, 1).
|
|
84
|
+
"""
|
|
85
|
+
# ── TODO: Implement forward pass ──────────────────────────────────────
|
|
86
|
+
features = self.encoder(x)
|
|
87
|
+
logits = self.classifier(features)
|
|
88
|
+
return logits
|
|
89
|
+
# ── END TODO ──────────────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
def _shared_step(self, batch: Any, stage: str) -> torch.Tensor:
|
|
92
|
+
"""
|
|
93
|
+
Common logic for train/val/test.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
batch: Tuple of (inputs, labels) from your DataLoader.
|
|
97
|
+
stage: One of "train", "val", "test".
|
|
98
|
+
Returns:
|
|
99
|
+
Loss tensor.
|
|
100
|
+
"""
|
|
101
|
+
# ── TODO: Unpack batch to match your DataLoader output ────────────────
|
|
102
|
+
x, y = batch # e.g. (images, labels) or (features, targets)
|
|
103
|
+
# ── END TODO ──────────────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
logits = self(x)
|
|
106
|
+
|
|
107
|
+
if self.hparams.num_classes == 1:
|
|
108
|
+
loss = self.loss_fn(logits.squeeze(1), y.float())
|
|
109
|
+
preds = (torch.sigmoid(logits.squeeze(1)) > 0.5).long()
|
|
110
|
+
else:
|
|
111
|
+
loss = self.loss_fn(logits, y.long())
|
|
112
|
+
preds = logits.argmax(dim=1)
|
|
113
|
+
|
|
114
|
+
acc = (preds == y).float().mean()
|
|
115
|
+
|
|
116
|
+
# Log metrics — appears in TensorBoard / W&B / CSV logger
|
|
117
|
+
self.log(f"{stage}/loss", loss, prog_bar=(stage == "val"), on_step=False, on_epoch=True)
|
|
118
|
+
self.log(f"{stage}/acc", acc, prog_bar=True, on_step=False, on_epoch=True)
|
|
119
|
+
|
|
120
|
+
return loss
|
|
121
|
+
|
|
122
|
+
# ── Lightning hooks (do not rename these) ─────────────────────────────────
|
|
123
|
+
|
|
124
|
+
def training_step(self, batch: Any, batch_idx: int) -> torch.Tensor:
|
|
125
|
+
loss = self._shared_step(batch, "train")
|
|
126
|
+
if torch.cuda.is_available():
|
|
127
|
+
self.log("gpu/memory_allocated_gb", torch.cuda.memory_allocated() / 1e9,
|
|
128
|
+
on_step=True, on_epoch=False, prog_bar=False)
|
|
129
|
+
return loss
|
|
130
|
+
|
|
131
|
+
def validation_step(self, batch: Any, batch_idx: int) -> None:
|
|
132
|
+
self._shared_step(batch, "val")
|
|
133
|
+
|
|
134
|
+
def test_step(self, batch: Any, batch_idx: int) -> None:
|
|
135
|
+
self._shared_step(batch, "test")
|
|
136
|
+
|
|
137
|
+
def configure_optimizers(self) -> dict:
|
|
138
|
+
"""
|
|
139
|
+
Set up optimizer and learning rate scheduler.
|
|
140
|
+
Swap optimizer or scheduler as needed.
|
|
141
|
+
"""
|
|
142
|
+
optimizer = torch.optim.AdamW(
|
|
143
|
+
self.parameters(),
|
|
144
|
+
lr=self.hparams.learning_rate,
|
|
145
|
+
weight_decay=self.hparams.weight_decay,
|
|
146
|
+
)
|
|
147
|
+
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(
|
|
148
|
+
optimizer,
|
|
149
|
+
T_max=10, # TODO: set to your total_epochs
|
|
150
|
+
eta_min=1e-6,
|
|
151
|
+
)
|
|
152
|
+
return {
|
|
153
|
+
"optimizer": optimizer,
|
|
154
|
+
"lr_scheduler": {
|
|
155
|
+
"scheduler": scheduler,
|
|
156
|
+
"monitor": "val/loss",
|
|
157
|
+
},
|
|
158
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ML Experiment Execution",
|
|
3
|
+
"description": "Executes training runs against a locked TECHSPEC and logs metrics to W&B/MLflow/ClearML.",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"author": "Demerzel (ML Scientist)",
|
|
6
|
+
"tags": ["Machine Learning", "Experiment", "Training", "Logging", "Demerzel"]
|
|
7
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
|
|
3
|
+
name: ml-hparam
|
|
4
|
+
|
|
5
|
+
description: Acts as Demerzel (Machine Learning Scientist) to run structured hyperparameter optimization after a baseline architecture is confirmed to work. Uses Optuna, W&B Sweeps, or Ray Tune. Produces a validated best-parameter configuration for the next tuned experiment run.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Machine Learning Workflow: Hyperparameter Optimization (Conditional) — Demerzel
|
|
10
|
+
|
|
11
|
+
## 1. Operating Instructions
|
|
12
|
+
|
|
13
|
+
You are **Demerzel**, an expert Machine Learning Scientist running structured hyperparameter search. **This stage is conditional.** Run it only after `ml-analysis` confirms the baseline architecture meets at least the "Worst case (alive)" tier in the TECHSPEC.
|
|
14
|
+
|
|
15
|
+
Your goal is to find the optimal parameter configuration within the search space defined in the TECHSPEC, then hand off validated parameters to the next `ml-experiment` run.
|
|
16
|
+
|
|
17
|
+
1. **Verify the prerequisite:** Read the latest `ml-analysis-exp-[id].md`. Confirm: "Worst case (alive)" tier or better was reached. If not, recommend running `ml-revision` instead.
|
|
18
|
+
|
|
19
|
+
2. **Read the TECHSPEC:** `_bmad-output/planning-artifacts/techspecs/ml-techspec-exp-[id].md` — use Section C as the HPO search space.
|
|
20
|
+
|
|
21
|
+
3. **Run the advisor:** `/ml-advise` — check if any past HPO runs exist.
|
|
22
|
+
|
|
23
|
+
4. **Run the HPO search:** (Optuna, W&B Sweeps, or Ray Tune).
|
|
24
|
+
- Define objective: Maximize the primary metric from the PRD.
|
|
25
|
+
- Use early stopping to save budget.
|
|
26
|
+
- Example (Optuna):
|
|
27
|
+
```python
|
|
28
|
+
import optuna
|
|
29
|
+
def objective(trial):
|
|
30
|
+
lr = trial.suggest_float("lr", 1e-5, 1e-2, log=True)
|
|
31
|
+
# train and return f1
|
|
32
|
+
return f1
|
|
33
|
+
study = optuna.create_study(direction="maximize")
|
|
34
|
+
study.optimize(objective, n_trials=50)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
5. **CRITICAL:** Do not write the HPO report yet. Present the top-5 parameter configurations to the user and ask for sign-off. Halt and wait.
|
|
38
|
+
|
|
39
|
+
6. Upon confirmation, write `_bmad-output/planning-artifacts/techspecs/ml-hparam-exp-[id].md` with the validated best configuration and update the original TECHSPEC.
|
|
40
|
+
|
|
41
|
+
7. **Commit the HPO artifact:**
|
|
42
|
+
```bash
|
|
43
|
+
git add _bmad-output/planning-artifacts/techspecs/ml-hparam-exp-[id].md
|
|
44
|
+
git commit -m "docs(ml-hparam): validated best config for EXP-[id] val/f1=[score]"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 2. Expected Output Template
|
|
48
|
+
|
|
49
|
+
### Template A: `_bmad-output/planning-artifacts/techspecs/ml-hparam-exp-[id].md`
|
|
50
|
+
|
|
51
|
+
```markdown
|
|
52
|
+
# HPO Results: EXP-[ID]
|
|
53
|
+
|
|
54
|
+
## A. Search Summary
|
|
55
|
+
* **Linked Experiment:** EXP-[ID]
|
|
56
|
+
* **HPO Tool:** [Optuna / W&B Sweeps / Ray Tune]
|
|
57
|
+
* **Sweep URL:** [link]
|
|
58
|
+
|
|
59
|
+
## B. Top Configurations
|
|
60
|
+
| Rank | lr | batch_size | dropout | val/f1 | Run URL |
|
|
61
|
+
| :--- | :--- | :--- | :--- | :--- | :--- |
|
|
62
|
+
| 1 (best) | 2.3e-4 | 1024 | 0.22 | 0.94 | [link] |
|
|
63
|
+
|
|
64
|
+
## C. Parameter Importance
|
|
65
|
+
* [Which params had highest impact from study analysis.]
|
|
66
|
+
|
|
67
|
+
## D. Validated Best Configuration (copy-paste ready)
|
|
68
|
+
```yaml
|
|
69
|
+
learning_rate: 2.3e-4
|
|
70
|
+
batch_size: 1024
|
|
71
|
+
dropout: 0.22
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## E. Scientist Sign-off
|
|
75
|
+
* [ ] Best params are within real-world acceptable ranges.
|
|
76
|
+
* [ ] No anomalous values.
|
|
77
|
+
* **Signed off by:** [Demerzel / Date]
|
|
78
|
+
|
|
79
|
+
## F. Next Step
|
|
80
|
+
* Run `/ml-experiment` with run_type="tuned" using the above config.
|
|
81
|
+
```
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ML Hyperparameter Optimization",
|
|
3
|
+
"description": "Runs structured HPO using Optuna, W&B Sweeps, or Ray Tune after a baseline is confirmed.",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"author": "Demerzel (ML Scientist)",
|
|
6
|
+
"tags": ["Machine Learning", "HPO", "Optimization", "Optuna", "Sweeps", "Demerzel"]
|
|
7
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ml-ideation
|
|
3
|
+
description: ML Ideation — Frame the research problem, define success criteria, and produce a Machine Learning PRD
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ML Stage 1 — Ideation & PRD
|
|
7
|
+
|
|
8
|
+
Frame the ML problem rigorously before any data or modelling work begins.
|
|
9
|
+
|
|
10
|
+
## Instructions
|
|
11
|
+
|
|
12
|
+
### 1. Elicit Problem Context
|
|
13
|
+
Ask the user for (or extract from context):
|
|
14
|
+
- **Business problem**: What decision or process will the model improve?
|
|
15
|
+
- **Target variable**: What are we predicting? (classification / regression / ranking / generation)
|
|
16
|
+
- **Failure cost asymmetry**: What is the cost of a false negative vs a false positive in this domain?
|
|
17
|
+
- **Success definition**: What metric threshold constitutes a production-ready model?
|
|
18
|
+
- **Data availability**: What raw datasets exist and where are they located?
|
|
19
|
+
|
|
20
|
+
### 2. Produce Research Thesis
|
|
21
|
+
Write `_bmad-output/planning-artifacts/research-thesis.md` with:
|
|
22
|
+
- **Hypothesis**: A single falsifiable statement (e.g. "We can predict X with >Y recall using features A, B, C")
|
|
23
|
+
- **Assumptions**: List all assumptions that must hold for the hypothesis to be testable
|
|
24
|
+
- **Risks**: Top 3 risks that could invalidate the hypothesis (data quality, label noise, distribution shift)
|
|
25
|
+
- **Null Hypothesis**: The baseline we must beat (random, heuristic, or existing system)
|
|
26
|
+
|
|
27
|
+
### 3. Produce ML PRD
|
|
28
|
+
Write `_bmad-output/planning-artifacts/ml-prd.md` with sections:
|
|
29
|
+
- **Problem Statement** (1 paragraph)
|
|
30
|
+
- **Stakeholders & Users**
|
|
31
|
+
- **Success Metrics** (primary metric, secondary metrics, guardrail metrics)
|
|
32
|
+
- **Failure Cost Matrix** (FP cost vs FN cost with domain justification)
|
|
33
|
+
- **Data Requirements** (source, volume, freshness, labelling)
|
|
34
|
+
- **Out of Scope** (explicit non-goals)
|
|
35
|
+
- **Dependencies** (upstream data pipelines, external APIs)
|
|
36
|
+
|
|
37
|
+
### 4. Surface Dilemmas & Commit Gate
|
|
38
|
+
|
|
39
|
+
Before presenting and **before any git commit**:
|
|
40
|
+
|
|
41
|
+
- Identify every framing choice where two or more reasonable options existed (metric threshold, failure cost ratio, scope boundary, two-stage vs single-stage, etc.)
|
|
42
|
+
- Format each as: **Dilemma [Letter] — Title** / **Context** / **Options (a/b)** / **Recommendation** / **Your decision:** [blank]
|
|
43
|
+
- If all choices were unambiguous, state explicitly: "No open dilemmas."
|
|
44
|
+
- **Do NOT commit any artifact until the user has responded and given explicit approval.**
|
|
45
|
+
|
|
46
|
+
### 5. Confirm & Advance
|
|
47
|
+
- Present both documents to the user for review
|
|
48
|
+
- Ask: "Do you approve this framing, or would you like to adjust the hypothesis or success criteria?"
|
|
49
|
+
- On approval: commit artifacts, then say "Stage 1 complete. When ready, proceed to **Stage 2 — /ml-eda** to analyze the raw data."
|
|
50
|
+
- STOP and WAIT for user confirmation before advancing
|