bigpowers 1.2.3 → 1.3.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/CHANGELOG.md +15 -0
- package/CLAUDE.md +5 -4
- package/CONVENTIONS.md +55 -13
- package/README.md +10 -8
- package/SKILL-INDEX.md +14 -11
- package/assess-impact/SKILL.md +2 -2
- package/build-epic/SKILL.md +42 -0
- package/change-request/SKILL.md +16 -16
- package/compose-workflow/REFERENCE.md +3 -3
- package/compose-workflow/SKILL.md +1 -1
- package/deepen-architecture/SKILL.md +6 -6
- package/define-success/SKILL.md +1 -1
- package/delegate-task/SKILL.md +4 -4
- package/develop-tdd/SKILL.md +5 -5
- package/dispatch-agents/SKILL.md +2 -2
- package/evolve-skill/SKILL.md +2 -2
- package/execute-plan/SKILL.md +22 -59
- package/fix-bug/SKILL.md +37 -0
- package/grill-with-docs/SKILL.md +1 -1
- package/inspect-quality/SKILL.md +5 -5
- package/investigate-bug/SKILL.md +2 -2
- package/kickoff-branch/SKILL.md +1 -1
- package/map-codebase/SKILL.md +4 -4
- package/migrate-spec/REFERENCE-GSD.md +35 -41
- package/migrate-spec/REFERENCE.md +43 -44
- package/migrate-spec/SKILL.md +20 -20
- package/model-domain/SKILL.md +7 -7
- package/orchestrate-project/REFERENCE.md +10 -10
- package/orchestrate-project/SKILL.md +13 -16
- package/package.json +7 -7
- package/plan-release/SKILL.md +63 -39
- package/plan-work/SKILL.md +6 -6
- package/request-review/SKILL.md +2 -2
- package/research-first/SKILL.md +3 -3
- package/run-evals/REFERENCE.md +1 -1
- package/run-planning/SKILL.md +24 -0
- package/scope-work/SKILL.md +6 -6
- package/scripts/bp-yaml-set.sh +9 -0
- package/scripts/bp-yaml-snapshot.sh +23 -0
- package/scripts/convert-legado.sh +153 -0
- package/scripts/enrich-epics-from-archive.sh +101 -0
- package/scripts/land-branch.sh +5 -1
- package/scripts/project-survey.sh +18 -9
- package/scripts/sync-bugs-registry.sh +53 -0
- package/scripts/sync-skills.sh +5 -5
- package/scripts/sync-status-from-epics.sh +51 -0
- package/scripts/validate-specs-yaml.sh +41 -0
- package/scripts/yaml-tools.py +144 -0
- package/seed-conventions/SKILL.md +3 -3
- package/session-state/SKILL.md +59 -50
- package/setup-environment/SKILL.md +1 -1
- package/slice-tasks/SKILL.md +6 -6
- package/spike-prototype/SKILL.md +5 -5
- package/survey-context/SKILL.md +38 -27
- package/trace-requirement/SKILL.md +8 -8
- package/using-bigpowers/SKILL.md +10 -9
- package/validate-fix/SKILL.md +4 -4
- package/verify-work/SKILL.md +12 -17
- package/visual-dashboard/SKILL.md +25 -74
- package/visual-dashboard/scripts/cockpit.html +66 -0
- package/visual-dashboard/scripts/read-specs-status.cjs +123 -0
- package/visual-dashboard/scripts/server.cjs +40 -0
- package/write-document/SKILL.md +1 -1
- package/maintain-wiki/SKILL.md +0 -130
- package/profiles/obsidian-wiki.md +0 -120
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# convert-legado.sh — RELEASE-PLAN.md + SCOPE.md → YAML layout (one-time migration helper)
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
SPECS="$REPO_ROOT/specs"
|
|
6
|
+
RP_MD="$SPECS/RELEASE-PLAN.md"
|
|
7
|
+
SCOPE_MD="$SPECS/SCOPE.md"
|
|
8
|
+
STATE_MD="$SPECS/STATE.md"
|
|
9
|
+
|
|
10
|
+
mkdir -p "$SPECS/requirements/snapshots" "$SPECS/epics" "$SPECS/archive"
|
|
11
|
+
|
|
12
|
+
if [[ ! -f "$RP_MD" ]]; then
|
|
13
|
+
echo "convert-legado: no $RP_MD — skip"
|
|
14
|
+
exit 0
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
# Archive legacy MD if YAML already exists (idempotent)
|
|
18
|
+
archive_if_needed() {
|
|
19
|
+
local src="$1"
|
|
20
|
+
[[ -f "$src" ]] || return 0
|
|
21
|
+
local base
|
|
22
|
+
base=$(basename "$src")
|
|
23
|
+
if [[ ! -f "$SPECS/archive/$base" ]]; then
|
|
24
|
+
cp "$src" "$SPECS/archive/$base"
|
|
25
|
+
echo "archived: specs/archive/$base"
|
|
26
|
+
fi
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# Parse ### WSn — Title · WSJF X.X from RELEASE-PLAN.md
|
|
30
|
+
python3 - "$RP_MD" "$SPECS" <<'PY'
|
|
31
|
+
import json
|
|
32
|
+
import re
|
|
33
|
+
import sys
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
|
|
36
|
+
rp = Path(sys.argv[1])
|
|
37
|
+
specs = Path(sys.argv[2])
|
|
38
|
+
text = rp.read_text(encoding="utf-8")
|
|
39
|
+
|
|
40
|
+
version = "3.0.0"
|
|
41
|
+
m = re.search(r"v(\d+\.\d+\.\d+)", text)
|
|
42
|
+
if m:
|
|
43
|
+
version = m.group(1)
|
|
44
|
+
|
|
45
|
+
epics = []
|
|
46
|
+
for m in re.finditer(r"^### WS(\d+)\s+—\s+(.+?)\s+·\s+WSJF\s+([\d.]+)", text, re.M):
|
|
47
|
+
n, title, wsjf = m.groups()
|
|
48
|
+
eid = f"e{int(n):02d}"
|
|
49
|
+
slug = re.sub(r"[^a-z0-9]+", "-", title.lower()).strip("-")[:40]
|
|
50
|
+
epics.append((eid, title.strip(), float(wsjf), slug))
|
|
51
|
+
|
|
52
|
+
epic_lines = []
|
|
53
|
+
status_lines = []
|
|
54
|
+
for eid, title, wsjf, slug in epics:
|
|
55
|
+
fname = f"epics/{eid}-{slug}.yaml"
|
|
56
|
+
tj = json.dumps(title.strip())
|
|
57
|
+
epic_lines.append(
|
|
58
|
+
f" - id: {eid}\n title: {tj}\n wsjf: {wsjf}\n file: {fname}\n mode: flat"
|
|
59
|
+
)
|
|
60
|
+
status_lines.append(f" {eid}: done")
|
|
61
|
+
|
|
62
|
+
epic_path = specs / fname
|
|
63
|
+
if not epic_path.exists():
|
|
64
|
+
epic_path.write_text(
|
|
65
|
+
f"id: {eid}\ntitle: {tj}\nwsjf: {wsjf}\ncovers: []\nstories: []\n",
|
|
66
|
+
encoding="utf-8",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
release_yaml = f"""release:
|
|
70
|
+
version: "{version}"
|
|
71
|
+
codename: Consolidation
|
|
72
|
+
status: in_progress
|
|
73
|
+
semantic_release: true
|
|
74
|
+
bump_hint: minor
|
|
75
|
+
epics:
|
|
76
|
+
{chr(10).join(epic_lines)}
|
|
77
|
+
"""
|
|
78
|
+
(specs / "release-plan.yaml").write_text(release_yaml, encoding="utf-8")
|
|
79
|
+
|
|
80
|
+
exec_yaml = "development_status:\n" + "\n".join(status_lines) + "\n"
|
|
81
|
+
(specs / "execution-status.yaml").write_text(exec_yaml, encoding="utf-8")
|
|
82
|
+
print(f"convert-legado: wrote release-plan.yaml ({len(epics)} epics) + execution-status.yaml")
|
|
83
|
+
PY
|
|
84
|
+
|
|
85
|
+
# state.yaml from STATE.md or default
|
|
86
|
+
branch=$(git -C "$REPO_ROOT" branch --show-current 2>/dev/null || echo "main")
|
|
87
|
+
hash=$(git -C "$REPO_ROOT" rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
|
88
|
+
|
|
89
|
+
if [[ ! -f "$SPECS/state.yaml" ]]; then
|
|
90
|
+
cat > "$SPECS/state.yaml" <<EOF
|
|
91
|
+
active_flow: build_epic
|
|
92
|
+
active_epic_id: e01
|
|
93
|
+
active_story_id: null
|
|
94
|
+
release:
|
|
95
|
+
target_version: "3.0.0"
|
|
96
|
+
last_tag: null
|
|
97
|
+
last_publish: null
|
|
98
|
+
epic_cycle:
|
|
99
|
+
current_step: null
|
|
100
|
+
next_skill: survey-context
|
|
101
|
+
completed_steps: []
|
|
102
|
+
git:
|
|
103
|
+
branch: $branch
|
|
104
|
+
hash: $hash
|
|
105
|
+
handoff:
|
|
106
|
+
last_step_completed: null
|
|
107
|
+
open_decisions: []
|
|
108
|
+
next_skill: survey-context
|
|
109
|
+
EOF
|
|
110
|
+
echo "convert-legado: wrote state.yaml"
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
# requirements/SCOPE stub from SCOPE.md if missing
|
|
114
|
+
if [[ -f "$SCOPE_MD" && ! -f "$SPECS/requirements/SCOPE_LATEST.yaml" ]]; then
|
|
115
|
+
mkdir -p "$SPECS/requirements"
|
|
116
|
+
python3 - "$SCOPE_MD" "$SPECS/requirements/SCOPE_LATEST.yaml" <<'PY'
|
|
117
|
+
import sys
|
|
118
|
+
from pathlib import Path
|
|
119
|
+
src = Path(sys.argv[1]).read_text(encoding="utf-8")
|
|
120
|
+
out = Path(sys.argv[2])
|
|
121
|
+
# Minimal YAML wrapper — full parse is manual follow-up
|
|
122
|
+
body = src.replace('"', '\\"').replace("\n", "\\n")
|
|
123
|
+
out.write_text(
|
|
124
|
+
'version: "1"\n'
|
|
125
|
+
'source: specs/SCOPE.md\n'
|
|
126
|
+
'migrated: true\n'
|
|
127
|
+
'summary: "See specs/archive/SCOPE.md for prose; refine this file with scope-work"\n',
|
|
128
|
+
encoding="utf-8",
|
|
129
|
+
)
|
|
130
|
+
print("convert-legado: wrote requirements/SCOPE_LATEST.yaml (stub)")
|
|
131
|
+
PY
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
if [[ ! -f "$SPECS/requirements/VISION_LATEST.yaml" ]]; then
|
|
135
|
+
mkdir -p "$SPECS/requirements"
|
|
136
|
+
cat > "$SPECS/requirements/VISION_LATEST.yaml" <<'EOF'
|
|
137
|
+
version: "1"
|
|
138
|
+
north_star: "Maintain senior-grade quality while scaling agentic autonomy"
|
|
139
|
+
success_criteria:
|
|
140
|
+
- "Compliance suite >= 94%"
|
|
141
|
+
- "59 skills synced and documented"
|
|
142
|
+
out_of_scope:
|
|
143
|
+
- "Domain-specific scaffold skills"
|
|
144
|
+
- "SaaS tracker integrations"
|
|
145
|
+
EOF
|
|
146
|
+
echo "convert-legado: wrote requirements/VISION_LATEST.yaml"
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
archive_if_needed "$RP_MD"
|
|
150
|
+
archive_if_needed "$SCOPE_MD"
|
|
151
|
+
archive_if_needed "$STATE_MD"
|
|
152
|
+
|
|
153
|
+
bash "$REPO_ROOT/scripts/validate-specs-yaml.sh" "$SPECS"
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# enrich-epics-from-archive.sh — populate specs/epics from archive RELEASE-PLAN.md
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
ARCHIVE="${1:-$REPO_ROOT/specs/archive/RELEASE-PLAN.md}"
|
|
6
|
+
SPECS="$REPO_ROOT/specs"
|
|
7
|
+
|
|
8
|
+
python3 - "$ARCHIVE" "$SPECS" <<'PY'
|
|
9
|
+
import json
|
|
10
|
+
import re
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
archive = Path(sys.argv[1])
|
|
15
|
+
specs = Path(sys.argv[2])
|
|
16
|
+
text = archive.read_text(encoding="utf-8")
|
|
17
|
+
|
|
18
|
+
epic_re = re.compile(
|
|
19
|
+
r"^### WS(\d+)\s+—\s+(.+?)\s+·\s+WSJF\s+([\d.]+)\s*$", re.M
|
|
20
|
+
)
|
|
21
|
+
sections = []
|
|
22
|
+
for m in epic_re.finditer(text):
|
|
23
|
+
start = m.end()
|
|
24
|
+
nxt = epic_re.search(text, start)
|
|
25
|
+
end = nxt.start() if nxt else len(text)
|
|
26
|
+
sections.append((m.group(1), m.group(2).strip(), float(m.group(3)), text[start:end]))
|
|
27
|
+
|
|
28
|
+
status_lines = []
|
|
29
|
+
for n, title, wsjf, body in sections:
|
|
30
|
+
eid = f"e{int(n):02d}"
|
|
31
|
+
slug = re.sub(r"[^a-z0-9]+", "-", title.lower()).strip("-")[:40]
|
|
32
|
+
fname = f"epics/{eid}-{slug}.yaml"
|
|
33
|
+
epic_path = specs / fname
|
|
34
|
+
|
|
35
|
+
tasks_raw = []
|
|
36
|
+
for line in body.splitlines():
|
|
37
|
+
lm = re.match(r"^-\s+\[[ xX]\]\s+(.+)$", line.strip())
|
|
38
|
+
if lm:
|
|
39
|
+
tasks_raw.append(lm.group(1).strip())
|
|
40
|
+
verify_m = re.search(r"→\s*verify:\s*`([^`]+)`", body)
|
|
41
|
+
epic_verify = verify_m.group(1) if verify_m else None
|
|
42
|
+
|
|
43
|
+
sid = f"{eid}s01"
|
|
44
|
+
story_tasks = []
|
|
45
|
+
for i, t in enumerate(tasks_raw, start=1):
|
|
46
|
+
tid = f"{eid}s{i:02d}" if len(tasks_raw) > 1 else sid
|
|
47
|
+
story_tasks.append({"desc": t, "verify": epic_verify or "true"})
|
|
48
|
+
status_lines.append(f" {tid}: done")
|
|
49
|
+
|
|
50
|
+
if not story_tasks and epic_verify:
|
|
51
|
+
story_tasks.append({"desc": title, "verify": epic_verify})
|
|
52
|
+
|
|
53
|
+
stories_yaml = []
|
|
54
|
+
if len(story_tasks) == 1:
|
|
55
|
+
stories_yaml.append(
|
|
56
|
+
{
|
|
57
|
+
"id": sid,
|
|
58
|
+
"title": title,
|
|
59
|
+
"tasks": story_tasks,
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
status_lines.append(f" {eid}: done")
|
|
63
|
+
else:
|
|
64
|
+
for i, t in enumerate(tasks_raw, start=1):
|
|
65
|
+
tid = f"{eid}s{i:02d}"
|
|
66
|
+
stories_yaml.append(
|
|
67
|
+
{
|
|
68
|
+
"id": tid,
|
|
69
|
+
"title": t[:80],
|
|
70
|
+
"tasks": [{"desc": t, "verify": epic_verify or "true"}],
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
status_lines.append(f" {eid}: done")
|
|
74
|
+
|
|
75
|
+
tj = json.dumps(title)
|
|
76
|
+
lines = [
|
|
77
|
+
f"id: {eid}",
|
|
78
|
+
f"title: {tj}",
|
|
79
|
+
f"wsjf: {wsjf}",
|
|
80
|
+
"covers: []",
|
|
81
|
+
"stories:",
|
|
82
|
+
]
|
|
83
|
+
for st in stories_yaml:
|
|
84
|
+
lines.append(f" - id: {st['id']}")
|
|
85
|
+
lines.append(f" title: {json.dumps(st['title'])}")
|
|
86
|
+
lines.append(" tasks:")
|
|
87
|
+
for task in st["tasks"]:
|
|
88
|
+
lines.append(f" - desc: {json.dumps(task['desc'])}")
|
|
89
|
+
lines.append(f" verify: {json.dumps(task['verify'])}")
|
|
90
|
+
epic_path.parent.mkdir(parents=True, exist_ok=True)
|
|
91
|
+
epic_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
92
|
+
|
|
93
|
+
exec_path = specs / "execution-status.yaml"
|
|
94
|
+
exec_path.write_text(
|
|
95
|
+
"development_status:\n" + "\n".join(dict.fromkeys(status_lines)) + "\n",
|
|
96
|
+
encoding="utf-8",
|
|
97
|
+
)
|
|
98
|
+
print(f"enrich-epics: wrote {len(sections)} epics + execution-status.yaml")
|
|
99
|
+
PY
|
|
100
|
+
|
|
101
|
+
bash "$REPO_ROOT/scripts/validate-specs-yaml.sh" "$SPECS"
|
package/scripts/land-branch.sh
CHANGED
|
@@ -88,7 +88,11 @@ run_verify_suite() {
|
|
|
88
88
|
if jq -e '.scripts.test' package.json >/dev/null 2>&1; then
|
|
89
89
|
local test_script
|
|
90
90
|
test_script=$(jq -r '.scripts.test' package.json)
|
|
91
|
-
if [ "$test_script"
|
|
91
|
+
if [ "$test_script" = "echo \"Error: no test specified\" && exit 1" ]; then
|
|
92
|
+
:
|
|
93
|
+
elif [ "$test_script" = "false" ]; then
|
|
94
|
+
:
|
|
95
|
+
else
|
|
92
96
|
npm test
|
|
93
97
|
return
|
|
94
98
|
fi
|
|
@@ -8,10 +8,14 @@ has_conventions=$([[ -f "CONVENTIONS.md" ]] && echo "true" || echo "false")
|
|
|
8
8
|
has_claude=$([[ -f "CLAUDE.md" ]] && echo "true" || echo "false")
|
|
9
9
|
has_specs=$([[ -d "specs" ]] && echo "true" || echo "false")
|
|
10
10
|
|
|
11
|
-
# 2. Scan specs/
|
|
11
|
+
# 2. Scan specs/ (YAML-first)
|
|
12
12
|
specs_files=""
|
|
13
13
|
if [[ "$has_specs" == "true" ]]; then
|
|
14
|
-
specs_files=$(
|
|
14
|
+
specs_files=$( {
|
|
15
|
+
ls specs/*.yaml 2>/dev/null || true
|
|
16
|
+
ls specs/requirements/*.yaml 2>/dev/null || true
|
|
17
|
+
ls specs/*.md 2>/dev/null || true
|
|
18
|
+
} | xargs -n1 basename 2>/dev/null | sort -u | tr '\n' ',' | sed 's/,$//')
|
|
15
19
|
fi
|
|
16
20
|
|
|
17
21
|
# 3. Git state
|
|
@@ -19,13 +23,18 @@ current_branch=$(git branch --show-current 2>/dev/null || echo "not-a-repo")
|
|
|
19
23
|
git_status=$(git status --short 2>/dev/null | head -n 5 | tr '\n' ' ' | sed 's/"/\\"/g')
|
|
20
24
|
recent_logs=$(git log --oneline -3 2>/dev/null | tr '\n' ' ' | sed 's/"/\\"/g')
|
|
21
25
|
|
|
22
|
-
# 4. Phase mapping logic (
|
|
26
|
+
# 4. Phase mapping logic (YAML-first)
|
|
23
27
|
phase="Discover"
|
|
24
|
-
if [[ -f "specs/
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
if [[ -f "specs/state.yaml" ]]; then
|
|
29
|
+
flow=$(grep -E '^active_flow:' specs/state.yaml | sed 's/active_flow:[[:space:]]*//')
|
|
30
|
+
case "$flow" in
|
|
31
|
+
fix_bug) phase="Bug" ;;
|
|
32
|
+
build_epic) phase="Execute" ;;
|
|
33
|
+
planning) phase="Plan" ;;
|
|
34
|
+
esac
|
|
35
|
+
elif [[ -f "specs/release-plan.yaml" ]]; then
|
|
27
36
|
phase="Plan"
|
|
28
|
-
elif [[ -f "specs/SCOPE.md" ]]; then
|
|
37
|
+
elif [[ -f "specs/requirements/SCOPE_LATEST.yaml" ]] || [[ -f "specs/SCOPE.md" ]]; then
|
|
29
38
|
phase="Design"
|
|
30
39
|
elif [[ "$current_branch" != "main" && "$current_branch" != "master" ]]; then
|
|
31
40
|
phase="Initiate"
|
|
@@ -49,6 +58,6 @@ Welcome to Bigpowers. I have analyzed the project state.
|
|
|
49
58
|
You are currently in the '$phase' phase on the '$current_branch' branch.
|
|
50
59
|
EOF
|
|
51
60
|
|
|
52
|
-
if [[ -f "specs/
|
|
53
|
-
echo "
|
|
61
|
+
if [[ -f "specs/release-plan.yaml" ]]; then
|
|
62
|
+
echo "release-plan.yaml found. Read specs/state.yaml for active epic/story."
|
|
54
63
|
fi
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# sync-bugs-registry.sh — rebuild specs/bugs/registry.yaml from BUG-*.md frontmatter
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
BUGS="$REPO_ROOT/specs/bugs"
|
|
6
|
+
mkdir -p "$BUGS"
|
|
7
|
+
|
|
8
|
+
python3 - "$BUGS" <<'PY'
|
|
9
|
+
import re
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
bugs_dir = Path(sys.argv[1])
|
|
14
|
+
entries = []
|
|
15
|
+
for path in sorted(bugs_dir.glob("BUG-*.md")):
|
|
16
|
+
text = path.read_text(encoding="utf-8")
|
|
17
|
+
if not text.startswith("---"):
|
|
18
|
+
continue
|
|
19
|
+
end = text.find("---", 3)
|
|
20
|
+
if end < 0:
|
|
21
|
+
continue
|
|
22
|
+
fm = text[3:end]
|
|
23
|
+
data = {}
|
|
24
|
+
for line in fm.splitlines():
|
|
25
|
+
if ":" not in line:
|
|
26
|
+
continue
|
|
27
|
+
k, _, v = line.partition(":")
|
|
28
|
+
data[k.strip()] = v.strip().strip('"').strip("'")
|
|
29
|
+
bug_id = data.get("bug_id") or path.stem
|
|
30
|
+
entries.append(
|
|
31
|
+
{
|
|
32
|
+
"id": bug_id,
|
|
33
|
+
"status": data.get("status", "open"),
|
|
34
|
+
"severity": data.get("severity", "medium"),
|
|
35
|
+
"scope": data.get("scope", "general"),
|
|
36
|
+
"title": data.get("title", path.stem),
|
|
37
|
+
"file": f"bugs/{path.name}",
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
out = bugs_dir / "registry.yaml"
|
|
42
|
+
lines = ["# AUTO-GENERATED — sync-bugs-registry.sh", "bugs:"]
|
|
43
|
+
for e in entries:
|
|
44
|
+
lines.append(f" - id: {e['id']}")
|
|
45
|
+
lines.append(f" status: {e['status']}")
|
|
46
|
+
lines.append(f" severity: {e['severity']}")
|
|
47
|
+
lines.append(f" scope: {e['scope']}")
|
|
48
|
+
lines.append(f" title: \"{e['title'].replace(chr(34), '')}\"")
|
|
49
|
+
lines.append(f" file: {e['file']}")
|
|
50
|
+
lines.append("")
|
|
51
|
+
out.write_text("\n".join(lines), encoding="utf-8")
|
|
52
|
+
print(f"sync-bugs-registry: {len(entries)} bugs -> {out}")
|
|
53
|
+
PY
|
package/scripts/sync-skills.sh
CHANGED
|
@@ -31,7 +31,7 @@ for skill_dir in "$REPO_ROOT"/*/; do
|
|
|
31
31
|
description=$(awk '/^---/{f++} f==1 && /^description:/{p=1} p && !/^---/{print} f==2{exit}' "$skill_md" \
|
|
32
32
|
| sed 's/^description:[[:space:]]*//' \
|
|
33
33
|
| tr -d '\n' \
|
|
34
|
-
| sed 's/[[:space:]]
|
|
34
|
+
| sed -E 's/[[:space:]]+/ /g')
|
|
35
35
|
|
|
36
36
|
[[ -z "$name" ]] && continue
|
|
37
37
|
|
|
@@ -85,13 +85,13 @@ for skill_dir in "$REPO_ROOT"/*/; do
|
|
|
85
85
|
skill_count=$((skill_count + 1))
|
|
86
86
|
done
|
|
87
87
|
|
|
88
|
-
# Assemble final gemini-extension.json
|
|
89
|
-
pkg_version=$(
|
|
90
|
-
pkg_desc=$(
|
|
88
|
+
# Assemble final gemini-extension.json (top-level fields only — not scripts.version)
|
|
89
|
+
pkg_version=$(jq -r '.version' "$REPO_ROOT/package.json")
|
|
90
|
+
pkg_desc=$(jq -r '.description' "$REPO_ROOT/package.json")
|
|
91
91
|
|
|
92
92
|
jq -n --arg name "bigpowers" \
|
|
93
93
|
--arg version "$pkg_version" \
|
|
94
|
-
--arg desc "$skill_count
|
|
94
|
+
--arg desc "${skill_count} skills — ${pkg_desc}" \
|
|
95
95
|
'{name: $name, version: $version, description: $desc}' > "$GEMINI_MANIFEST"
|
|
96
96
|
|
|
97
97
|
# 4. Write OpenCode configuration: opencode.json (minimal project-level config)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# sync-status-from-epics.sh — seed execution-status.yaml keys from epic shards
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
SPECS="$REPO_ROOT/specs"
|
|
6
|
+
OUT="$SPECS/execution-status.yaml"
|
|
7
|
+
EPICS="$SPECS/epics"
|
|
8
|
+
|
|
9
|
+
python3 - "$EPICS" "$OUT" <<'PY'
|
|
10
|
+
import re
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
epics_dir = Path(sys.argv[1])
|
|
15
|
+
out = Path(sys.argv[2])
|
|
16
|
+
keys: dict[str, str] = {}
|
|
17
|
+
|
|
18
|
+
existing_path = epics_dir.parent / "execution-status.yaml"
|
|
19
|
+
if existing_path.exists():
|
|
20
|
+
existing = existing_path.read_text(encoding="utf-8")
|
|
21
|
+
for m in re.finditer(r"^ ([a-z0-9._-]+):\s*(\S+)", existing, re.M):
|
|
22
|
+
keys[m.group(1)] = m.group(2)
|
|
23
|
+
|
|
24
|
+
for epic_file in sorted(epics_dir.glob("e*.yaml")):
|
|
25
|
+
text = epic_file.read_text(encoding="utf-8")
|
|
26
|
+
em = re.search(r"^id:\s*(e\d+)", text, re.M)
|
|
27
|
+
if em:
|
|
28
|
+
eid = em.group(1)
|
|
29
|
+
keys.setdefault(eid, "backlog")
|
|
30
|
+
for sm in re.finditer(r"^\s+- id:\s*(e\d+s\d+)", text, re.M):
|
|
31
|
+
keys.setdefault(sm.group(1), "backlog")
|
|
32
|
+
|
|
33
|
+
for folder in sorted(epics_dir.glob("e*/")):
|
|
34
|
+
epic_yaml = folder / "epic.yaml"
|
|
35
|
+
if epic_yaml.exists():
|
|
36
|
+
text = epic_yaml.read_text(encoding="utf-8")
|
|
37
|
+
em = re.search(r"^id:\s*(e\d+)", text, re.M)
|
|
38
|
+
if em:
|
|
39
|
+
keys.setdefault(em.group(1), "backlog")
|
|
40
|
+
for story in (folder / "stories").glob("e*s*.md"):
|
|
41
|
+
m = re.match(r"(e\d+s\d+)", story.name)
|
|
42
|
+
if m:
|
|
43
|
+
keys.setdefault(m.group(1), "backlog")
|
|
44
|
+
|
|
45
|
+
lines = ["development_status:"]
|
|
46
|
+
for k in sorted(keys.keys()):
|
|
47
|
+
lines.append(f" {k}: {keys[k]}")
|
|
48
|
+
lines.append("")
|
|
49
|
+
out.write_text("\n".join(lines), encoding="utf-8")
|
|
50
|
+
print(f"sync-status-from-epics: wrote {out} ({len(keys)} keys)")
|
|
51
|
+
PY
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# validate-specs-yaml.sh — required keys for state, release-plan, execution-status
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
SPECS="${1:-$REPO_ROOT/specs}"
|
|
6
|
+
|
|
7
|
+
err=0
|
|
8
|
+
need() {
|
|
9
|
+
local file="$1" pattern="$2" msg="$3"
|
|
10
|
+
if [[ ! -f "$file" ]]; then
|
|
11
|
+
echo "missing: $file"
|
|
12
|
+
err=1
|
|
13
|
+
return
|
|
14
|
+
fi
|
|
15
|
+
if ! grep -qE "$pattern" "$file"; then
|
|
16
|
+
echo "$file: $msg"
|
|
17
|
+
err=1
|
|
18
|
+
fi
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
need "$SPECS/state.yaml" '^active_flow:' 'missing active_flow'
|
|
22
|
+
need "$SPECS/release-plan.yaml" '^release:' 'missing release block'
|
|
23
|
+
need "$SPECS/release-plan.yaml" 'version:' 'missing release.version'
|
|
24
|
+
need "$SPECS/release-plan.yaml" '^epics:' 'missing epics list'
|
|
25
|
+
need "$SPECS/execution-status.yaml" '^development_status:' 'missing development_status'
|
|
26
|
+
|
|
27
|
+
if [[ -f "$SPECS/release-plan.yaml" ]]; then
|
|
28
|
+
while IFS= read -r f; do
|
|
29
|
+
[[ -z "$f" ]] && continue
|
|
30
|
+
path="$SPECS/$f"
|
|
31
|
+
if [[ ! -f "$path" ]]; then
|
|
32
|
+
echo "release-plan: missing epic file $path"
|
|
33
|
+
err=1
|
|
34
|
+
fi
|
|
35
|
+
done < <(grep -E '^\s+file:' "$SPECS/release-plan.yaml" | sed 's/.*file:[[:space:]]*//')
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
if [[ "$err" -ne 0 ]]; then
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
echo "validate-specs-yaml: OK"
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Minimal YAML helpers for bigpowers specs (no external deps)."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _parse_simple_yaml(text: str) -> dict[str, Any]:
|
|
12
|
+
"""Parse flat and one-level-nested YAML (no lists of objects)."""
|
|
13
|
+
root: dict[str, Any] = {}
|
|
14
|
+
stack: list[tuple[int, dict[str, Any]]] = [(0, root)]
|
|
15
|
+
for raw in text.splitlines():
|
|
16
|
+
if not raw.strip() or raw.strip().startswith("#"):
|
|
17
|
+
continue
|
|
18
|
+
indent = len(raw) - len(raw.lstrip())
|
|
19
|
+
line = raw.strip()
|
|
20
|
+
if ":" not in line:
|
|
21
|
+
continue
|
|
22
|
+
key, _, val = line.partition(":")
|
|
23
|
+
key = key.strip()
|
|
24
|
+
val = val.strip()
|
|
25
|
+
while stack and indent < stack[-1][0]:
|
|
26
|
+
stack.pop()
|
|
27
|
+
cur = stack[-1][1]
|
|
28
|
+
if val == "":
|
|
29
|
+
nxt: dict[str, Any] = {}
|
|
30
|
+
cur[key] = nxt
|
|
31
|
+
stack.append((indent + 2, nxt))
|
|
32
|
+
else:
|
|
33
|
+
if val in ("true", "false"):
|
|
34
|
+
cur[key] = val == "true"
|
|
35
|
+
elif val.startswith('"') and val.endswith('"'):
|
|
36
|
+
cur[key] = val[1:-1]
|
|
37
|
+
elif val.startswith("'") and val.endswith("'"):
|
|
38
|
+
cur[key] = val[1:-1]
|
|
39
|
+
else:
|
|
40
|
+
try:
|
|
41
|
+
if "." in val:
|
|
42
|
+
cur[key] = float(val)
|
|
43
|
+
else:
|
|
44
|
+
cur[key] = int(val)
|
|
45
|
+
except ValueError:
|
|
46
|
+
cur[key] = val
|
|
47
|
+
return root
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _dump_value(val: Any, indent: int) -> str:
|
|
51
|
+
sp = " " * indent
|
|
52
|
+
if isinstance(val, dict):
|
|
53
|
+
lines = []
|
|
54
|
+
for k, v in val.items():
|
|
55
|
+
if isinstance(v, dict):
|
|
56
|
+
lines.append(f"{sp}{k}:")
|
|
57
|
+
lines.append(_dump_value(v, indent + 2).rstrip())
|
|
58
|
+
elif isinstance(v, bool):
|
|
59
|
+
lines.append(f"{sp}{k}: {'true' if v else 'false'}")
|
|
60
|
+
elif isinstance(v, (int, float)):
|
|
61
|
+
lines.append(f"{sp}{k}: {v}")
|
|
62
|
+
else:
|
|
63
|
+
s = str(v).replace('"', '\\"')
|
|
64
|
+
lines.append(f'{sp}{k}: "{s}"')
|
|
65
|
+
return "\n".join(lines) + "\n"
|
|
66
|
+
return f"{sp}{val}\n"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def dump_yaml(data: dict[str, Any]) -> str:
|
|
70
|
+
return _dump_value(data, 0)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def set_path(path: Path, dotted_key: str, value: str) -> None:
|
|
74
|
+
text = path.read_text(encoding="utf-8") if path.exists() else ""
|
|
75
|
+
data = _parse_simple_yaml(text) if text.strip() else {}
|
|
76
|
+
parts = dotted_key.split(".")
|
|
77
|
+
cur: Any = data
|
|
78
|
+
for part in parts[:-1]:
|
|
79
|
+
if part not in cur or not isinstance(cur[part], dict):
|
|
80
|
+
cur[part] = {}
|
|
81
|
+
cur = cur[part]
|
|
82
|
+
last = parts[-1]
|
|
83
|
+
if value.lower() in ("true", "false"):
|
|
84
|
+
cur[last] = value.lower() == "true"
|
|
85
|
+
else:
|
|
86
|
+
try:
|
|
87
|
+
cur[last] = int(value)
|
|
88
|
+
except ValueError:
|
|
89
|
+
try:
|
|
90
|
+
cur[last] = float(value)
|
|
91
|
+
except ValueError:
|
|
92
|
+
cur[last] = value.strip('"').strip("'")
|
|
93
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
94
|
+
path.write_text(dump_yaml(data), encoding="utf-8")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def validate_file(path: Path, required_keys: list[str]) -> list[str]:
|
|
98
|
+
errors: list[str] = []
|
|
99
|
+
if not path.exists():
|
|
100
|
+
return [f"missing: {path}"]
|
|
101
|
+
text = path.read_text(encoding="utf-8")
|
|
102
|
+
data = _parse_simple_yaml(text)
|
|
103
|
+
for key in required_keys:
|
|
104
|
+
parts = key.split(".")
|
|
105
|
+
cur: Any = data
|
|
106
|
+
for part in parts:
|
|
107
|
+
if not isinstance(cur, dict) or part not in cur:
|
|
108
|
+
errors.append(f"{path}: missing key '{key}'")
|
|
109
|
+
break
|
|
110
|
+
cur = cur[part]
|
|
111
|
+
return errors
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def main() -> int:
|
|
115
|
+
if len(sys.argv) < 2:
|
|
116
|
+
print("usage: yaml-tools.py set <file> <dotted.key> <value>", file=sys.stderr)
|
|
117
|
+
return 2
|
|
118
|
+
cmd = sys.argv[1]
|
|
119
|
+
if cmd == "set":
|
|
120
|
+
_, _, file, key, val = sys.argv
|
|
121
|
+
set_path(Path(file), key, val)
|
|
122
|
+
return 0
|
|
123
|
+
if cmd == "validate":
|
|
124
|
+
root = Path(sys.argv[2]) if len(sys.argv) > 2 else Path("specs")
|
|
125
|
+
errors: list[str] = []
|
|
126
|
+
errors += validate_file(root / "state.yaml", ["active_flow"])
|
|
127
|
+
errors += validate_file(
|
|
128
|
+
root / "release-plan.yaml", ["release", "release.version", "epics"]
|
|
129
|
+
)
|
|
130
|
+
errors += validate_file(
|
|
131
|
+
root / "execution-status.yaml", ["development_status"]
|
|
132
|
+
)
|
|
133
|
+
if errors:
|
|
134
|
+
for e in errors:
|
|
135
|
+
print(e)
|
|
136
|
+
return 1
|
|
137
|
+
print("OK")
|
|
138
|
+
return 0
|
|
139
|
+
print(f"unknown command: {cmd}", file=sys.stderr)
|
|
140
|
+
return 2
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
if __name__ == "__main__":
|
|
144
|
+
sys.exit(main())
|
|
@@ -73,7 +73,7 @@ Stack: [language, framework, runtime]
|
|
|
73
73
|
## Agent Rules
|
|
74
74
|
- **Workflow Mandate:** You MUST use the bigpowers skills (e.g. `plan-work`, `develop-tdd`, `orchestrate-project`) to perform tasks. DO NOT write code directly in response to a user prompt like "build this feature".
|
|
75
75
|
- Read specs/ before writing code.
|
|
76
|
-
- All planning and specifications MUST be written to `specs/` (
|
|
76
|
+
- All planning and specifications MUST be written to `specs/` (`requirements/SCOPE_LATEST.yaml`, `release-plan.yaml`, `epics/`) before any code is generated.
|
|
77
77
|
- Write the minimum code that solves the stated problem. Nothing extra.
|
|
78
78
|
- Never refactor, rename, or reorganize code outside the task scope.
|
|
79
79
|
- Run tests after every change. Show evidence before declaring done.
|
|
@@ -113,7 +113,7 @@ Stack: [language, framework, runtime]
|
|
|
113
113
|
## Agent Rules
|
|
114
114
|
- **Workflow Mandate:** You MUST use the bigpowers skills (e.g. `plan-work`, `develop-tdd`, `orchestrate-project`) to perform tasks. DO NOT write code directly in response to a user prompt like "build this feature".
|
|
115
115
|
- Read specs/ before writing code.
|
|
116
|
-
- All planning and specifications MUST be written to `specs/` (
|
|
116
|
+
- All planning and specifications MUST be written to `specs/` (`requirements/SCOPE_LATEST.yaml`, `release-plan.yaml`, `epics/`) before any code is generated.
|
|
117
117
|
- Write the minimum code that solves the stated problem. Nothing extra.
|
|
118
118
|
- Never refactor, rename, or reorganize code outside the task scope.
|
|
119
119
|
- Run tests after every change. Show evidence before declaring done.
|
|
@@ -153,7 +153,7 @@ Stack: [language, framework, runtime]
|
|
|
153
153
|
## Agent Rules
|
|
154
154
|
- **Workflow Mandate:** You MUST use the bigpowers skills (e.g. `plan-work`, `develop-tdd`, `orchestrate-project`) to perform tasks. DO NOT write code directly in response to a user prompt like "build this feature".
|
|
155
155
|
- Read specs/ before writing code.
|
|
156
|
-
- All planning and specifications MUST be written to `specs/` (
|
|
156
|
+
- All planning and specifications MUST be written to `specs/` (`requirements/SCOPE_LATEST.yaml`, `release-plan.yaml`, `epics/`) before any code is generated.
|
|
157
157
|
- Write the minimum code that solves the stated problem. Nothing extra.
|
|
158
158
|
- Never refactor, rename, or reorganize code outside the task scope.
|
|
159
159
|
- Run tests after every change. Show evidence before declaring done.
|