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.
Files changed (65) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/CLAUDE.md +5 -4
  3. package/CONVENTIONS.md +55 -13
  4. package/README.md +10 -8
  5. package/SKILL-INDEX.md +14 -11
  6. package/assess-impact/SKILL.md +2 -2
  7. package/build-epic/SKILL.md +42 -0
  8. package/change-request/SKILL.md +16 -16
  9. package/compose-workflow/REFERENCE.md +3 -3
  10. package/compose-workflow/SKILL.md +1 -1
  11. package/deepen-architecture/SKILL.md +6 -6
  12. package/define-success/SKILL.md +1 -1
  13. package/delegate-task/SKILL.md +4 -4
  14. package/develop-tdd/SKILL.md +5 -5
  15. package/dispatch-agents/SKILL.md +2 -2
  16. package/evolve-skill/SKILL.md +2 -2
  17. package/execute-plan/SKILL.md +22 -59
  18. package/fix-bug/SKILL.md +37 -0
  19. package/grill-with-docs/SKILL.md +1 -1
  20. package/inspect-quality/SKILL.md +5 -5
  21. package/investigate-bug/SKILL.md +2 -2
  22. package/kickoff-branch/SKILL.md +1 -1
  23. package/map-codebase/SKILL.md +4 -4
  24. package/migrate-spec/REFERENCE-GSD.md +35 -41
  25. package/migrate-spec/REFERENCE.md +43 -44
  26. package/migrate-spec/SKILL.md +20 -20
  27. package/model-domain/SKILL.md +7 -7
  28. package/orchestrate-project/REFERENCE.md +10 -10
  29. package/orchestrate-project/SKILL.md +13 -16
  30. package/package.json +7 -7
  31. package/plan-release/SKILL.md +63 -39
  32. package/plan-work/SKILL.md +6 -6
  33. package/request-review/SKILL.md +2 -2
  34. package/research-first/SKILL.md +3 -3
  35. package/run-evals/REFERENCE.md +1 -1
  36. package/run-planning/SKILL.md +24 -0
  37. package/scope-work/SKILL.md +6 -6
  38. package/scripts/bp-yaml-set.sh +9 -0
  39. package/scripts/bp-yaml-snapshot.sh +23 -0
  40. package/scripts/convert-legado.sh +153 -0
  41. package/scripts/enrich-epics-from-archive.sh +101 -0
  42. package/scripts/land-branch.sh +5 -1
  43. package/scripts/project-survey.sh +18 -9
  44. package/scripts/sync-bugs-registry.sh +53 -0
  45. package/scripts/sync-skills.sh +5 -5
  46. package/scripts/sync-status-from-epics.sh +51 -0
  47. package/scripts/validate-specs-yaml.sh +41 -0
  48. package/scripts/yaml-tools.py +144 -0
  49. package/seed-conventions/SKILL.md +3 -3
  50. package/session-state/SKILL.md +59 -50
  51. package/setup-environment/SKILL.md +1 -1
  52. package/slice-tasks/SKILL.md +6 -6
  53. package/spike-prototype/SKILL.md +5 -5
  54. package/survey-context/SKILL.md +38 -27
  55. package/trace-requirement/SKILL.md +8 -8
  56. package/using-bigpowers/SKILL.md +10 -9
  57. package/validate-fix/SKILL.md +4 -4
  58. package/verify-work/SKILL.md +12 -17
  59. package/visual-dashboard/SKILL.md +25 -74
  60. package/visual-dashboard/scripts/cockpit.html +66 -0
  61. package/visual-dashboard/scripts/read-specs-status.cjs +123 -0
  62. package/visual-dashboard/scripts/server.cjs +40 -0
  63. package/write-document/SKILL.md +1 -1
  64. package/maintain-wiki/SKILL.md +0 -130
  65. 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"
@@ -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" != "echo \"Error: no test specified\" && exit 1" ] && [ "$test_script" != "false" ]; then
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=$(ls specs/*.md 2>/dev/null | xargs -n1 basename | tr '\n' ',' | sed 's/,$//')
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 (simplified for script)
26
+ # 4. Phase mapping logic (YAML-first)
23
27
  phase="Discover"
24
- if [[ -f "specs/PLAN.md" ]]; then
25
- phase="Execute"
26
- elif [[ -f "specs/TASKS.md" ]]; then
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/PLAN.md" ]]; then
53
- echo "Current PLAN.md found. I am ready to implement the next step."
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
@@ -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:]]\+/ /g')
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=$(grep '"version":' "$REPO_ROOT/package.json" | sed 's/.*: "\(.*\)".*/\1/')
90
- pkg_desc=$(grep '"description":' "$REPO_ROOT/package.json" | sed 's/.*: "\(.*\)".*/\1/')
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+ $pkg_desc" \
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/` (e.g. `specs/PLAN.md`) before any code is generated.
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/` (e.g. `specs/PLAN.md`) before any code is generated.
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/` (e.g. `specs/PLAN.md`) before any code is generated.
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.