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.
Files changed (93) hide show
  1. package/.pi/package.json +2 -2
  2. package/.pi/prompts/audit-code.md +1 -1
  3. package/.pi/prompts/build-epic.md +10 -0
  4. package/.pi/prompts/craft-skill.md +2 -2
  5. package/.pi/prompts/elaborate-spec.md +1 -1
  6. package/.pi/prompts/evolve-skill.md +1 -1
  7. package/.pi/prompts/generate-allure-report.md +284 -0
  8. package/.pi/prompts/grill-me.md +1 -1
  9. package/.pi/prompts/guard-git.md +1 -1
  10. package/.pi/prompts/harden-vps.md +287 -0
  11. package/.pi/prompts/investigate-bug.md +2 -2
  12. package/.pi/prompts/model-domain.md +3 -3
  13. package/.pi/prompts/plan-refactor.md +2 -2
  14. package/.pi/prompts/plan-release.md +5 -10
  15. package/.pi/prompts/plan-work.md +14 -1
  16. package/.pi/prompts/using-bigpowers.md +1 -1
  17. package/.pi/prompts/write-document.md +2 -2
  18. package/.pi/skills/audit-code/SKILL.md +1 -1
  19. package/.pi/skills/build-epic/SKILL.md +10 -0
  20. package/.pi/skills/change-request/SKILL.md +1 -1
  21. package/.pi/skills/commit-message/SKILL.md +3 -3
  22. package/.pi/skills/compose-workflow/SKILL.md +1 -1
  23. package/.pi/skills/craft-skill/SKILL.md +4 -4
  24. package/.pi/skills/deepen-architecture/SKILL.md +13 -13
  25. package/.pi/skills/deploy/SKILL.md +4 -4
  26. package/.pi/skills/develop-tdd/SKILL.md +5 -5
  27. package/.pi/skills/elaborate-spec/SKILL.md +1 -1
  28. package/.pi/skills/evolve-skill/SKILL.md +2 -2
  29. package/.pi/skills/extract-design/SKILL.md +1 -1
  30. package/.pi/skills/generate-allure-report/SKILL.md +285 -0
  31. package/.pi/skills/grill-me/SKILL.md +2 -2
  32. package/.pi/skills/grill-with-docs/SKILL.md +1 -1
  33. package/.pi/skills/guard-git/SKILL.md +5 -5
  34. package/.pi/skills/harden-vps/SKILL.md +288 -0
  35. package/.pi/skills/investigate-bug/SKILL.md +2 -2
  36. package/.pi/skills/migrate-spec/SKILL.md +13 -13
  37. package/.pi/skills/model-domain/SKILL.md +5 -5
  38. package/.pi/skills/orchestrate-project/SKILL.md +2 -2
  39. package/.pi/skills/organize-workspace/SKILL.md +1 -1
  40. package/.pi/skills/plan-refactor/SKILL.md +2 -2
  41. package/.pi/skills/plan-release/SKILL.md +5 -10
  42. package/.pi/skills/plan-work/SKILL.md +15 -2
  43. package/.pi/skills/publish-package/SKILL.md +8 -8
  44. package/.pi/skills/release-branch/SKILL.md +2 -2
  45. package/.pi/skills/research-first/SKILL.md +1 -1
  46. package/.pi/skills/run-evals/SKILL.md +1 -1
  47. package/.pi/skills/security-review/SKILL.md +3 -3
  48. package/.pi/skills/seed-conventions/SKILL.md +3 -3
  49. package/.pi/skills/smoke-test/SKILL.md +5 -5
  50. package/.pi/skills/stocktake-skills/SKILL.md +1 -1
  51. package/.pi/skills/using-bigpowers/SKILL.md +1 -1
  52. package/.pi/skills/validate-contracts/SKILL.md +6 -6
  53. package/.pi/skills/verify-work/SKILL.md +1 -1
  54. package/.pi/skills/wire-ci/SKILL.md +8 -8
  55. package/.pi/skills/write-document/SKILL.md +3 -3
  56. package/CHANGELOG.md +14 -0
  57. package/README.md +1 -1
  58. package/SKILL-INDEX.md +1 -1
  59. package/allure-results/categories.json +23 -0
  60. package/allure-results/executor.json +6 -0
  61. package/allure-results/junit-results.xml +2743 -0
  62. package/package.json +4 -3
  63. package/scripts/adapters/pi.sh +4 -1
  64. package/scripts/check-skill-links.py +78 -0
  65. package/scripts/generate-allure-report.sh +146 -0
  66. package/scripts/generate-reference-tables.sh +2 -1
  67. package/scripts/lib/__pycache__/link_utils.cpython-312.pyc +0 -0
  68. package/scripts/lib/link_utils.py +49 -0
  69. package/scripts/lib/srp-engine.py +86 -1
  70. package/scripts/record-cycle-time.sh +28 -0
  71. package/scripts/sync-status-from-epics.sh +247 -24
  72. package/scripts/validate-skill-catalog.sh +1 -1
  73. package/skills/audit-code/SKILL.md +1 -1
  74. package/skills/build-epic/SKILL.md +10 -0
  75. package/skills/craft-skill/SKILL.md +2 -2
  76. package/skills/elaborate-spec/SKILL.md +1 -1
  77. package/skills/evolve-skill/REFERENCE.md +1 -1
  78. package/skills/generate-allure-report/REFERENCE.md +243 -0
  79. package/skills/generate-allure-report/SKILL.md +40 -0
  80. package/skills/grill-me/REFERENCE.md +1 -1
  81. package/skills/guard-git/SKILL.md +1 -1
  82. package/skills/harden-vps/REFERENCE.md +179 -0
  83. package/skills/harden-vps/SKILL.md +107 -0
  84. package/skills/investigate-bug/SKILL.md +2 -2
  85. package/skills/model-domain/CONTEXT-FORMAT.md +3 -3
  86. package/skills/plan-refactor/SKILL.md +2 -2
  87. package/skills/plan-release/SKILL.md +5 -10
  88. package/skills/plan-work/REFERENCE.md +13 -0
  89. package/skills/plan-work/SKILL.md +1 -1
  90. package/skills/using-bigpowers/SKILL.md +1 -1
  91. package/skills/write-document/REFERENCE.md +2 -2
  92. package/skills-lock.json +20 -10
  93. 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
