bigpowers 2.76.3 → 2.77.1
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/.pi/package.json +2 -2
- package/.pi/prompts/audit-code.md +1 -1
- package/.pi/prompts/build-epic.md +10 -0
- package/.pi/prompts/craft-skill.md +2 -2
- package/.pi/prompts/elaborate-spec.md +1 -1
- package/.pi/prompts/evolve-skill.md +1 -1
- package/.pi/prompts/generate-allure-report.md +284 -0
- package/.pi/prompts/grill-me.md +1 -1
- package/.pi/prompts/guard-git.md +1 -1
- package/.pi/prompts/harden-vps.md +287 -0
- package/.pi/prompts/investigate-bug.md +2 -2
- package/.pi/prompts/model-domain.md +3 -3
- package/.pi/prompts/plan-refactor.md +2 -2
- package/.pi/prompts/plan-release.md +5 -10
- package/.pi/prompts/plan-work.md +14 -1
- package/.pi/prompts/using-bigpowers.md +1 -1
- package/.pi/prompts/write-document.md +2 -2
- package/.pi/skills/audit-code/SKILL.md +1 -1
- package/.pi/skills/build-epic/SKILL.md +10 -0
- package/.pi/skills/change-request/SKILL.md +1 -1
- package/.pi/skills/commit-message/SKILL.md +3 -3
- package/.pi/skills/compose-workflow/SKILL.md +1 -1
- package/.pi/skills/craft-skill/SKILL.md +4 -4
- package/.pi/skills/deepen-architecture/SKILL.md +13 -13
- package/.pi/skills/deploy/SKILL.md +4 -4
- package/.pi/skills/develop-tdd/SKILL.md +5 -5
- package/.pi/skills/elaborate-spec/SKILL.md +1 -1
- package/.pi/skills/evolve-skill/SKILL.md +2 -2
- package/.pi/skills/extract-design/SKILL.md +1 -1
- package/.pi/skills/generate-allure-report/SKILL.md +285 -0
- package/.pi/skills/grill-me/SKILL.md +2 -2
- package/.pi/skills/grill-with-docs/SKILL.md +1 -1
- package/.pi/skills/guard-git/SKILL.md +5 -5
- package/.pi/skills/harden-vps/SKILL.md +288 -0
- package/.pi/skills/investigate-bug/SKILL.md +2 -2
- package/.pi/skills/migrate-spec/SKILL.md +13 -13
- package/.pi/skills/model-domain/SKILL.md +5 -5
- package/.pi/skills/orchestrate-project/SKILL.md +2 -2
- package/.pi/skills/organize-workspace/SKILL.md +1 -1
- package/.pi/skills/plan-refactor/SKILL.md +2 -2
- package/.pi/skills/plan-release/SKILL.md +5 -10
- package/.pi/skills/plan-work/SKILL.md +15 -2
- package/.pi/skills/publish-package/SKILL.md +8 -8
- package/.pi/skills/release-branch/SKILL.md +2 -2
- package/.pi/skills/research-first/SKILL.md +1 -1
- package/.pi/skills/run-evals/SKILL.md +1 -1
- package/.pi/skills/security-review/SKILL.md +3 -3
- package/.pi/skills/seed-conventions/SKILL.md +3 -3
- package/.pi/skills/smoke-test/SKILL.md +5 -5
- package/.pi/skills/stocktake-skills/SKILL.md +1 -1
- package/.pi/skills/using-bigpowers/SKILL.md +1 -1
- package/.pi/skills/validate-contracts/SKILL.md +6 -6
- package/.pi/skills/verify-work/SKILL.md +1 -1
- package/.pi/skills/wire-ci/SKILL.md +8 -8
- package/.pi/skills/write-document/SKILL.md +3 -3
- package/CHANGELOG.md +14 -0
- package/README.md +1 -1
- package/SKILL-INDEX.md +1 -1
- package/allure-results/categories.json +23 -0
- package/allure-results/executor.json +6 -0
- package/allure-results/junit-results.xml +2743 -0
- package/package.json +4 -3
- package/scripts/adapters/pi.sh +4 -1
- package/scripts/check-skill-links.py +78 -0
- package/scripts/generate-allure-report.sh +146 -0
- package/scripts/generate-reference-tables.sh +2 -1
- package/scripts/lib/__pycache__/link_utils.cpython-312.pyc +0 -0
- package/scripts/lib/link_utils.py +49 -0
- package/scripts/lib/srp-engine.py +86 -1
- package/scripts/record-cycle-time.sh +28 -0
- package/scripts/sync-status-from-epics.sh +247 -24
- package/scripts/validate-skill-catalog.sh +1 -1
- package/skills/audit-code/SKILL.md +1 -1
- package/skills/build-epic/SKILL.md +10 -0
- package/skills/craft-skill/SKILL.md +2 -2
- package/skills/elaborate-spec/SKILL.md +1 -1
- package/skills/evolve-skill/REFERENCE.md +1 -1
- package/skills/generate-allure-report/REFERENCE.md +243 -0
- package/skills/generate-allure-report/SKILL.md +40 -0
- package/skills/grill-me/REFERENCE.md +1 -1
- package/skills/guard-git/SKILL.md +1 -1
- package/skills/harden-vps/REFERENCE.md +179 -0
- package/skills/harden-vps/SKILL.md +107 -0
- package/skills/investigate-bug/SKILL.md +2 -2
- package/skills/model-domain/CONTEXT-FORMAT.md +3 -3
- package/skills/plan-refactor/SKILL.md +2 -2
- package/skills/plan-release/SKILL.md +5 -10
- package/skills/plan-work/REFERENCE.md +13 -0
- package/skills/plan-work/SKILL.md +1 -1
- package/skills/using-bigpowers/SKILL.md +1 -1
- package/skills/write-document/REFERENCE.md +2 -2
- package/skills-lock.json +20 -10
- package/tests/test_srp_engine.py +148 -0
|
@@ -12,6 +12,32 @@ $PYTHON - "$EPICS" "$OUT" <<'PY'
|
|
|
12
12
|
import re
|
|
13
13
|
import sys
|
|
14
14
|
from pathlib import Path
|
|
15
|
+
import yaml
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _yaml_str(s: str) -> str:
|
|
19
|
+
"""Return a YAML-safe quoted string, using single quotes when needed."""
|
|
20
|
+
if not s:
|
|
21
|
+
return "''"
|
|
22
|
+
if any(c in s for c in ":{}[]&*?|>-;!%@`,\"\n\t#'"):
|
|
23
|
+
escaped = s.replace("'", "''")
|
|
24
|
+
return f"'{escaped}'"
|
|
25
|
+
if s.startswith(" ") or s.endswith(" ") or s[0] in "0123456789":
|
|
26
|
+
escaped = s.replace("'", "''")
|
|
27
|
+
return f"'{escaped}'"
|
|
28
|
+
return s
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _yaml_val(v) -> str:
|
|
32
|
+
"""Return a YAML-safe scalar representation."""
|
|
33
|
+
if v is None:
|
|
34
|
+
return "null"
|
|
35
|
+
if isinstance(v, bool):
|
|
36
|
+
return "true" if v else "false"
|
|
37
|
+
if isinstance(v, (int, float)):
|
|
38
|
+
return str(v)
|
|
39
|
+
return _yaml_str(str(v))
|
|
40
|
+
|
|
15
41
|
|
|
16
42
|
epics_dir = Path(sys.argv[1])
|
|
17
43
|
out = Path(sys.argv[2])
|
|
@@ -23,34 +49,231 @@ if existing_path.exists():
|
|
|
23
49
|
for m in re.finditer(r"^ ([a-z0-9._-]+):\s*(\S+)", existing, re.M):
|
|
24
50
|
keys[m.group(1)] = m.group(2)
|
|
25
51
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
# --- Parse release-plan.yaml for wsjf/tier per epic ---
|
|
53
|
+
rp_epics: dict[str, dict[str, object]] = {}
|
|
54
|
+
rp_path = epics_dir.parent / "release-plan.yaml"
|
|
55
|
+
if rp_path.exists():
|
|
56
|
+
try:
|
|
57
|
+
rp = yaml.safe_load(rp_path.read_text(encoding="utf-8"))
|
|
58
|
+
for ep in (rp.get("epics") or []):
|
|
59
|
+
eid = ep.get("id")
|
|
60
|
+
if not eid:
|
|
61
|
+
continue
|
|
62
|
+
entry: dict[str, object] = {}
|
|
63
|
+
if "wsjf" in ep:
|
|
64
|
+
entry["wsjf"] = ep["wsjf"]
|
|
65
|
+
if "tier" in ep:
|
|
66
|
+
entry["tier"] = ep["tier"]
|
|
67
|
+
rp_epics[eid] = entry
|
|
68
|
+
except Exception:
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
# --- Collect all epic sources (capsule dirs + flat YAML) ---
|
|
72
|
+
epic_sources: list[tuple[str, Path]] = []
|
|
73
|
+
seen_eids: set[str] = set()
|
|
74
|
+
|
|
75
|
+
# Search roots: epics_dir itself, plus immediate subdirectories (e.g., archive/)
|
|
76
|
+
search_roots = [epics_dir] if epics_dir.is_dir() else []
|
|
77
|
+
for sub in sorted(epics_dir.iterdir()) if epics_dir.is_dir() else []:
|
|
78
|
+
if sub.is_dir():
|
|
79
|
+
search_roots.append(sub)
|
|
80
|
+
|
|
81
|
+
for root in search_roots:
|
|
82
|
+
for capsule in sorted(root.glob("e*/")):
|
|
83
|
+
ey = capsule / "epic.yaml"
|
|
84
|
+
if ey.exists():
|
|
85
|
+
try:
|
|
86
|
+
ed = yaml.safe_load(ey.read_text(encoding="utf-8"))
|
|
87
|
+
eid = ed.get("id")
|
|
88
|
+
if eid and eid not in seen_eids:
|
|
89
|
+
epic_sources.append((eid, ey))
|
|
90
|
+
seen_eids.add(eid)
|
|
91
|
+
except Exception:
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
for epic_file in sorted(root.glob("e*.yaml")):
|
|
95
|
+
if re.search(r"e\d+s\d+", epic_file.stem):
|
|
96
|
+
continue
|
|
97
|
+
try:
|
|
98
|
+
ed = yaml.safe_load(epic_file.read_text(encoding="utf-8"))
|
|
99
|
+
eid = ed.get("id")
|
|
100
|
+
if eid and eid not in seen_eids:
|
|
101
|
+
epic_sources.append((eid, epic_file))
|
|
102
|
+
seen_eids.add(eid)
|
|
103
|
+
except Exception:
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
# --- Build enriched epic + story data ---
|
|
107
|
+
epics_output: dict[str, dict[str, object]] = {}
|
|
108
|
+
stories_output: dict[str, dict[str, object]] = {}
|
|
109
|
+
|
|
110
|
+
risk_rank = {"P0": 4, "P1": 3, "P2": 2, "P3": 1}
|
|
111
|
+
sec_rank = {"high": 4, "medium": 3, "low": 2, "none": 1}
|
|
112
|
+
|
|
113
|
+
for eid, epic_path in sorted(epic_sources):
|
|
114
|
+
try:
|
|
115
|
+
ed = yaml.safe_load(epic_path.read_text(encoding="utf-8"))
|
|
116
|
+
except Exception:
|
|
117
|
+
continue
|
|
118
|
+
keys.setdefault(eid, "backlog")
|
|
119
|
+
|
|
120
|
+
title = ed.get("title", "")
|
|
121
|
+
total_bcps = ed.get("total_bcps") or ed.get("bcps")
|
|
122
|
+
|
|
123
|
+
epics_output[eid] = {
|
|
124
|
+
"status": keys[eid],
|
|
125
|
+
"title": title,
|
|
126
|
+
"wsjf": rp_epics.get(eid, {}).get("wsjf"),
|
|
127
|
+
"total_bcps": total_bcps,
|
|
128
|
+
}
|
|
129
|
+
if "tier" in rp_epics.get(eid, {}):
|
|
130
|
+
epics_output[eid]["tier"] = rp_epics[eid]["tier"]
|
|
131
|
+
|
|
132
|
+
capsule_dir = epic_path.parent
|
|
133
|
+
stories = ed.get("stories") or []
|
|
49
134
|
|
|
135
|
+
for story in stories:
|
|
136
|
+
if not isinstance(story, dict):
|
|
137
|
+
continue
|
|
138
|
+
sid = story.get("id")
|
|
139
|
+
if not sid or not re.match(r"^e\d+s\d+$", str(sid)):
|
|
140
|
+
continue
|
|
141
|
+
keys.setdefault(sid, "backlog")
|
|
142
|
+
|
|
143
|
+
story_bcps = story.get("bcp") or story.get("bcps")
|
|
144
|
+
stories_output[sid] = {
|
|
145
|
+
"status": keys[sid],
|
|
146
|
+
"title": story.get("title", ""),
|
|
147
|
+
"bcps": story_bcps,
|
|
148
|
+
"epic": eid,
|
|
149
|
+
"tasks_total": 0,
|
|
150
|
+
"tasks_passing": 0,
|
|
151
|
+
"tasks_failing": 0,
|
|
152
|
+
"risk_max": None,
|
|
153
|
+
"security_max": None,
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
# Look for -tasks.yaml at capsule root
|
|
157
|
+
tasks_file = None
|
|
158
|
+
for pattern in [f"{sid}-tasks.yaml", f"{sid}*.yaml"]:
|
|
159
|
+
for tf in sorted(capsule_dir.glob(pattern)):
|
|
160
|
+
try:
|
|
161
|
+
tf_data = yaml.safe_load(tf.read_text(encoding="utf-8"))
|
|
162
|
+
if isinstance(tf_data, dict) and "tasks" in tf_data:
|
|
163
|
+
tasks_file = tf
|
|
164
|
+
break
|
|
165
|
+
except Exception:
|
|
166
|
+
pass
|
|
167
|
+
if tasks_file:
|
|
168
|
+
break
|
|
169
|
+
|
|
170
|
+
if tasks_file:
|
|
171
|
+
try:
|
|
172
|
+
tf_data = yaml.safe_load(tasks_file.read_text(encoding="utf-8"))
|
|
173
|
+
tasks = tf_data.get("tasks") or []
|
|
174
|
+
|
|
175
|
+
stories_output[sid]["tasks_total"] = len(tasks)
|
|
176
|
+
passing = sum(
|
|
177
|
+
1 for t in tasks
|
|
178
|
+
if isinstance(t, dict) and t.get("status") in ("done", "passing")
|
|
179
|
+
)
|
|
180
|
+
failing = sum(
|
|
181
|
+
1 for t in tasks
|
|
182
|
+
if isinstance(t, dict) and t.get("status") == "failing"
|
|
183
|
+
)
|
|
184
|
+
stories_output[sid]["tasks_passing"] = passing
|
|
185
|
+
stories_output[sid]["tasks_failing"] = failing
|
|
186
|
+
|
|
187
|
+
# Risk max: check individual tasks + story-level
|
|
188
|
+
max_risk_val = None
|
|
189
|
+
max_risk_rank = 0
|
|
190
|
+
for t in tasks:
|
|
191
|
+
tr = t.get("risk") if isinstance(t, dict) else None
|
|
192
|
+
if tr in risk_rank and risk_rank[tr] > max_risk_rank:
|
|
193
|
+
max_risk_rank = risk_rank[tr]
|
|
194
|
+
max_risk_val = tr
|
|
195
|
+
sr = tf_data.get("risk")
|
|
196
|
+
if sr in risk_rank and risk_rank[sr] > max_risk_rank:
|
|
197
|
+
max_risk_val = sr
|
|
198
|
+
stories_output[sid]["risk_max"] = max_risk_val
|
|
199
|
+
|
|
200
|
+
# Security max: check individual tasks + story-level
|
|
201
|
+
max_sec_val = None
|
|
202
|
+
max_sec_rank = 0
|
|
203
|
+
for t in tasks:
|
|
204
|
+
ts = t.get("security") if isinstance(t, dict) else None
|
|
205
|
+
if ts in sec_rank and sec_rank[ts] > max_sec_rank:
|
|
206
|
+
max_sec_rank = sec_rank[ts]
|
|
207
|
+
max_sec_val = ts
|
|
208
|
+
ss = tf_data.get("security")
|
|
209
|
+
if ss in sec_rank and sec_rank[ss] > max_sec_rank:
|
|
210
|
+
max_sec_val = ss
|
|
211
|
+
stories_output[sid]["security_max"] = max_sec_val
|
|
212
|
+
|
|
213
|
+
except Exception:
|
|
214
|
+
pass
|
|
215
|
+
|
|
216
|
+
# Stories/ subdirectory (future-proofing)
|
|
217
|
+
stories_dir = capsule_dir / "stories"
|
|
218
|
+
if stories_dir.is_dir():
|
|
219
|
+
for story_file in sorted(stories_dir.glob("e*s*.md")):
|
|
220
|
+
m = re.match(r"(e\d+s\d+)", story_file.stem)
|
|
221
|
+
if m:
|
|
222
|
+
sid = m.group(1)
|
|
223
|
+
keys.setdefault(sid, "backlog")
|
|
224
|
+
if sid not in stories_output:
|
|
225
|
+
stories_output[sid] = {
|
|
226
|
+
"status": keys[sid],
|
|
227
|
+
"title": "",
|
|
228
|
+
"bcps": None,
|
|
229
|
+
"epic": eid,
|
|
230
|
+
"tasks_total": 0,
|
|
231
|
+
"tasks_passing": 0,
|
|
232
|
+
"tasks_failing": 0,
|
|
233
|
+
"risk_max": None,
|
|
234
|
+
"security_max": None,
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
# --- Build output ---
|
|
50
238
|
lines = ["development_status:"]
|
|
51
239
|
for k in sorted(keys.keys()):
|
|
52
240
|
lines.append(f" {k}: {keys[k]}")
|
|
53
241
|
lines.append("")
|
|
242
|
+
|
|
243
|
+
# --- Epics section ---
|
|
244
|
+
lines.append("epics:")
|
|
245
|
+
for eid in sorted(epics_output.keys()):
|
|
246
|
+
lines.append(f" {eid}:")
|
|
247
|
+
ep = epics_output[eid]
|
|
248
|
+
lines.append(f" status: {_yaml_val(ep['status'])}")
|
|
249
|
+
lines.append(f" title: {_yaml_str(ep.get('title', ''))}")
|
|
250
|
+
if ep.get("wsjf") is not None:
|
|
251
|
+
lines.append(f" wsjf: {ep['wsjf']}")
|
|
252
|
+
if ep.get("tier") is not None:
|
|
253
|
+
lines.append(f" tier: {ep['tier']}")
|
|
254
|
+
if ep.get("total_bcps") is not None:
|
|
255
|
+
lines.append(f" total_bcps: {ep['total_bcps']}")
|
|
256
|
+
lines.append("")
|
|
257
|
+
|
|
258
|
+
# --- Stories section ---
|
|
259
|
+
lines.append("stories:")
|
|
260
|
+
for sid in sorted(stories_output.keys()):
|
|
261
|
+
lines.append(f" {sid}:")
|
|
262
|
+
st = stories_output[sid]
|
|
263
|
+
lines.append(f" status: {_yaml_val(st['status'])}")
|
|
264
|
+
lines.append(f" title: {_yaml_str(st.get('title', ''))}")
|
|
265
|
+
if st.get("bcps") is not None:
|
|
266
|
+
lines.append(f" bcps: {st['bcps']}")
|
|
267
|
+
lines.append(f" epic: {_yaml_val(st['epic'])}")
|
|
268
|
+
lines.append(f" tasks_total: {st['tasks_total']}")
|
|
269
|
+
lines.append(f" tasks_passing: {st['tasks_passing']}")
|
|
270
|
+
lines.append(f" tasks_failing: {st['tasks_failing']}")
|
|
271
|
+
if st.get("risk_max") is not None:
|
|
272
|
+
lines.append(f" risk_max: {_yaml_val(st['risk_max'])}")
|
|
273
|
+
if st.get("security_max") is not None:
|
|
274
|
+
lines.append(f" security_max: {_yaml_val(st['security_max'])}")
|
|
275
|
+
lines.append("")
|
|
276
|
+
|
|
54
277
|
out.write_text("\n".join(lines), encoding="utf-8")
|
|
55
|
-
print(f"sync-status-from-epics: wrote {out} ({len(keys)} keys)")
|
|
278
|
+
print(f"sync-status-from-epics: wrote {out} ({len(keys)} development keys, {len(epics_output)} epics, {len(stories_output)} stories)")
|
|
56
279
|
PY
|
|
@@ -18,7 +18,7 @@ VIOLATIONS=0
|
|
|
18
18
|
WARNINGS=0
|
|
19
19
|
|
|
20
20
|
# Documented naming exceptions (craft-skill REFERENCE.md)
|
|
21
|
-
VERB_NOUN_EXCEPTIONS="grill-me grill-with-docs context7-mcp using-bigpowers deploy"
|
|
21
|
+
VERB_NOUN_EXCEPTIONS="grill-me grill-with-docs context7-mcp using-bigpowers deploy generate-allure-report"
|
|
22
22
|
|
|
23
23
|
catalog_usage() {
|
|
24
24
|
echo "Usage: bash scripts/validate-skill-catalog.sh [--archive] [--strict] [--skill <name>]"
|
|
@@ -31,7 +31,7 @@ Apply the full checklist to churn-ranked files in descending order. Files with z
|
|
|
31
31
|
- Default: full checklist
|
|
32
32
|
- --quick: Run only Supply Chain and Test Coverage. Use for changes under 50 LOC.
|
|
33
33
|
- --gate: Non-interactive mode for automated CI gating (used by build-epic step 6). Exit with non-zero status code (`exit 1`) on ANY checklist failure; `exit 0` only if ALL items pass. Produces a compact pass/fail summary to stderr. On failure, list every ✗ item with reason.
|
|
34
|
-
- --parallel: Run checklist sections in **isolated git worktrees** (e45s18) so concurrent checks cannot corrupt each other's working tree
|
|
34
|
+
- --parallel: Run checklist sections in **isolated git worktrees** (e45s18) so concurrent checks cannot corrupt each other's working tree:
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
37
|
bash scripts/lib/parallel-review-worktrees.sh audit-code
|
|
@@ -35,6 +35,7 @@ Orchestrates the **build** flow for a single epic: survey → plan tasks → kic
|
|
|
35
35
|
## Process
|
|
36
36
|
|
|
37
37
|
1. Read `specs/state.yaml`, `specs/execution-status.yaml`, `specs/release-plan.yaml`, active `specs/epics/eNN-slug/epic.yaml`.
|
|
38
|
+
- **On story start (step 1):** Write `started_at` ISO 8601 timestamp to `execution-status.yaml` under the story's key in the `stories:` section. The `bp-timing.sh` script already tracks skill-level timings — this is story-level.
|
|
38
39
|
2. **Step 0 — Threat Model:** Run `security-review` against the epic's scope (read from the epic capsule). Output `specs/security/epics/<epic-id>/THREAT_MODEL.md` with surface area, vulnerability categories, risk level, and mitigation guidance.
|
|
39
40
|
3. **Assess Impact (Step 2):** Before writing tasks, run `assess-impact --lightweight` on the proposed change. If the risk score exceeds 7, gate — require a `grill-me` session. Write the impact report to `specs/IMPACT-<epic>-<story>.md`. For net-new code with no existing dependents, skip.
|
|
40
41
|
4. **BCP Tracking (Step 2):** After `plan-work` completes, read the `bcps:` count (Business Complexity Points story size) from the epic capsule and carry it into `state.yaml` as `epic_cycle.story_bcps = N`.
|
|
@@ -43,6 +44,15 @@ Orchestrates the **build** flow for a single epic: survey → plan tasks → kic
|
|
|
43
44
|
6. Run **only the current step** (resume mode) unless user asked for full auto-run.
|
|
44
45
|
7. After step verify passes, increment `epic_cycle.step` in `state.yaml` (or `bash scripts/bp-yaml-set.sh` if available).
|
|
45
46
|
8. On story complete, set `execution-status.yaml` story key to `done`; run `bash scripts/sync-status-from-epics.sh`.
|
|
47
|
+
- **On story complete (step 8):** Write `completed_at` ISO 8601 timestamp to `execution-status.yaml` under the same story key. Format:
|
|
48
|
+
|
|
49
|
+
```yaml
|
|
50
|
+
stories:
|
|
51
|
+
e01s01:
|
|
52
|
+
status: done
|
|
53
|
+
started_at: "2026-07-12T16:00:00-03:00"
|
|
54
|
+
completed_at: "2026-07-12T18:45:00-03:00"
|
|
55
|
+
```
|
|
46
56
|
9. **Traceability refresh:** Before step 8 (release-branch), run `bash scripts/trace-stories.sh` to regenerate `specs/traceability-matrix.json`, `specs/TRACEABILITY_LATEST.md`, and the `specs/codebase-wiki/` OKF bundle. Surface dark/orphan/stale findings for the just-built epic in your verify summary. If `scripts/trace-stories.sh` is missing or fails, note "trace skipped" and continue — trace failure must be visible, not silent (blocking is gate-trace's job, e38s06).
|
|
47
57
|
|
|
48
58
|
9b. **OKF wiki refresh (e39s08):** Before step 8, run maintain-wiki INGEST to refresh the OKF wiki bundle:
|
|
@@ -33,7 +33,7 @@ Move process detail into the SKILL.md body or REFERENCE.md — never into `descr
|
|
|
33
33
|
- Any reference materials to include?
|
|
34
34
|
- What specs/ output does it produce (if any)?
|
|
35
35
|
|
|
36
|
-
2. **Verify Principles** — Ensure the skill aligns with [PRINCIPLES.md](
|
|
36
|
+
2. **Verify Principles** — Ensure the skill aligns with [PRINCIPLES.md](https://github.com/danielvm-git/bigpowers/blob/main/docs/PRINCIPLES.md):
|
|
37
37
|
- Is it atomic (verb-noun)?
|
|
38
38
|
- Is it "deep" (simple interface, complex internal logic)?
|
|
39
39
|
- Does it include Hard Gates?
|
|
@@ -46,7 +46,7 @@ Move process detail into the SKILL.md body or REFERENCE.md — never into `descr
|
|
|
46
46
|
|
|
47
47
|
**Auto-skill from library README:** When user provides a library README or API docs URL, extract: triggers, HARD GATEs, verify commands, specs/ output — draft SKILL.md without inventing APIs not in the source.
|
|
48
48
|
|
|
49
|
-
4. Add `model:` frontmatter (`haiku` | `sonnet` | `opus`) per [model-profiles.md](
|
|
49
|
+
4. Add `model:` frontmatter (`haiku` | `sonnet` | `opus`) per [model-profiles.md](https://github.com/danielvm-git/bigpowers/blob/main/docs/references/model-profiles.md).
|
|
50
50
|
|
|
51
51
|
> **STREAM CONTINUITY** — When writing file content, output in continuous chunks of ~200 lines. Do not pause. Continue immediately until complete. If you need time, emit a placeholder comment rather than going silent.
|
|
52
52
|
|
|
@@ -65,7 +65,7 @@ Once the user has answered the main questions, probe for assumptions:
|
|
|
65
65
|
|
|
66
66
|
### 4. Synthesize and confirm
|
|
67
67
|
|
|
68
|
-
Summarize your understanding in 3–5 bullet points aligned with [countable-story-format.md](
|
|
68
|
+
Summarize your understanding in 3–5 bullet points aligned with [countable-story-format.md](https://github.com/danielvm-git/bigpowers/blob/main/docs/countable-story-format.md):
|
|
69
69
|
- The problem (feeds into §1 Business narrative)
|
|
70
70
|
- The solution and main flow (feeds into §5)
|
|
71
71
|
- The key constraints and alternative flows (feeds into §6)
|
|
@@ -9,4 +9,4 @@
|
|
|
9
9
|
**Evidence:** path/to/benchmark-report.md
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
Benchmark repo:
|
|
12
|
+
Benchmark repo: `bigpowers-benchmark` — resolve the local clone on this machine (e.g. `gh repo clone danielvm-git/bigpowers-benchmark` if absent); never assume a hard-coded absolute path.
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# generate-allure-report — Reference
|
|
2
|
+
|
|
3
|
+
## Data Sources
|
|
4
|
+
|
|
5
|
+
The script reads five YAML sources from the project:
|
|
6
|
+
|
|
7
|
+
| Source | Path | Fields Used |
|
|
8
|
+
|--------|------|-------------|
|
|
9
|
+
| Execution status | `specs/execution-status.yaml` | `epics`, `stories`, `development_status` |
|
|
10
|
+
| Release plan | `specs/release-plan.yaml` | `release.version`, `release.status`, `bugs` summary |
|
|
11
|
+
| Epic capsules | `specs/epics/**/epic.yaml` + `-tasks.yaml` | Epic metadata, task pass/fail counts |
|
|
12
|
+
| Cycle times | `specs/metrics/cycle-times.yaml` | Story-level `cycle_minutes`, `bcp_per_hour`, `source` |
|
|
13
|
+
| Bug registry | `specs/bugs/registry.yaml` | Bug counts by status and severity |
|
|
14
|
+
|
|
15
|
+
## Script Body
|
|
16
|
+
|
|
17
|
+
`scripts/generate-allure-report.sh`:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
#!/usr/bin/env bash
|
|
21
|
+
set -euo pipefail
|
|
22
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
23
|
+
ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || ROOT="$(dirname "${BASH_SOURCE[0]}")/.."
|
|
24
|
+
|
|
25
|
+
mkdir -p "$ROOT/allure-results"
|
|
26
|
+
|
|
27
|
+
$PYTHON - "$ROOT" <<'PY'
|
|
28
|
+
import json
|
|
29
|
+
import sys
|
|
30
|
+
import xml.etree.ElementTree as ET
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
|
|
33
|
+
root = Path(sys.argv[1])
|
|
34
|
+
out = root / "allure-results"
|
|
35
|
+
|
|
36
|
+
# 1. Read execution-status.yaml
|
|
37
|
+
exec_status_file = root / "specs" / "execution-status.yaml"
|
|
38
|
+
release_plan_file = root / "specs" / "release-plan.yaml"
|
|
39
|
+
cycle_times_file = root / "specs" / "metrics" / "cycle-times.yaml"
|
|
40
|
+
bugs_registry_file = root / "specs" / "bugs" / "registry.yaml"
|
|
41
|
+
|
|
42
|
+
sys.path.insert(0, str(root / "scripts" / "lib"))
|
|
43
|
+
from simple_yaml import parse_simple_yaml
|
|
44
|
+
|
|
45
|
+
exec_status = parse_simple_yaml(exec_status_file.read_text()) if exec_status_file.exists() else {}
|
|
46
|
+
release_plan = parse_simple_yaml(release_plan_file.read_text()) if release_plan_file.exists() else {}
|
|
47
|
+
cycle_times = parse_simple_yaml(cycle_times_file.read_text()) if cycle_times_file.exists() else {"stories": []}
|
|
48
|
+
bugs_registry = parse_simple_yaml(bugs_registry_file.read_text()) if bugs_registry_file.exists() else {"bugs": []}
|
|
49
|
+
|
|
50
|
+
# Build cycle-times lookup
|
|
51
|
+
ct_lookup = {}
|
|
52
|
+
for ct in cycle_times.get("stories", []):
|
|
53
|
+
if isinstance(ct, dict):
|
|
54
|
+
ct_lookup[ct.get("id", "")] = ct
|
|
55
|
+
|
|
56
|
+
# 2. Build JUnit XML
|
|
57
|
+
stories = exec_status.get("stories", {})
|
|
58
|
+
|
|
59
|
+
# Counts for testsuite attributes
|
|
60
|
+
total_stories = len(stories)
|
|
61
|
+
incomplete = sum(1 for s in stories.values() if isinstance(s, dict) and s.get("status") != "done")
|
|
62
|
+
|
|
63
|
+
testsuite = ET.Element("testsuite", {
|
|
64
|
+
"name": "bigpowers-epic-progress",
|
|
65
|
+
"tests": str(total_stories),
|
|
66
|
+
"failures": str(incomplete),
|
|
67
|
+
"errors": "0",
|
|
68
|
+
"skipped": "0",
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
for story_id in sorted(stories.keys()):
|
|
72
|
+
story = stories[story_id]
|
|
73
|
+
if not isinstance(story, dict):
|
|
74
|
+
continue
|
|
75
|
+
|
|
76
|
+
epic_id = story.get("epic", "unknown")
|
|
77
|
+
title = story.get("title", story_id)
|
|
78
|
+
bcps = story.get("bcps", 0)
|
|
79
|
+
status = story.get("status", "backlog")
|
|
80
|
+
risk_max = story.get("risk_max", "none")
|
|
81
|
+
security_max = story.get("security_max", "none")
|
|
82
|
+
|
|
83
|
+
# Enrich with cycle-times data
|
|
84
|
+
ct_data = ct_lookup.get(story_id, {})
|
|
85
|
+
cycle_minutes = ct_data.get("cycle_minutes", 0)
|
|
86
|
+
bcp_per_hour = ct_data.get("bcp_per_hour", 0)
|
|
87
|
+
|
|
88
|
+
# Time: cycle_minutes * 60 for seconds in Allure display
|
|
89
|
+
time_seconds = cycle_minutes * 60.0 if cycle_minutes else 0.0
|
|
90
|
+
|
|
91
|
+
testcase = ET.SubElement(testsuite, "testcase", {
|
|
92
|
+
"classname": epic_id,
|
|
93
|
+
"name": f"{story_id}: {title}",
|
|
94
|
+
"time": str(round(time_seconds, 3)),
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
props = ET.SubElement(testcase, "properties")
|
|
98
|
+
ET.SubElement(props, "property", {"name": "risk", "value": risk_max})
|
|
99
|
+
ET.SubElement(props, "property", {"name": "security", "value": security_max})
|
|
100
|
+
ET.SubElement(props, "property", {"name": "bcps", "value": str(bcps)})
|
|
101
|
+
ET.SubElement(props, "property", {"name": "status", "value": status})
|
|
102
|
+
ET.SubElement(props, "property", {"name": "bcp_per_hour", "value": str(bcp_per_hour)})
|
|
103
|
+
ET.SubElement(props, "property", {"name": "lead_time_minutes", "value": str(cycle_minutes)})
|
|
104
|
+
|
|
105
|
+
if status != "done":
|
|
106
|
+
ET.SubElement(testcase, "failure", {
|
|
107
|
+
"message": f"Story {story_id} is {status} [risk={risk_max}, security={security_max}]",
|
|
108
|
+
"type": "StoryIncomplete"
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
tree = ET.ElementTree(testsuite)
|
|
112
|
+
ET.indent(tree, space=" ")
|
|
113
|
+
tree.write(str(out / "junit-results.xml"), encoding="utf-8", xml_declaration=True)
|
|
114
|
+
|
|
115
|
+
# 3. Build categories.json
|
|
116
|
+
epics = exec_status.get("epics", {})
|
|
117
|
+
categories = []
|
|
118
|
+
|
|
119
|
+
for epic_id in sorted(epics.keys()):
|
|
120
|
+
epic = epics[epic_id]
|
|
121
|
+
if isinstance(epic, dict) and epic.get("status") != "done":
|
|
122
|
+
categories.append({
|
|
123
|
+
"name": f"Epic: {epic.get('title', epic_id)}",
|
|
124
|
+
"matchedStatuses": ["failed"],
|
|
125
|
+
"messageRegex": f".*{epic_id}:.*"
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
categories.append({
|
|
129
|
+
"name": "P0 Risk",
|
|
130
|
+
"matchedStatuses": ["failed"],
|
|
131
|
+
"messageRegex": ".*risk.*P0.*"
|
|
132
|
+
})
|
|
133
|
+
categories.append({
|
|
134
|
+
"name": "Security Review",
|
|
135
|
+
"matchedStatuses": ["failed"],
|
|
136
|
+
"messageRegex": ".*security.*(?:medium|high).*"
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
# Add bug-based categories
|
|
140
|
+
bug_list = bugs_registry.get("bugs", [])
|
|
141
|
+
bug_count = len(bug_list) if isinstance(bug_list, list) else 0
|
|
142
|
+
open_bugs = sum(1 for b in bug_list if isinstance(b, dict) and b.get("status") not in ("fixed", "closed", None))
|
|
143
|
+
if open_bugs > 0:
|
|
144
|
+
categories.append({
|
|
145
|
+
"name": "Open Bugs",
|
|
146
|
+
"matchedStatuses": ["failed"],
|
|
147
|
+
"messageRegex": ".*Bug.*"
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
(out / "categories.json").write_text(json.dumps(categories, indent=2))
|
|
151
|
+
|
|
152
|
+
# 4. Build executor.json
|
|
153
|
+
rl = release_plan.get("release", {}) if isinstance(release_plan.get("release"), dict) else {}
|
|
154
|
+
executor = {
|
|
155
|
+
"name": "bigpowers",
|
|
156
|
+
"type": "bigpowers",
|
|
157
|
+
"buildName": rl.get("version", "unknown") if isinstance(rl, dict) else "unknown",
|
|
158
|
+
"buildOrder": len(exec_status.get("development_status", {})),
|
|
159
|
+
}
|
|
160
|
+
(out / "executor.json").write_text(json.dumps(executor, indent=2))
|
|
161
|
+
|
|
162
|
+
# Summary
|
|
163
|
+
epic_count = len([e for e in epics.values() if isinstance(e, dict) and e.get("status") == "done"])
|
|
164
|
+
total_epics = len(epics)
|
|
165
|
+
print(f"generate-allure-report: {total_stories} stories, {epic_count}/{total_epics} epics done, {bug_count} bugs")
|
|
166
|
+
print(f" -> {out}/junit-results.xml")
|
|
167
|
+
print(f" -> {out}/categories.json")
|
|
168
|
+
print(f" -> {out}/executor.json")
|
|
169
|
+
PY
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## JUnit XML Schema
|
|
173
|
+
|
|
174
|
+
```xml
|
|
175
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
176
|
+
<testsuite name="bigpowers-epic-progress" tests="N" failures="F" errors="0" skipped="0">
|
|
177
|
+
<testcase classname="e01" name="e01s01: Security slopcheck tags" time="0.75">
|
|
178
|
+
<properties>
|
|
179
|
+
<property name="risk" value="none"/>
|
|
180
|
+
<property name="security" value="none"/>
|
|
181
|
+
<property name="bcps" value="1"/>
|
|
182
|
+
<property name="status" value="done"/>
|
|
183
|
+
<property name="bcp_per_hour" value="1.3"/>
|
|
184
|
+
<property name="lead_time_minutes" value="45"/>
|
|
185
|
+
</properties>
|
|
186
|
+
</testcase>
|
|
187
|
+
<testcase classname="e01" name="e01s99: Some incomplete story" time="0.0">
|
|
188
|
+
<properties>...</properties>
|
|
189
|
+
<failure message="Story e01s99 is backlog [risk=P0, security=high]" type="StoryIncomplete"/>
|
|
190
|
+
</testcase>
|
|
191
|
+
</testsuite>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Categories JSON Schema
|
|
195
|
+
|
|
196
|
+
```json
|
|
197
|
+
[
|
|
198
|
+
{
|
|
199
|
+
"name": "Epic: Quality Core - Skill Hardening",
|
|
200
|
+
"matchedStatuses": ["failed"],
|
|
201
|
+
"messageRegex": ".*e45:.*"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"name": "P0 Risk",
|
|
205
|
+
"matchedStatuses": ["failed"],
|
|
206
|
+
"messageRegex": ".*risk.*P0.*"
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"name": "Security Review",
|
|
210
|
+
"matchedStatuses": ["failed"],
|
|
211
|
+
"messageRegex": ".*security.*(?:medium|high).*"
|
|
212
|
+
}
|
|
213
|
+
]
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Executor JSON Schema
|
|
217
|
+
|
|
218
|
+
```json
|
|
219
|
+
{
|
|
220
|
+
"name": "bigpowers",
|
|
221
|
+
"type": "bigpowers",
|
|
222
|
+
"buildName": "2.76.2",
|
|
223
|
+
"buildOrder": 400
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Example Usage
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
# Generate reports
|
|
231
|
+
bash scripts/generate-allure-report.sh
|
|
232
|
+
|
|
233
|
+
# Verify output
|
|
234
|
+
test -f allure-results/junit-results.xml && echo "JUnit OK"
|
|
235
|
+
test -f allure-results/categories.json && echo "Categories OK"
|
|
236
|
+
test -f allure-results/executor.json && echo "Executor OK"
|
|
237
|
+
|
|
238
|
+
# Serve with Allure
|
|
239
|
+
allure serve allure-results/
|
|
240
|
+
|
|
241
|
+
# Or open the Allure TestOps UI
|
|
242
|
+
allure open allure-results/
|
|
243
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: generate-allure-report
|
|
3
|
+
model: sonnet
|
|
4
|
+
effort: standard
|
|
5
|
+
description: "Generate Allure-ready reports from bigpowers YAML metadata. Reads execution-status.yaml, release-plan.yaml, epic capsules, task YAMLs, cycle-times.yaml, and bug registry to produce allure-results/junit-results.xml, categories.json, and executor.json. Use when preparing progress dashboards, integrating with Allure TestOps, or generating CI reports."
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Generate Allure Report
|
|
9
|
+
|
|
10
|
+
Generate Allure TestOps-compatible reports from bigpowers project metadata. Produces JUnit XML for story-level test results, custom categories for filtering, and executor metadata — all in the `allure-results/` directory.
|
|
11
|
+
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
bash scripts/generate-allure-report.sh
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## What It Produces
|
|
19
|
+
|
|
20
|
+
Three files in `allure-results/`:
|
|
21
|
+
|
|
22
|
+
| File | Description |
|
|
23
|
+
|------|-------------|
|
|
24
|
+
| `junit-results.xml` | One `<testcase>` per story with `<properties>` for risk, security, WSJF, tier, wave, and status. Incomplete stories get a `<failure>` element. |
|
|
25
|
+
| `categories.json` | Custom Allure categories for filtering by epic, risk level (P0), and security reviews. |
|
|
26
|
+
| `executor.json` | Build metadata — name, type, version from release-plan.yaml, build order. |
|
|
27
|
+
|
|
28
|
+
## Data Sources
|
|
29
|
+
|
|
30
|
+
See [REFERENCE.md](REFERENCE.md)
|
|
31
|
+
|
|
32
|
+
## Verify
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
test -f allure-results/junit-results.xml && test -f allure-results/categories.json && test -f allure-results/executor.json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Handoff
|
|
39
|
+
|
|
40
|
+
- next_skill: null (terminal skill — no downstream workflow step)
|
|
@@ -43,7 +43,7 @@ Ask one question at a time. For each challenge, cite the specific URL and sectio
|
|
|
43
43
|
|
|
44
44
|
When an assumption doesn't match the docs:
|
|
45
45
|
|
|
46
|
-
> "Your plan uses `library.doThing(a, b)` but the [docs](
|
|
46
|
+
> "Your plan uses `library.doThing(a, b)` but the [docs](https://example.com/api-reference) show the signature is `doThing(config: {a, b})` with a config object. This will fail at runtime."
|
|
47
47
|
|
|
48
48
|
Document each discrepancy clearly.
|
|
49
49
|
|