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
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: generate-allure-report
|
|
3
|
+
model: sonnet
|
|
4
|
+
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."
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Generate Allure Report
|
|
8
|
+
|
|
9
|
+
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.
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bash scripts/generate-allure-report.sh
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## What It Produces
|
|
18
|
+
|
|
19
|
+
Three files in `allure-results/`:
|
|
20
|
+
|
|
21
|
+
| File | Description |
|
|
22
|
+
|------|-------------|
|
|
23
|
+
| `junit-results.xml` | One `<testcase>` per story with `<properties>` for risk, security, WSJF, tier, wave, and status. Incomplete stories get a `<failure>` element. |
|
|
24
|
+
| `categories.json` | Custom Allure categories for filtering by epic, risk level (P0), and security reviews. |
|
|
25
|
+
| `executor.json` | Build metadata — name, type, version from release-plan.yaml, build order. |
|
|
26
|
+
|
|
27
|
+
## Data Sources
|
|
28
|
+
|
|
29
|
+
See [REFERENCE.md](../../../skills/generate-allure-report/REFERENCE.md)
|
|
30
|
+
|
|
31
|
+
## Verify
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
test -f allure-results/junit-results.xml && test -f allure-results/categories.json && test -f allure-results/executor.json
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Handoff
|
|
38
|
+
|
|
39
|
+
- next_skill: null (terminal skill — no downstream workflow step)
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
# generate-allure-report — Reference
|
|
44
|
+
|
|
45
|
+
## Data Sources
|
|
46
|
+
|
|
47
|
+
The script reads five YAML sources from the project:
|
|
48
|
+
|
|
49
|
+
| Source | Path | Fields Used |
|
|
50
|
+
|--------|------|-------------|
|
|
51
|
+
| Execution status | `specs/execution-status.yaml` | `epics`, `stories`, `development_status` |
|
|
52
|
+
| Release plan | `specs/release-plan.yaml` | `release.version`, `release.status`, `bugs` summary |
|
|
53
|
+
| Epic capsules | `specs/epics/**/epic.yaml` + `-tasks.yaml` | Epic metadata, task pass/fail counts |
|
|
54
|
+
| Cycle times | `specs/metrics/cycle-times.yaml` | Story-level `cycle_minutes`, `bcp_per_hour`, `source` |
|
|
55
|
+
| Bug registry | `specs/bugs/registry.yaml` | Bug counts by status and severity |
|
|
56
|
+
|
|
57
|
+
## Script Body
|
|
58
|
+
|
|
59
|
+
`scripts/generate-allure-report.sh`:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
#!/usr/bin/env bash
|
|
63
|
+
set -euo pipefail
|
|
64
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
65
|
+
ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || ROOT="$(dirname "${BASH_SOURCE[0]}")/.."
|
|
66
|
+
|
|
67
|
+
mkdir -p "$ROOT/allure-results"
|
|
68
|
+
|
|
69
|
+
$PYTHON - "$ROOT" <<'PY'
|
|
70
|
+
import json
|
|
71
|
+
import sys
|
|
72
|
+
import xml.etree.ElementTree as ET
|
|
73
|
+
from pathlib import Path
|
|
74
|
+
|
|
75
|
+
root = Path(sys.argv[1])
|
|
76
|
+
out = root / "allure-results"
|
|
77
|
+
|
|
78
|
+
# 1. Read execution-status.yaml
|
|
79
|
+
exec_status_file = root / "specs" / "execution-status.yaml"
|
|
80
|
+
release_plan_file = root / "specs" / "release-plan.yaml"
|
|
81
|
+
cycle_times_file = root / "specs" / "metrics" / "cycle-times.yaml"
|
|
82
|
+
bugs_registry_file = root / "specs" / "bugs" / "registry.yaml"
|
|
83
|
+
|
|
84
|
+
sys.path.insert(0, str(root / "scripts" / "lib"))
|
|
85
|
+
from simple_yaml import parse_simple_yaml
|
|
86
|
+
|
|
87
|
+
exec_status = parse_simple_yaml(exec_status_file.read_text()) if exec_status_file.exists() else {}
|
|
88
|
+
release_plan = parse_simple_yaml(release_plan_file.read_text()) if release_plan_file.exists() else {}
|
|
89
|
+
cycle_times = parse_simple_yaml(cycle_times_file.read_text()) if cycle_times_file.exists() else {"stories": []}
|
|
90
|
+
bugs_registry = parse_simple_yaml(bugs_registry_file.read_text()) if bugs_registry_file.exists() else {"bugs": []}
|
|
91
|
+
|
|
92
|
+
# Build cycle-times lookup
|
|
93
|
+
ct_lookup = {}
|
|
94
|
+
for ct in cycle_times.get("stories", []):
|
|
95
|
+
if isinstance(ct, dict):
|
|
96
|
+
ct_lookup[ct.get("id", "")] = ct
|
|
97
|
+
|
|
98
|
+
# 2. Build JUnit XML
|
|
99
|
+
stories = exec_status.get("stories", {})
|
|
100
|
+
|
|
101
|
+
# Counts for testsuite attributes
|
|
102
|
+
total_stories = len(stories)
|
|
103
|
+
incomplete = sum(1 for s in stories.values() if isinstance(s, dict) and s.get("status") != "done")
|
|
104
|
+
|
|
105
|
+
testsuite = ET.Element("testsuite", {
|
|
106
|
+
"name": "bigpowers-epic-progress",
|
|
107
|
+
"tests": str(total_stories),
|
|
108
|
+
"failures": str(incomplete),
|
|
109
|
+
"errors": "0",
|
|
110
|
+
"skipped": "0",
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
for story_id in sorted(stories.keys()):
|
|
114
|
+
story = stories[story_id]
|
|
115
|
+
if not isinstance(story, dict):
|
|
116
|
+
continue
|
|
117
|
+
|
|
118
|
+
epic_id = story.get("epic", "unknown")
|
|
119
|
+
title = story.get("title", story_id)
|
|
120
|
+
bcps = story.get("bcps", 0)
|
|
121
|
+
status = story.get("status", "backlog")
|
|
122
|
+
risk_max = story.get("risk_max", "none")
|
|
123
|
+
security_max = story.get("security_max", "none")
|
|
124
|
+
|
|
125
|
+
# Enrich with cycle-times data
|
|
126
|
+
ct_data = ct_lookup.get(story_id, {})
|
|
127
|
+
cycle_minutes = ct_data.get("cycle_minutes", 0)
|
|
128
|
+
bcp_per_hour = ct_data.get("bcp_per_hour", 0)
|
|
129
|
+
|
|
130
|
+
# Time: cycle_minutes * 60 for seconds in Allure display
|
|
131
|
+
time_seconds = cycle_minutes * 60.0 if cycle_minutes else 0.0
|
|
132
|
+
|
|
133
|
+
testcase = ET.SubElement(testsuite, "testcase", {
|
|
134
|
+
"classname": epic_id,
|
|
135
|
+
"name": f"{story_id}: {title}",
|
|
136
|
+
"time": str(round(time_seconds, 3)),
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
props = ET.SubElement(testcase, "properties")
|
|
140
|
+
ET.SubElement(props, "property", {"name": "risk", "value": risk_max})
|
|
141
|
+
ET.SubElement(props, "property", {"name": "security", "value": security_max})
|
|
142
|
+
ET.SubElement(props, "property", {"name": "bcps", "value": str(bcps)})
|
|
143
|
+
ET.SubElement(props, "property", {"name": "status", "value": status})
|
|
144
|
+
ET.SubElement(props, "property", {"name": "bcp_per_hour", "value": str(bcp_per_hour)})
|
|
145
|
+
ET.SubElement(props, "property", {"name": "lead_time_minutes", "value": str(cycle_minutes)})
|
|
146
|
+
|
|
147
|
+
if status != "done":
|
|
148
|
+
ET.SubElement(testcase, "failure", {
|
|
149
|
+
"message": f"Story {story_id} is {status} [risk={risk_max}, security={security_max}]",
|
|
150
|
+
"type": "StoryIncomplete"
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
tree = ET.ElementTree(testsuite)
|
|
154
|
+
ET.indent(tree, space=" ")
|
|
155
|
+
tree.write(str(out / "junit-results.xml"), encoding="utf-8", xml_declaration=True)
|
|
156
|
+
|
|
157
|
+
# 3. Build categories.json
|
|
158
|
+
epics = exec_status.get("epics", {})
|
|
159
|
+
categories = []
|
|
160
|
+
|
|
161
|
+
for epic_id in sorted(epics.keys()):
|
|
162
|
+
epic = epics[epic_id]
|
|
163
|
+
if isinstance(epic, dict) and epic.get("status") != "done":
|
|
164
|
+
categories.append({
|
|
165
|
+
"name": f"Epic: {epic.get('title', epic_id)}",
|
|
166
|
+
"matchedStatuses": ["failed"],
|
|
167
|
+
"messageRegex": f".*{epic_id}:.*"
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
categories.append({
|
|
171
|
+
"name": "P0 Risk",
|
|
172
|
+
"matchedStatuses": ["failed"],
|
|
173
|
+
"messageRegex": ".*risk.*P0.*"
|
|
174
|
+
})
|
|
175
|
+
categories.append({
|
|
176
|
+
"name": "Security Review",
|
|
177
|
+
"matchedStatuses": ["failed"],
|
|
178
|
+
"messageRegex": ".*security.*(?:medium|high).*"
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
# Add bug-based categories
|
|
182
|
+
bug_list = bugs_registry.get("bugs", [])
|
|
183
|
+
bug_count = len(bug_list) if isinstance(bug_list, list) else 0
|
|
184
|
+
open_bugs = sum(1 for b in bug_list if isinstance(b, dict) and b.get("status") not in ("fixed", "closed", None))
|
|
185
|
+
if open_bugs > 0:
|
|
186
|
+
categories.append({
|
|
187
|
+
"name": "Open Bugs",
|
|
188
|
+
"matchedStatuses": ["failed"],
|
|
189
|
+
"messageRegex": ".*Bug.*"
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
(out / "categories.json").write_text(json.dumps(categories, indent=2))
|
|
193
|
+
|
|
194
|
+
# 4. Build executor.json
|
|
195
|
+
rl = release_plan.get("release", {}) if isinstance(release_plan.get("release"), dict) else {}
|
|
196
|
+
executor = {
|
|
197
|
+
"name": "bigpowers",
|
|
198
|
+
"type": "bigpowers",
|
|
199
|
+
"buildName": rl.get("version", "unknown") if isinstance(rl, dict) else "unknown",
|
|
200
|
+
"buildOrder": len(exec_status.get("development_status", {})),
|
|
201
|
+
}
|
|
202
|
+
(out / "executor.json").write_text(json.dumps(executor, indent=2))
|
|
203
|
+
|
|
204
|
+
# Summary
|
|
205
|
+
epic_count = len([e for e in epics.values() if isinstance(e, dict) and e.get("status") == "done"])
|
|
206
|
+
total_epics = len(epics)
|
|
207
|
+
print(f"generate-allure-report: {total_stories} stories, {epic_count}/{total_epics} epics done, {bug_count} bugs")
|
|
208
|
+
print(f" -> {out}/junit-results.xml")
|
|
209
|
+
print(f" -> {out}/categories.json")
|
|
210
|
+
print(f" -> {out}/executor.json")
|
|
211
|
+
PY
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## JUnit XML Schema
|
|
215
|
+
|
|
216
|
+
```xml
|
|
217
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
218
|
+
<testsuite name="bigpowers-epic-progress" tests="N" failures="F" errors="0" skipped="0">
|
|
219
|
+
<testcase classname="e01" name="e01s01: Security slopcheck tags" time="0.75">
|
|
220
|
+
<properties>
|
|
221
|
+
<property name="risk" value="none"/>
|
|
222
|
+
<property name="security" value="none"/>
|
|
223
|
+
<property name="bcps" value="1"/>
|
|
224
|
+
<property name="status" value="done"/>
|
|
225
|
+
<property name="bcp_per_hour" value="1.3"/>
|
|
226
|
+
<property name="lead_time_minutes" value="45"/>
|
|
227
|
+
</properties>
|
|
228
|
+
</testcase>
|
|
229
|
+
<testcase classname="e01" name="e01s99: Some incomplete story" time="0.0">
|
|
230
|
+
<properties>...</properties>
|
|
231
|
+
<failure message="Story e01s99 is backlog [risk=P0, security=high]" type="StoryIncomplete"/>
|
|
232
|
+
</testcase>
|
|
233
|
+
</testsuite>
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Categories JSON Schema
|
|
237
|
+
|
|
238
|
+
```json
|
|
239
|
+
[
|
|
240
|
+
{
|
|
241
|
+
"name": "Epic: Quality Core - Skill Hardening",
|
|
242
|
+
"matchedStatuses": ["failed"],
|
|
243
|
+
"messageRegex": ".*e45:.*"
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
"name": "P0 Risk",
|
|
247
|
+
"matchedStatuses": ["failed"],
|
|
248
|
+
"messageRegex": ".*risk.*P0.*"
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
"name": "Security Review",
|
|
252
|
+
"matchedStatuses": ["failed"],
|
|
253
|
+
"messageRegex": ".*security.*(?:medium|high).*"
|
|
254
|
+
}
|
|
255
|
+
]
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Executor JSON Schema
|
|
259
|
+
|
|
260
|
+
```json
|
|
261
|
+
{
|
|
262
|
+
"name": "bigpowers",
|
|
263
|
+
"type": "bigpowers",
|
|
264
|
+
"buildName": "2.76.2",
|
|
265
|
+
"buildOrder": 400
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Example Usage
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
# Generate reports
|
|
273
|
+
bash scripts/generate-allure-report.sh
|
|
274
|
+
|
|
275
|
+
# Verify output
|
|
276
|
+
test -f allure-results/junit-results.xml && echo "JUnit OK"
|
|
277
|
+
test -f allure-results/categories.json && echo "Categories OK"
|
|
278
|
+
test -f allure-results/executor.json && echo "Executor OK"
|
|
279
|
+
|
|
280
|
+
# Serve with Allure
|
|
281
|
+
allure serve allure-results/
|
|
282
|
+
|
|
283
|
+
# Or open the Allure TestOps UI
|
|
284
|
+
allure open allure-results/
|
|
285
|
+
```
|
|
@@ -20,7 +20,7 @@ If a question can be answered by exploring the codebase, explore it instead.
|
|
|
20
20
|
|
|
21
21
|
## Docs mode
|
|
22
22
|
|
|
23
|
-
Ground every challenge in real documentation — no assumption about a library's behavior goes unchecked. See [REFERENCE.md](REFERENCE.md) for the full process.
|
|
23
|
+
Ground every challenge in real documentation — no assumption about a library's behavior goes unchecked. See [REFERENCE.md](../../../skills/grill-me/REFERENCE.md) for the full process.
|
|
24
24
|
|
|
25
25
|
Short form:
|
|
26
26
|
1. List every external library, third-party API, and framework behavior relied upon.
|
|
@@ -76,7 +76,7 @@ Ask one question at a time. For each challenge, cite the specific URL and sectio
|
|
|
76
76
|
|
|
77
77
|
When an assumption doesn't match the docs:
|
|
78
78
|
|
|
79
|
-
> "Your plan uses `library.doThing(a, b)` but the [docs](
|
|
79
|
+
> "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."
|
|
80
80
|
|
|
81
81
|
Document each discrepancy clearly.
|
|
82
82
|
|
|
@@ -27,7 +27,7 @@ description: "Doc-grounded variant of grill-me — stress-tests plan assumptions
|
|
|
27
27
|
|
|
28
28
|
→ verify: `grep -c 'https\?://' skills/grill-with-docs/SKILL.md | awk '{if($1>=2) print "OK"; else print "NEEDS DOCS"}'`
|
|
29
29
|
|
|
30
|
-
See [REFERENCE.md](REFERENCE.md) for question templates.
|
|
30
|
+
See [REFERENCE.md](../../../skills/grill-with-docs/REFERENCE.md) for question templates.
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
<!-- story: e03s01 -->
|
|
@@ -16,15 +16,15 @@ Installs a shared hook that blocks destructive git operations and enforces workf
|
|
|
16
16
|
- **Discipline**: Blocks direct commits or pushes to protected branches (`main`, `master`) unless `GIT_BIGPOWERS_LAND=1` (set only by `scripts/land-branch.sh`).
|
|
17
17
|
- **Allows**: `git push origin <feature-branch>` for backup/CI; solo land push to `main` only inside `land-branch.sh`.
|
|
18
18
|
- **Standardization**: Enforces [Conventional Commits](https://www.conventionalcommits.org/) for all `git commit` commands.
|
|
19
|
-
- **Secrets**: Blocks commits containing common secret patterns (`sk-`, `ghp_`, `AKIA`, `xoxb-`, `-----BEGIN` private keys) — see [REFERENCE.md](REFERENCE.md).
|
|
19
|
+
- **Secrets**: Blocks commits containing common secret patterns (`sk-`, `ghp_`, `AKIA`, `xoxb-`, `-----BEGIN` private keys) — see [REFERENCE.md](../../../skills/guard-git/REFERENCE.md).
|
|
20
20
|
|
|
21
21
|
## Quick start
|
|
22
22
|
|
|
23
23
|
1. **Scope**: ask project-only vs global (paths differ per product).
|
|
24
|
-
2. **
|
|
24
|
+
2. **Write the hook bundle** from [REFERENCE.md](../../../skills/guard-git/REFERENCE.md) into the client's hooks directory.
|
|
25
25
|
3. **Run `chmod +x`** on `pre-tool-use.sh`.
|
|
26
|
-
4. **Merge** the hook snippet from [REFERENCE.md](REFERENCE.md) into the right settings file — do not wipe unrelated keys.
|
|
27
|
-
5. **Verify** with the tests in [REFERENCE.md](REFERENCE.md).
|
|
26
|
+
4. **Merge** the hook snippet from [REFERENCE.md](../../../skills/guard-git/REFERENCE.md) into the right settings file — do not wipe unrelated keys.
|
|
27
|
+
5. **Verify** with the tests in [REFERENCE.md](../../../skills/guard-git/REFERENCE.md).
|
|
28
28
|
|
|
29
29
|
| Client | Mechanism | Config |
|
|
30
30
|
|--------|-----------|--------|
|
|
@@ -41,7 +41,7 @@ To add or remove patterns or protected branches, edit `pre-tool-use.sh`.
|
|
|
41
41
|
|
|
42
42
|
## Advanced
|
|
43
43
|
|
|
44
|
-
Full JSON examples, merge rules, Antigravity deny-list entries, and test commands: [REFERENCE.md](REFERENCE.md).
|
|
44
|
+
Full JSON examples, merge rules, Antigravity deny-list entries, and test commands: [REFERENCE.md](../../../skills/guard-git/REFERENCE.md).
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: harden-vps
|
|
3
|
+
model: haiku
|
|
4
|
+
description: "Harden a production Linux VPS for BigBase across three layers — BigBase app (systemd hardening, monitoring alerts, backup automation), Ubuntu OS (UFW firewall, fail2ban SSH, unattended-upgrades, SSH hardening), and Contabo VPS (health checks, daily backups, monthly snapshots). Use when the user wants to secure a production server, harden a VPS, audit server security, or mentions production hardening, VPS security, or harden the server."
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Harden VPS
|
|
8
|
+
|
|
9
|
+
Three-layer production hardening for BigBase on Contabo VPS. Each layer independently verifiable. Apply OS first (firewall blocks attacks immediately), then BigBase (alerts + backups), then Contabo (snapshots). See [REFERENCE.md](../../../skills/harden-vps/REFERENCE.md) for full script bodies, systemd unit template, and gotchas.
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
SSH as root into the VPS. Find credentials in the Contabo Customer Control Panel.
|
|
14
|
+
|
|
15
|
+
> **HARD GATE** — Run `ufw status` first. No firewall = layer 1 takes priority over everything.
|
|
16
|
+
|
|
17
|
+
## Layer 1 — Ubuntu OS
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# UFW
|
|
21
|
+
ufw default deny incoming && ufw default allow outgoing
|
|
22
|
+
ufw allow 22/tcp && ufw allow 80/tcp && ufw allow 443/tcp && ufw enable
|
|
23
|
+
# → verify: ufw status | grep -q active
|
|
24
|
+
|
|
25
|
+
# fail2ban
|
|
26
|
+
apt install -y fail2ban
|
|
27
|
+
# Configure /etc/fail2ban/jail.local: sshd, maxretry=3, bantime=3600, findtime=600
|
|
28
|
+
systemctl restart fail2ban
|
|
29
|
+
# → verify: fail2ban-client status sshd
|
|
30
|
+
|
|
31
|
+
# unattended-upgrades
|
|
32
|
+
apt install -y unattended-upgrades && dpkg-reconfigure -plow unattended-upgrades
|
|
33
|
+
# → verify: systemctl is-active unattended-upgrades | grep -q active
|
|
34
|
+
|
|
35
|
+
# SSH: PermitRootLogin no, PasswordAuthentication no, PubkeyAuthentication yes
|
|
36
|
+
# → verify: sshd -T | grep -E 'permitrootlogin no|passwordauthentication no'
|
|
37
|
+
|
|
38
|
+
# Deploy healthcheck.sh → /opt/bigbase/scripts/healthcheck.sh
|
|
39
|
+
# Crontab: */5 * * * * /opt/bigbase/scripts/healthcheck.sh
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Layer 2 — BigBase application
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# systemd: User=bigbase, NoNewPrivileges=yes, ProtectSystem=full,
|
|
46
|
+
# ProtectKernelTunables=yes, ProtectKernelModules=yes,
|
|
47
|
+
# ProtectControlGroups=yes, RestrictAddressFamilies=AF_INET AF_INET6,
|
|
48
|
+
# RestrictRealtime=yes, PrivateTmp=yes, LimitNOFILE=65536
|
|
49
|
+
# → verify: systemctl show bigbase -p NoNewPrivileges -p ProtectSystem -p User
|
|
50
|
+
|
|
51
|
+
# Alerts (BigBase requires auth; insert via SQLite)
|
|
52
|
+
sqlite3 /opt/bigbase/data/bigbase.db "
|
|
53
|
+
INSERT INTO monitoring_alerts (id,name,metric,threshold,operator,enabled,duration_seconds)
|
|
54
|
+
VALUES ('a1','Disk >80%','disk_used_percent',80,'gt',1,300);
|
|
55
|
+
INSERT INTO monitoring_alerts (id,name,metric,threshold,operator,enabled,duration_seconds)
|
|
56
|
+
VALUES ('a2','CPU >90%','cpu_percent',90,'gt',1,60);
|
|
57
|
+
INSERT INTO monitoring_alerts (id,name,metric,threshold,operator,enabled,duration_seconds)
|
|
58
|
+
VALUES ('a3','RAM >85%','mem_used_percent',85,'gt',1,120);
|
|
59
|
+
"
|
|
60
|
+
systemctl restart bigbase
|
|
61
|
+
|
|
62
|
+
# Backup crontab (root):
|
|
63
|
+
# 0 2 * * * cp /opt/bigbase/data/bigbase.db /backup/bigbase-$(date +\%Y\%m\%d).db
|
|
64
|
+
# 0 3 * * * find /backup/ -name "bigbase-*.db" -mtime +90 -delete
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Layer 3 — Contabo VPS
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# cntb CLI
|
|
71
|
+
curl -sL "$(curl -sL https://api.github.com/repos/contabo/cntb/releases/latest \
|
|
72
|
+
| grep browser_download_url.*linux_amd64.tar.gz | head -1 | cut -d'"' -f4)" \
|
|
73
|
+
| tar xz -C /usr/local/bin
|
|
74
|
+
|
|
75
|
+
# Snapshot script → /opt/bigbase/scripts/contabo-snapshot.sh (reads from /opt/bigbase/.env)
|
|
76
|
+
# Credentials as env vars in /opt/bigbase/.env (deployed by GitHub Actions):
|
|
77
|
+
# CONTABO_CLIENT_ID, CONTABO_CLIENT_SECRET, CONTABO_API_USER, CONTABO_API_PASSWORD
|
|
78
|
+
# Crontab: 0 4 1 * * /opt/bigbase/scripts/contabo-snapshot.sh
|
|
79
|
+
|
|
80
|
+
# > HARD GATE — Snapshot cron silently fails until env vars are set in .env.
|
|
81
|
+
# Credentials source: Contabo Customer Panel → API Details.
|
|
82
|
+
# Local dev: add to .envrc. Production: GitHub Secrets → deploy → /opt/bigbase/.env
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## CRITICAL GOTCHAS
|
|
86
|
+
|
|
87
|
+
1. **Shell escaping in Orca terminals:** `$VAR`, `$(…)`, and `%` get eaten by the local shell. Always use base64: `echo '<base64>' | base64 -d > script.sh`
|
|
88
|
+
2. **Crontab `%`:** cron interprets `%` as newline. Escape as `\%` in `$(date +\%Y\%m\%d)`
|
|
89
|
+
3. **fail2ban exit 255:** means a jail references a missing log file. Remove the broken jail, restart.
|
|
90
|
+
4. **BigBase alerts need auth:** POST to `/api/monitoring/alerts` requires Bearer token. Workaround: insert directly into SQLite, then restart BigBase.
|
|
91
|
+
|
|
92
|
+
## Verify all 8 gates
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
ufw status|grep -q active||echo FAIL:ufw
|
|
96
|
+
fail2ban-client status sshd>/dev/null 2>&1||echo FAIL:fail2ban
|
|
97
|
+
systemctl is-active unattended-upgrades|grep -q active||echo FAIL:unattended
|
|
98
|
+
sshd -T|grep -q 'permitrootlogin no'||echo FAIL:sshd
|
|
99
|
+
systemctl show bigbase -p NoNewPrivileges|grep -q yes||echo FAIL:systemd
|
|
100
|
+
systemctl is-active bigbase|grep -q active||echo FAIL:bigbase
|
|
101
|
+
sqlite3 /opt/bigbase/data/bigbase.db "SELECT count(*) FROM monitoring_alerts"|grep -q 3||echo FAIL:alerts
|
|
102
|
+
crontab -l|grep -q healthcheck&&crontab -l|grep -q bigbase.db&&crontab -l|grep -q contabo-snapshot||echo FAIL:crontab
|
|
103
|
+
echo ALL 8 GATES PASSED
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
→ verify: run the 8-gate one-liner on the VPS.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
# Harden VPS — Reference
|
|
111
|
+
|
|
112
|
+
## Health check script
|
|
113
|
+
|
|
114
|
+
Deploy to `/opt/bigbase/scripts/healthcheck.sh`:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
#!/bin/bash
|
|
118
|
+
LOG=/var/log/bigbase/health.log
|
|
119
|
+
mkdir -p /var/log/bigbase
|
|
120
|
+
DISK=$(df / | awk 'NR==2 {print $5}' | tr -d '%')
|
|
121
|
+
RAM=$(free | awk '/Mem:/ {printf "%.0f", $3/$2*100}')
|
|
122
|
+
NOW=$(date -Iseconds)
|
|
123
|
+
[ "$DISK" -gt 85 ] && echo "$NOW ALERT: Disk ${DISK}%" >> "$LOG"
|
|
124
|
+
[ "$RAM" -gt 90 ] && echo "$NOW ALERT: RAM ${RAM}%" >> "$LOG"
|
|
125
|
+
systemctl is-active --quiet bigbase || { echo "$NOW ALERT: BigBase DOWN" >> "$LOG"; systemctl restart bigbase; }
|
|
126
|
+
[ -f /var/run/reboot-required ] && echo "$NOW WARN: kernel reboot pending" >> "$LOG"
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Make executable: `chmod +x /opt/bigbase/scripts/healthcheck.sh`
|
|
130
|
+
|
|
131
|
+
## BigBase systemd unit
|
|
132
|
+
|
|
133
|
+
Full hardened unit at `/etc/systemd/system/bigbase.service`:
|
|
134
|
+
|
|
135
|
+
```ini
|
|
136
|
+
[Unit]
|
|
137
|
+
Description=BigBase BaaS Platform
|
|
138
|
+
Documentation=https://github.com/danielvm-git/bigbase
|
|
139
|
+
After=network.target caddy.service
|
|
140
|
+
Wants=caddy.service
|
|
141
|
+
|
|
142
|
+
[Service]
|
|
143
|
+
Type=simple
|
|
144
|
+
User=bigbase
|
|
145
|
+
Group=bigbase
|
|
146
|
+
WorkingDirectory=/opt/bigbase
|
|
147
|
+
ExecStart=/opt/bigbase/bin/bigbase serve \
|
|
148
|
+
--port 8080 \
|
|
149
|
+
--db /opt/bigbase/data/bigbase.db \
|
|
150
|
+
--sites-domain <your-domain.example.com>
|
|
151
|
+
EnvironmentFile=-/opt/bigbase/.env
|
|
152
|
+
Environment=BIGBASE_HOME=/opt/bigbase
|
|
153
|
+
Environment=HOME=/opt/bigbase
|
|
154
|
+
Environment=NPM_CONFIG_CACHE=/opt/bigbase/.npm
|
|
155
|
+
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
|
156
|
+
Restart=always
|
|
157
|
+
RestartSec=5
|
|
158
|
+
StartLimitIntervalSec=60
|
|
159
|
+
StartLimitBurst=3
|
|
160
|
+
TimeoutStopSec=30
|
|
161
|
+
KillSignal=SIGTERM
|
|
162
|
+
NoNewPrivileges=yes
|
|
163
|
+
PrivateTmp=yes
|
|
164
|
+
ProtectSystem=full
|
|
165
|
+
ProtectHome=yes
|
|
166
|
+
ProtectKernelTunables=yes
|
|
167
|
+
ProtectKernelModules=yes
|
|
168
|
+
ProtectControlGroups=yes
|
|
169
|
+
RestrictAddressFamilies=AF_INET AF_INET6
|
|
170
|
+
RestrictRealtime=yes
|
|
171
|
+
ReadWritePaths=/opt/bigbase/data
|
|
172
|
+
ReadWritePaths=/opt/bigbase/backups
|
|
173
|
+
ReadWritePaths=/opt/bigbase/secrets
|
|
174
|
+
ReadWritePaths=/opt/bigbase/.npm
|
|
175
|
+
ReadWritePaths=/opt/bigbase/logs
|
|
176
|
+
LimitNOFILE=65536
|
|
177
|
+
|
|
178
|
+
[Install]
|
|
179
|
+
WantedBy=multi-user.target
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## fail2ban jail.local
|
|
183
|
+
|
|
184
|
+
```ini
|
|
185
|
+
[sshd]
|
|
186
|
+
enabled = true
|
|
187
|
+
port = ssh
|
|
188
|
+
filter = sshd
|
|
189
|
+
logpath = /var/log/auth.log
|
|
190
|
+
maxretry = 3
|
|
191
|
+
bantime = 3600
|
|
192
|
+
findtime = 600
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Note: BigBase auth jail deferred — BigBase logs to journald, not a file. To enable, use `backend = systemd` with a journald filter.
|
|
196
|
+
|
|
197
|
+
## contabo-snapshot.sh
|
|
198
|
+
|
|
199
|
+
Deploy to `/opt/bigbase/scripts/contabo-snapshot.sh`. Reads credentials from `/opt/bigbase/.env` (same file used by BigBase systemd unit):
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
#!/bin/bash
|
|
203
|
+
set -e
|
|
204
|
+
LOG=/var/log/bigbase/snapshot.log
|
|
205
|
+
ENVFILE=/opt/bigbase/.env
|
|
206
|
+
INSTANCE_ID=<your-contabo-instance-id>
|
|
207
|
+
|
|
208
|
+
[ -f "$ENVFILE" ] || { echo "$(date -Iseconds) ERROR: $ENVFILE missing" >> "$LOG"; exit 1; }
|
|
209
|
+
set -a; source "$ENVFILE"; set +a
|
|
210
|
+
|
|
211
|
+
[ -z "$CONTABO_CLIENT_ID" ] && { echo "$(date -Iseconds) ERROR: CONTABO_CLIENT_ID not set" >> "$LOG"; exit 1; }
|
|
212
|
+
|
|
213
|
+
TOKEN=$(curl -s -d "client_id=$CONTABO_CLIENT_ID" \
|
|
214
|
+
-d "client_secret=$CONTABO_CLIENT_SECRET" \
|
|
215
|
+
--data-urlencode "username=$CONTABO_API_USER" \
|
|
216
|
+
--data-urlencode "password=$CONTABO_API_PASSWORD" \
|
|
217
|
+
-d 'grant_type=password' \
|
|
218
|
+
'https://auth.contabo.com/auth/realms/contabo/protocol/openid-connect/token' \
|
|
219
|
+
| jq -r '.access_token')
|
|
220
|
+
|
|
221
|
+
[ -z "$TOKEN" ] || [ "$TOKEN" = "null" ] && { echo "$(date -Iseconds) ERROR: auth failed" >> "$LOG"; exit 1; }
|
|
222
|
+
|
|
223
|
+
curl -s -X POST \
|
|
224
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
225
|
+
-H "Content-Type: application/json" \
|
|
226
|
+
-H "x-request-id: $(uuidgen 2>/dev/null || echo $RANDOM)" \
|
|
227
|
+
"https://api.contabo.com/v1/compute/instances/${INSTANCE_ID}/snapshots" \
|
|
228
|
+
| tee -a "$LOG"
|
|
229
|
+
|
|
230
|
+
echo >> "$LOG"
|
|
231
|
+
echo "$(date -Iseconds) Snapshot requested for $INSTANCE_ID" >> "$LOG"
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Contabo credentials
|
|
235
|
+
|
|
236
|
+
Add to `/opt/bigbase/.env` (same file used by BigBase systemd unit, deployed by GitHub Actions):
|
|
237
|
+
|
|
238
|
+
```
|
|
239
|
+
CONTABO_CLIENT_ID=
|
|
240
|
+
CONTABO_CLIENT_SECRET=
|
|
241
|
+
CONTABO_API_USER=
|
|
242
|
+
CONTABO_API_PASSWORD=
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Source: Contabo Customer Control Panel → API Details. ClientId and ClientSecret are generated there. API User is your email. API Password is set separately in the panel.
|
|
246
|
+
|
|
247
|
+
**Pipeline:** GitHub Secrets → deploy workflow → `/opt/bigbase/.env` on VPS. For local dev (`cntb get instances`), add to `.envrc`.
|
|
248
|
+
|
|
249
|
+
## Contabo instance info template
|
|
250
|
+
|
|
251
|
+
Fill in your instance details from the Contabo Customer Control Panel:
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
IP: <your-instance-ip>
|
|
255
|
+
IPv6: <your-instance-ipv6>
|
|
256
|
+
Region: <your-region>
|
|
257
|
+
OS: <your-os-version>
|
|
258
|
+
Disk: <your-disk-size>
|
|
259
|
+
Default user: root
|
|
260
|
+
Customer ID: <your-customer-id>
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## BigBase monitoring alert SQL
|
|
264
|
+
|
|
265
|
+
```sql
|
|
266
|
+
INSERT INTO monitoring_alerts (id, name, metric, threshold, operator, enabled, duration_seconds)
|
|
267
|
+
VALUES ('alert-001', 'VPS Disk above 80%', 'disk_used_percent', 80, 'gt', 1, 300);
|
|
268
|
+
|
|
269
|
+
INSERT INTO monitoring_alerts (id, name, metric, threshold, operator, enabled, duration_seconds)
|
|
270
|
+
VALUES ('alert-002', 'VPS CPU above 90%', 'cpu_percent', 90, 'gt', 1, 60);
|
|
271
|
+
|
|
272
|
+
INSERT INTO monitoring_alerts (id, name, metric, threshold, operator, enabled, duration_seconds)
|
|
273
|
+
VALUES ('alert-003', 'VPS RAM above 85%', 'mem_used_percent', 85, 'gt', 1, 120);
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Alerts are loaded at BigBase startup. Restart with `systemctl restart bigbase` after inserting.
|
|
277
|
+
|
|
278
|
+
## Base64 encoding workaround
|
|
279
|
+
|
|
280
|
+
When sending scripts through Orca terminal `--text`, the local bash shell interprets `$`, `(`, and `%`. Encode locally and decode remotely:
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
# Local
|
|
284
|
+
cat script.sh | base64
|
|
285
|
+
|
|
286
|
+
# Remote terminal
|
|
287
|
+
echo '<base64-output>' | base64 -d > script.sh
|
|
288
|
+
```
|
|
@@ -123,5 +123,5 @@ After writing the bug file, print a one-line summary of the root cause and sugge
|
|
|
123
123
|
|
|
124
124
|
<!-- story: e35s10 -->
|
|
125
125
|
|
|
126
|
-
- [Feathers' Seams & Characterization Tests](
|
|
127
|
-
- [Fowler's Code Smells](
|
|
126
|
+
- [Feathers' Seams & Characterization Tests](https://github.com/danielvm-git/bigpowers/blob/main/docs/references/feathers.md) — seam types, characterization tests, and the legacy code change algorithm
|
|
127
|
+
- [Fowler's Code Smells](https://github.com/danielvm-git/bigpowers/blob/main/docs/references/fowler.md) — identifying structural problems by smell before investigating
|