- for epic_file in sorted(epics_dir.glob("e*.yaml")):
27
- text = epic_file.read_text(encoding="utf-8")
28
- em = re.search(r"^id:\s*(e\d+)", text, re.M)
29
- if em:
30
- eid = em.group(1)
31
- keys.setdefault(eid, "backlog")
32
- for sm in re.finditer(r"^\s+- id:\s*(e\d+s\d+)", text, re.M):
33
- keys.setdefault(sm.group(1), "backlog")
34
-
35
- for folder in sorted(epics_dir.glob("e*/")):
36
- epic_yaml = folder / "epic.yaml"
37
- if epic_yaml.exists():
38
- text = epic_yaml.read_text(encoding="utf-8")
39
- em = re.search(r"^id:\s*(e\d+)", text, re.M)
40
- if em:
41
- keys.setdefault(em.group(1), "backlog")
42
- # Parse inline story IDs from capsule epic.yaml (same pattern as flat files)
43
- for sm in re.finditer(r"^\s+- id:\s*(e\d+s\d+)", text, re.M):
44
- keys.setdefault(sm.group(1), "backlog")
45
- for story in (folder / "stories").glob("e*s*.md"):
46
- m = re.match(r"(e\d+s\d+)", story.name)
47
- if m:
48
- keys.setdefault(m.group(1), "backlog")
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. See [REFERENCE.md](REFERENCE.md) or:
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](../docs/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](../docs/references/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](file:///Users/danielvm/Developer/bigpowers/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: `/Users/danielvm/Developer/bigpowers-benchmark/`
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](URL) show the signature is `doThing(config: {a, b})` with a config object. This will fail at runtime."
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