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.
- package/CHANGELOG.md +7 -0
- package/CLAUDE.md +5 -4
- package/CONVENTIONS.md +54 -12
- package/README.md +5 -5
- 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/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/SKILL.md +18 -18
- package/model-domain/SKILL.md +7 -7
- package/orchestrate-project/SKILL.md +5 -8
- package/package.json +4 -2
- package/plan-release/SKILL.md +63 -39
- package/plan-work/SKILL.md +6 -6
- package/research-first/SKILL.md +3 -3
- 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-status-from-epics.sh +51 -0
- package/scripts/validate-specs-yaml.sh +41 -0
- package/scripts/yaml-tools.py +144 -0
- package/session-state/SKILL.md +59 -50
- package/setup-environment/SKILL.md +1 -1
- package/slice-tasks/SKILL.md +6 -6
- 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 +3 -3
- 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,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"}'`
|
package/scope-work/SKILL.md
CHANGED
|
@@ -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/
|
|
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/
|
|
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 (
|
|
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/
|
|
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
|
|
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/
|
|
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"
|
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
|
|
@@ -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"
|