bigpowers 1.2.3 → 1.3.0

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 (56) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/CLAUDE.md +5 -4
  3. package/CONVENTIONS.md +54 -12
  4. package/README.md +5 -5
  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/SKILL.md +1 -1
  10. package/deepen-architecture/SKILL.md +6 -6
  11. package/define-success/SKILL.md +1 -1
  12. package/delegate-task/SKILL.md +4 -4
  13. package/develop-tdd/SKILL.md +5 -5
  14. package/dispatch-agents/SKILL.md +2 -2
  15. package/evolve-skill/SKILL.md +2 -2
  16. package/execute-plan/SKILL.md +22 -59
  17. package/fix-bug/SKILL.md +37 -0
  18. package/grill-with-docs/SKILL.md +1 -1
  19. package/inspect-quality/SKILL.md +5 -5
  20. package/investigate-bug/SKILL.md +2 -2
  21. package/kickoff-branch/SKILL.md +1 -1
  22. package/map-codebase/SKILL.md +4 -4
  23. package/migrate-spec/SKILL.md +18 -18
  24. package/model-domain/SKILL.md +7 -7
  25. package/orchestrate-project/SKILL.md +5 -8
  26. package/package.json +4 -2
  27. package/plan-release/SKILL.md +63 -39
  28. package/plan-work/SKILL.md +6 -6
  29. package/research-first/SKILL.md +3 -3
  30. package/run-planning/SKILL.md +24 -0
  31. package/scope-work/SKILL.md +6 -6
  32. package/scripts/bp-yaml-set.sh +9 -0
  33. package/scripts/bp-yaml-snapshot.sh +23 -0
  34. package/scripts/convert-legado.sh +153 -0
  35. package/scripts/enrich-epics-from-archive.sh +101 -0
  36. package/scripts/land-branch.sh +5 -1
  37. package/scripts/project-survey.sh +18 -9
  38. package/scripts/sync-bugs-registry.sh +53 -0
  39. package/scripts/sync-status-from-epics.sh +51 -0
  40. package/scripts/validate-specs-yaml.sh +41 -0
  41. package/scripts/yaml-tools.py +144 -0
  42. package/session-state/SKILL.md +59 -50
  43. package/setup-environment/SKILL.md +1 -1
  44. package/slice-tasks/SKILL.md +6 -6
  45. package/survey-context/SKILL.md +38 -27
  46. package/trace-requirement/SKILL.md +8 -8
  47. package/using-bigpowers/SKILL.md +10 -9
  48. package/validate-fix/SKILL.md +3 -3
  49. package/verify-work/SKILL.md +12 -17
  50. package/visual-dashboard/SKILL.md +25 -74
  51. package/visual-dashboard/scripts/cockpit.html +66 -0
  52. package/visual-dashboard/scripts/read-specs-status.cjs +123 -0
  53. package/visual-dashboard/scripts/server.cjs +40 -0
  54. package/write-document/SKILL.md +1 -1
  55. package/maintain-wiki/SKILL.md +0 -130
  56. package/profiles/obsidian-wiki.md +0 -120
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: run-planning
3
+ model: sonnet
4
+ description: Advance discover-phase workflows and update specs/planning-status.yaml. Use for survey, scope, research, plan-release chain without touching build epics.
5
+ ---
6
+
7
+ # Run Planning
8
+
9
+ Updates `specs/planning-status.yaml` as discover-phase skills complete.
10
+
11
+ ## Workflows (default keys)
12
+
13
+ - `survey-context` → `scope-work` → `research-first` → `elaborate-spec` (optional) → `plan-release` → `slice-tasks`
14
+
15
+ ## Process
16
+
17
+ 1. Read `specs/planning-status.yaml` and `specs/state.yaml`.
18
+ 2. Find first workflow with `status: pending` or `optional` not yet run.
19
+ 3. Invoke the matching skill; on success set `status: done` for that workflow key.
20
+ 4. Set `state.yaml` `active_flow: planning` while in this chain.
21
+
22
+ ## Verify
23
+
24
+ → verify: `test -f specs/planning-status.yaml && grep -c 'status: done' specs/planning-status.yaml | awk '{if($1>=3) print "OK"; else print "INCOMPLETE"}'`
@@ -1,22 +1,22 @@
1
1
  ---
2
2
  name: scope-work
3
- description: Define what is in and out of scope for the current effort and save as specs/SCOPE.md. Use when user wants a PRD, scope definition, or before plan-release on a new initiative.
3
+ description: Define what is in and out of scope for the current effort and save as specs/requirements/SCOPE_LATEST.yaml. Use when user wants a PRD, scope definition, or before plan-release on a new initiative.
4
4
  model: sonnet
5
5
  ---
6
6
 
7
7
  # Scope Work
8
8
 
9
- Turn the current conversation into a bounded PRD at `specs/SCOPE.md`.
9
+ Turn the current conversation into a bounded PRD at `specs/requirements/SCOPE_LATEST.yaml`.
10
10
 
11
11
  ## Process
12
12
 
13
- 1. Read existing `specs/` artifacts (CONTEXT.md, RELEASE-PLAN.md if any).
13
+ 1. Read existing `specs/` artifacts (`release-plan.yaml`, `plans/TECH_STACK_LATEST.md`, `requirements/VISION_LATEST.yaml` if any).
14
14
  2. Interview (if needed): goal, users, in-scope, out-of-scope, constraints, success metrics.
15
- 3. Write `specs/SCOPE.md` with: Vision, In Scope, Out of Scope, Constraints, Success Criteria.
15
+ 3. Write `specs/requirements/SCOPE_LATEST.yaml` with: `core_value`, `summary`, `in_scope[]`, `out_of_scope[]`, `constraints`, `success_criteria`, `references`.
16
16
  4. Run `research-first` if external dependencies are proposed.
17
17
 
18
- > **HARD GATE** — Every "in scope" item must map to a future story ID or explicit "deferred" with reason.
18
+ > **HARD GATE** — Every `in_scope` item must map to a future epic/story ID or explicit deferred note in `out_of_scope`.
19
19
 
20
20
  ## Verify
21
21
 
22
- → verify: `test -f specs/SCOPE.md && grep -c "Out of Scope" specs/SCOPE.md | awk '{if($1>0) print "OK"; else print "MISSING"}'`
22
+ → verify: `test -f specs/requirements/SCOPE_LATEST.yaml && grep -c 'out_of_scope' specs/requirements/SCOPE_LATEST.yaml | awk '{if($1>0) print "OK"; else print "MISSING"}'`
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+ # bp-yaml-set.sh — patch a dotted key in a specs YAML file
3
+ # Usage: bp-yaml-set.sh <file> <dotted.key> <value>
4
+ set -euo pipefail
5
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
6
+ FILE="${1:?file}"
7
+ KEY="${2:?dotted.key}"
8
+ VAL="${3:?value}"
9
+ python3 "$REPO_ROOT/scripts/yaml-tools.py" set "$FILE" "$KEY" "$VAL"
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env bash
2
+ # bp-yaml-snapshot.sh — freeze release-plan + requirements into snapshots/
3
+ # Usage: bp-yaml-snapshot.sh [version] (default: read from release-plan.yaml)
4
+ set -euo pipefail
5
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
6
+ SPECS="$REPO_ROOT/specs"
7
+ VER="${1:-}"
8
+
9
+ if [[ -z "$VER" ]]; then
10
+ VER=$(grep -E '^\s+version:' "$SPECS/release-plan.yaml" | head -1 | sed 's/.*"\(.*\)".*/\1/')
11
+ fi
12
+ DEST="$SPECS/requirements/snapshots/release-$VER"
13
+ mkdir -p "$DEST"
14
+
15
+ for f in release-plan.yaml requirements/VISION_LATEST.yaml requirements/SCOPE_LATEST.yaml; do
16
+ src="$SPECS/$f"
17
+ [[ -f "$src" ]] || continue
18
+ base=$(basename "$f")
19
+ cp "$src" "$DEST/$base"
20
+ done
21
+
22
+ echo "bp-yaml-snapshot: wrote $DEST"
23
+ ls -la "$DEST"
@@ -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
@@ -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"