bigpowers 2.86.3 → 2.86.5
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/.agents/skills/craft-skill/SKILL.md +2 -2
- package/.agents/skills/define-success/SKILL.md +6 -59
- package/.agents/skills/deploy/SKILL.md +1 -1
- package/.agents/skills/elaborate-spec/SKILL.md +1 -1
- package/.agents/skills/harden-vps/SKILL.md +63 -60
- package/.agents/skills/investigate-bug/SKILL.md +2 -2
- package/.agents/skills/orchestrate-project/SKILL.md +1 -1
- package/.agents/skills/plan-refactor/SKILL.md +2 -2
- package/.agents/skills/plan-release/SKILL.md +2 -2
- package/.agents/skills/plan-work/SKILL.md +1 -1
- package/.agents/skills/using-bigpowers/SKILL.md +2 -2
- package/.agents/skills/wire-ci/SKILL.md +11 -14
- package/.codebuddy/skills/craft-skill/SKILL.md +2 -2
- package/.codebuddy/skills/define-success/SKILL.md +6 -59
- package/.codebuddy/skills/deploy/SKILL.md +1 -1
- package/.codebuddy/skills/elaborate-spec/SKILL.md +1 -1
- package/.codebuddy/skills/harden-vps/SKILL.md +63 -60
- package/.codebuddy/skills/investigate-bug/SKILL.md +2 -2
- package/.codebuddy/skills/orchestrate-project/SKILL.md +1 -1
- package/.codebuddy/skills/plan-refactor/SKILL.md +2 -2
- package/.codebuddy/skills/plan-release/SKILL.md +2 -2
- package/.codebuddy/skills/plan-work/SKILL.md +1 -1
- package/.codebuddy/skills/using-bigpowers/SKILL.md +2 -2
- package/.codebuddy/skills/wire-ci/SKILL.md +11 -14
- package/.codex/skills/craft-skill/SKILL.md +2 -2
- package/.codex/skills/define-success/SKILL.md +6 -59
- package/.codex/skills/deploy/SKILL.md +1 -1
- package/.codex/skills/elaborate-spec/SKILL.md +1 -1
- package/.codex/skills/harden-vps/SKILL.md +63 -60
- package/.codex/skills/investigate-bug/SKILL.md +2 -2
- package/.codex/skills/orchestrate-project/SKILL.md +1 -1
- package/.codex/skills/plan-refactor/SKILL.md +2 -2
- package/.codex/skills/plan-release/SKILL.md +2 -2
- package/.codex/skills/plan-work/SKILL.md +1 -1
- package/.codex/skills/using-bigpowers/SKILL.md +2 -2
- package/.codex/skills/wire-ci/SKILL.md +11 -14
- package/.hermes/skills/craft-skill/SKILL.md +2 -2
- package/.hermes/skills/define-success/SKILL.md +6 -59
- package/.hermes/skills/deploy/SKILL.md +1 -1
- package/.hermes/skills/elaborate-spec/SKILL.md +1 -1
- package/.hermes/skills/harden-vps/SKILL.md +63 -60
- package/.hermes/skills/investigate-bug/SKILL.md +2 -2
- package/.hermes/skills/orchestrate-project/SKILL.md +1 -1
- package/.hermes/skills/plan-refactor/SKILL.md +2 -2
- package/.hermes/skills/plan-release/SKILL.md +2 -2
- package/.hermes/skills/plan-work/SKILL.md +1 -1
- package/.hermes/skills/using-bigpowers/SKILL.md +2 -2
- package/.hermes/skills/wire-ci/SKILL.md +11 -14
- package/.kilocode/rules/craft-skill.md +2 -2
- package/.kilocode/rules/define-success.md +6 -59
- package/.kilocode/rules/deploy.md +1 -1
- package/.kilocode/rules/elaborate-spec.md +1 -1
- package/.kilocode/rules/harden-vps.md +63 -60
- package/.kilocode/rules/investigate-bug.md +2 -2
- package/.kilocode/rules/orchestrate-project.md +1 -1
- package/.kilocode/rules/plan-refactor.md +2 -2
- package/.kilocode/rules/plan-release.md +2 -2
- package/.kilocode/rules/plan-work.md +1 -1
- package/.kilocode/rules/using-bigpowers.md +2 -2
- package/.kilocode/rules/wire-ci.md +11 -14
- package/.mimocode/skills/craft-skill/SKILL.md +2 -2
- package/.mimocode/skills/define-success/SKILL.md +6 -59
- package/.mimocode/skills/deploy/SKILL.md +1 -1
- package/.mimocode/skills/elaborate-spec/SKILL.md +1 -1
- package/.mimocode/skills/harden-vps/SKILL.md +63 -60
- package/.mimocode/skills/investigate-bug/SKILL.md +2 -2
- package/.mimocode/skills/orchestrate-project/SKILL.md +1 -1
- package/.mimocode/skills/plan-refactor/SKILL.md +2 -2
- package/.mimocode/skills/plan-release/SKILL.md +2 -2
- package/.mimocode/skills/plan-work/SKILL.md +1 -1
- package/.mimocode/skills/using-bigpowers/SKILL.md +2 -2
- package/.mimocode/skills/wire-ci/SKILL.md +11 -14
- package/.pi/package.json +1 -1
- package/.pi/prompts/craft-skill.md +2 -2
- package/.pi/prompts/define-success.md +5 -58
- package/.pi/prompts/deploy.md +1 -1
- package/.pi/prompts/elaborate-spec.md +1 -1
- package/.pi/prompts/harden-vps.md +63 -60
- package/.pi/prompts/investigate-bug.md +2 -2
- package/.pi/prompts/orchestrate-project.md +1 -1
- package/.pi/prompts/plan-refactor.md +2 -2
- package/.pi/prompts/plan-release.md +2 -2
- package/.pi/prompts/plan-work.md +1 -1
- package/.pi/prompts/using-bigpowers.md +2 -2
- package/.pi/prompts/wire-ci.md +11 -14
- package/.pi/skills/craft-skill/SKILL.md +2 -2
- package/.pi/skills/define-success/SKILL.md +6 -59
- package/.pi/skills/deploy/SKILL.md +1 -1
- package/.pi/skills/elaborate-spec/SKILL.md +1 -1
- package/.pi/skills/harden-vps/SKILL.md +63 -60
- package/.pi/skills/investigate-bug/SKILL.md +2 -2
- package/.pi/skills/orchestrate-project/SKILL.md +1 -1
- package/.pi/skills/plan-refactor/SKILL.md +2 -2
- package/.pi/skills/plan-release/SKILL.md +2 -2
- package/.pi/skills/plan-work/SKILL.md +1 -1
- package/.pi/skills/using-bigpowers/SKILL.md +2 -2
- package/.pi/skills/wire-ci/SKILL.md +11 -14
- package/.trae/skills/craft-skill/SKILL.md +2 -2
- package/.trae/skills/define-success/SKILL.md +6 -59
- package/.trae/skills/deploy/SKILL.md +1 -1
- package/.trae/skills/elaborate-spec/SKILL.md +1 -1
- package/.trae/skills/harden-vps/SKILL.md +63 -60
- package/.trae/skills/investigate-bug/SKILL.md +2 -2
- package/.trae/skills/orchestrate-project/SKILL.md +1 -1
- package/.trae/skills/plan-refactor/SKILL.md +2 -2
- package/.trae/skills/plan-release/SKILL.md +2 -2
- package/.trae/skills/plan-work/SKILL.md +1 -1
- package/.trae/skills/using-bigpowers/SKILL.md +2 -2
- package/.trae/skills/wire-ci/SKILL.md +11 -14
- package/.windsurf/rules/craft-skill.md +2 -2
- package/.windsurf/rules/define-success.md +6 -59
- package/.windsurf/rules/deploy.md +1 -1
- package/.windsurf/rules/elaborate-spec.md +1 -1
- package/.windsurf/rules/harden-vps.md +63 -60
- package/.windsurf/rules/investigate-bug.md +2 -2
- package/.windsurf/rules/orchestrate-project.md +1 -1
- package/.windsurf/rules/plan-refactor.md +2 -2
- package/.windsurf/rules/plan-release.md +2 -2
- package/.windsurf/rules/plan-work.md +1 -1
- package/.windsurf/rules/using-bigpowers.md +2 -2
- package/.windsurf/rules/wire-ci.md +11 -14
- package/.zcode/skills/craft-skill/SKILL.md +2 -2
- package/.zcode/skills/define-success/SKILL.md +6 -59
- package/.zcode/skills/deploy/SKILL.md +1 -1
- package/.zcode/skills/elaborate-spec/SKILL.md +1 -1
- package/.zcode/skills/harden-vps/SKILL.md +63 -60
- package/.zcode/skills/investigate-bug/SKILL.md +2 -2
- package/.zcode/skills/orchestrate-project/SKILL.md +1 -1
- package/.zcode/skills/plan-refactor/SKILL.md +2 -2
- package/.zcode/skills/plan-release/SKILL.md +2 -2
- package/.zcode/skills/plan-work/SKILL.md +1 -1
- package/.zcode/skills/using-bigpowers/SKILL.md +2 -2
- package/.zcode/skills/wire-ci/SKILL.md +11 -14
- package/CHANGELOG.md +14 -0
- package/SKILL-INDEX.md +2 -2
- package/package.json +1 -1
- package/scripts/check-skill-links.py +35 -0
- package/scripts/golden-g12-status-consistency.sh +232 -0
- package/scripts/land-branch.sh +9 -3
- package/scripts/lib/__pycache__/link_utils.cpython-312.pyc +0 -0
- package/scripts/lib/__pycache__/srp_engine_links.cpython-312.pyc +0 -0
- package/scripts/lib/golden-suite-gates.sh +9 -0
- package/scripts/lib/target-contracts.sh +37 -1
- package/scripts/sync-status-from-epics.sh +8 -0
- package/scripts/test-target-contracts.sh +73 -0
- package/scripts/tombstone-skill.sh +6 -0
- package/skills/craft-skill/SKILL.md +2 -2
- package/skills/define-success/SKILL.md +7 -61
- package/skills/deploy/SKILL.md +1 -1
- package/skills/elaborate-spec/SKILL.md +1 -1
- package/skills/harden-vps/REFERENCE.md +39 -36
- package/skills/harden-vps/SKILL.md +24 -24
- package/skills/investigate-bug/SKILL.md +2 -2
- package/skills/orchestrate-project/REFERENCE.md +1 -1
- package/skills/plan-refactor/SKILL.md +2 -2
- package/skills/plan-release/SKILL.md +2 -2
- package/skills/plan-work/SKILL.md +1 -1
- package/skills/security-review/fixtures/CWE-639-idor-negative.go +1 -1
- package/skills/security-review/fixtures/CWE-639-idor-positive.go +1 -1
- package/skills/using-bigpowers/SKILL.md +2 -2
- package/skills/wire-ci/REFERENCE.md +11 -14
- package/skills-lock.json +12 -12
- package/.fix_regex.sh +0 -16
- package/.hermes-verify-sed-fix.py +0 -53
- package/_test_e28s01.sh +0 -2
- package/audit-independent-unwaived-2026-07-06.md +0 -49
- package/hermes-verify-sed-fix.py +0 -30
- package/instructions +0 -1
- package/scripts/hermes-verify-e39s02.sh +0 -28
- package/test_figlet.py +0 -17
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# story: e51s04
|
|
3
|
+
# golden-g12-status-consistency.sh — Planning SoT Consistency Gate (G-12)
|
|
4
|
+
#
|
|
5
|
+
# CONVENTIONS.md § specs/ declares specs/execution-status.yaml the sole SoT for
|
|
6
|
+
# story state. Nothing enforced that. sync-status-from-epics.sh is a *seeder*:
|
|
7
|
+
# it preloads keys from execution-status.yaml itself and uses setdefault(), so
|
|
8
|
+
# an existing key keeps its old value and epic.yaml's stories[].status is never
|
|
9
|
+
# read. Story status written by build-epic step 8 into a capsule could therefore
|
|
10
|
+
# disagree with the SoT forever, undetected
|
|
11
|
+
# (BUG-2026-07-26-planning-sot-drift).
|
|
12
|
+
#
|
|
13
|
+
# This gate makes the two agree or fails.
|
|
14
|
+
#
|
|
15
|
+
# Authority model (CONVENTIONS.md § specs/): execution-status.yaml is the sole
|
|
16
|
+
# SoT for story state; a capsule's stories[].status is derived. Capsules under
|
|
17
|
+
# specs/epics/archive/ are frozen historical records — an archived shard that
|
|
18
|
+
# still reads 'todo' while the SoT reads 'done' is normal history, not drift, so
|
|
19
|
+
# archive/ is excluded. Status vocabulary is normalised before comparison
|
|
20
|
+
# ('passing' == 'done'; 'todo'/'planned'/'not-started' == 'backlog').
|
|
21
|
+
#
|
|
22
|
+
# Checks:
|
|
23
|
+
# 1. A LIVE capsule must not disagree with execution-status.yaml (both the
|
|
24
|
+
# stories: and development_status: sections). In practice this catches a
|
|
25
|
+
# capsule marked done by build-epic step 8 whose SoT was never updated.
|
|
26
|
+
# 2. Every capsule_dir in release-plan.yaml must resolve to a real directory.
|
|
27
|
+
# 3. A live capsule must not also exist under archive/ — two shards for one
|
|
28
|
+
# epic makes "which status is authoritative" undecidable.
|
|
29
|
+
#
|
|
30
|
+
# Usage: bash scripts/golden-g12-status-consistency.sh [--self-test]
|
|
31
|
+
# Exit 0: SoT is consistent
|
|
32
|
+
# Exit 1: drift detected (or, under --self-test, the gate failed to detect
|
|
33
|
+
# deliberately injected drift — a vacuous gate, G-08 bug class)
|
|
34
|
+
|
|
35
|
+
set -euo pipefail
|
|
36
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
37
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/skill-common.sh"
|
|
38
|
+
resolve_repo_root
|
|
39
|
+
|
|
40
|
+
RED='\033[0;31m'
|
|
41
|
+
GREEN='\033[0;32m'
|
|
42
|
+
NC='\033[0m'
|
|
43
|
+
|
|
44
|
+
SELF_TEST=0
|
|
45
|
+
[[ "${1:-}" == "--self-test" ]] && SELF_TEST=1
|
|
46
|
+
|
|
47
|
+
check_tree() {
|
|
48
|
+
# $1 = specs dir to check
|
|
49
|
+
$PYTHON - "$1" <<'PY'
|
|
50
|
+
import sys, re
|
|
51
|
+
from pathlib import Path
|
|
52
|
+
import yaml
|
|
53
|
+
|
|
54
|
+
specs = Path(sys.argv[1])
|
|
55
|
+
epics_dir = specs / "epics"
|
|
56
|
+
exec_path = specs / "execution-status.yaml"
|
|
57
|
+
rp_path = specs / "release-plan.yaml"
|
|
58
|
+
|
|
59
|
+
problems = []
|
|
60
|
+
archived_skipped = 0
|
|
61
|
+
|
|
62
|
+
# Status vocabulary normalisation — different skills have written different
|
|
63
|
+
# words for the same state over the repo's history.
|
|
64
|
+
ALIASES = {
|
|
65
|
+
"passing": "done",
|
|
66
|
+
"complete": "done",
|
|
67
|
+
"completed": "done",
|
|
68
|
+
"todo": "backlog",
|
|
69
|
+
"planned": "backlog",
|
|
70
|
+
"not-started": "backlog",
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def norm(v):
|
|
75
|
+
return ALIASES.get(str(v).strip().lower(), str(v).strip().lower())
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
exec_data = yaml.safe_load(exec_path.read_text(encoding="utf-8")) or {}
|
|
79
|
+
dev = exec_data.get("development_status") or {}
|
|
80
|
+
stories = exec_data.get("stories") or {}
|
|
81
|
+
|
|
82
|
+
# --- Check 1: LIVE epic.yaml story status vs execution-status.yaml ---
|
|
83
|
+
for epic_file in sorted(epics_dir.rglob("epic.yaml")):
|
|
84
|
+
rel = epic_file.relative_to(specs)
|
|
85
|
+
if "archive" in epic_file.relative_to(epics_dir).parts:
|
|
86
|
+
archived_skipped += 1
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
ed = yaml.safe_load(epic_file.read_text(encoding="utf-8")) or {}
|
|
91
|
+
except Exception as exc:
|
|
92
|
+
problems.append(f"unparseable epic shard {rel}: {exc}")
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
for story in ed.get("stories") or []:
|
|
96
|
+
if not isinstance(story, dict):
|
|
97
|
+
continue
|
|
98
|
+
sid = story.get("id")
|
|
99
|
+
declared = story.get("status")
|
|
100
|
+
if not sid or not declared:
|
|
101
|
+
continue
|
|
102
|
+
if not re.match(r"^e\d+s\d+$", str(sid)):
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
sot_detail = stories.get(sid, {})
|
|
106
|
+
sot_detail = sot_detail.get("status") if isinstance(sot_detail, dict) else None
|
|
107
|
+
sot_summary = dev.get(sid)
|
|
108
|
+
|
|
109
|
+
if sot_detail is not None and norm(sot_detail) != norm(declared):
|
|
110
|
+
problems.append(
|
|
111
|
+
f"{sid}: {rel} says '{declared}' but execution-status.yaml "
|
|
112
|
+
f"stories.{sid}.status says '{sot_detail}'"
|
|
113
|
+
)
|
|
114
|
+
if sot_summary is not None and norm(sot_summary) != norm(declared):
|
|
115
|
+
problems.append(
|
|
116
|
+
f"{sid}: {rel} says '{declared}' but execution-status.yaml "
|
|
117
|
+
f"development_status.{sid} says '{sot_summary}'"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# --- Check 2: release-plan.yaml capsule_dir resolves ---
|
|
121
|
+
if rp_path.exists():
|
|
122
|
+
rp = yaml.safe_load(rp_path.read_text(encoding="utf-8")) or {}
|
|
123
|
+
for ep in rp.get("epics") or []:
|
|
124
|
+
if not isinstance(ep, dict):
|
|
125
|
+
continue
|
|
126
|
+
cd = ep.get("capsule_dir")
|
|
127
|
+
if cd and not (specs / cd).is_dir():
|
|
128
|
+
problems.append(
|
|
129
|
+
f"{ep.get('id')}: release-plan.yaml capsule_dir '{cd}' does not exist"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# --- Check 3: no epic has both a live and an archived capsule ---
|
|
133
|
+
archive_dir = epics_dir / "archive"
|
|
134
|
+
if archive_dir.is_dir():
|
|
135
|
+
live = {p.name for p in epics_dir.iterdir() if p.is_dir() and p.name != "archive"}
|
|
136
|
+
for p in archive_dir.iterdir():
|
|
137
|
+
if p.is_dir() and p.name in live:
|
|
138
|
+
problems.append(
|
|
139
|
+
f"{p.name}: capsule exists both live and under archive/ — "
|
|
140
|
+
f"authoritative status is undecidable"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
if archived_skipped:
|
|
144
|
+
print(f" info: {archived_skipped} archived capsule(s) skipped (frozen history)")
|
|
145
|
+
|
|
146
|
+
if problems:
|
|
147
|
+
for p in problems:
|
|
148
|
+
print(f" DRIFT {p}")
|
|
149
|
+
print(f"\n{len(problems)} inconsistencies")
|
|
150
|
+
sys.exit(1)
|
|
151
|
+
|
|
152
|
+
print(" ok : epic shards, execution-status.yaml and release-plan.yaml agree")
|
|
153
|
+
sys.exit(0)
|
|
154
|
+
PY
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if [[ "$SELF_TEST" -eq 1 ]]; then
|
|
158
|
+
echo "=== G-12 self-test: prove the gate can fail ==="
|
|
159
|
+
TMP=$(mktemp -d)
|
|
160
|
+
trap 'rm -rf "$TMP"' EXIT
|
|
161
|
+
# Copy only what check_tree reads — specs/ as a whole is ~28MB and the copy
|
|
162
|
+
# would dominate the gate's runtime.
|
|
163
|
+
mkdir -p "$TMP/specs"
|
|
164
|
+
cp -R "$REPO_ROOT/specs/epics" "$TMP/specs/epics"
|
|
165
|
+
cp "$REPO_ROOT/specs/execution-status.yaml" "$TMP/specs/"
|
|
166
|
+
cp "$REPO_ROOT/specs/release-plan.yaml" "$TMP/specs/"
|
|
167
|
+
|
|
168
|
+
# Inject drift into a LIVE capsule. Archived shards are excluded from the
|
|
169
|
+
# check, so mutating one would prove nothing — the gate would rightly stay
|
|
170
|
+
# silent and the self-test would report a false vacuity failure.
|
|
171
|
+
$PYTHON - "$TMP/specs" <<'PY'
|
|
172
|
+
import sys, re
|
|
173
|
+
from pathlib import Path
|
|
174
|
+
import yaml
|
|
175
|
+
|
|
176
|
+
specs = Path(sys.argv[1])
|
|
177
|
+
epics_dir = specs / "epics"
|
|
178
|
+
exec_path = specs / "execution-status.yaml"
|
|
179
|
+
data = yaml.safe_load(exec_path.read_text(encoding="utf-8")) or {}
|
|
180
|
+
|
|
181
|
+
for epic_file in sorted(epics_dir.rglob("epic.yaml")):
|
|
182
|
+
if "archive" in epic_file.relative_to(epics_dir).parts:
|
|
183
|
+
continue
|
|
184
|
+
ed = yaml.safe_load(epic_file.read_text(encoding="utf-8")) or {}
|
|
185
|
+
for story in ed.get("stories") or []:
|
|
186
|
+
if not isinstance(story, dict):
|
|
187
|
+
continue
|
|
188
|
+
sid, st = story.get("id"), story.get("status")
|
|
189
|
+
if sid and st and re.match(r"^e\d+s\d+$", str(sid)):
|
|
190
|
+
flipped = "backlog" if st != "backlog" else "done"
|
|
191
|
+
data.setdefault("stories", {}).setdefault(sid, {})["status"] = flipped
|
|
192
|
+
data.setdefault("development_status", {})[sid] = flipped
|
|
193
|
+
exec_path.write_text(
|
|
194
|
+
yaml.dump(data, default_flow_style=False, sort_keys=False),
|
|
195
|
+
encoding="utf-8",
|
|
196
|
+
)
|
|
197
|
+
print(f"injected: {sid} {st} -> {flipped} (live capsule)")
|
|
198
|
+
sys.exit(0)
|
|
199
|
+
print("ERROR: no live story with a status found to mutate", file=sys.stderr)
|
|
200
|
+
sys.exit(2)
|
|
201
|
+
PY
|
|
202
|
+
|
|
203
|
+
set +e
|
|
204
|
+
check_tree "$TMP/specs" >/dev/null 2>&1
|
|
205
|
+
injected_exit=$?
|
|
206
|
+
set -e
|
|
207
|
+
|
|
208
|
+
if [[ $injected_exit -ne 0 ]]; then
|
|
209
|
+
echo -e "${GREEN}PASS${NC} G-12 detected injected drift (exit $injected_exit)"
|
|
210
|
+
echo "G-12 self-test: PASS"
|
|
211
|
+
exit 0
|
|
212
|
+
fi
|
|
213
|
+
echo -e "${RED}FAIL${NC} G-12 passed on deliberately drifted specs — vacuous gate"
|
|
214
|
+
echo "G-12 self-test: FAIL — same bug class as G-08 / BUG-2026-07-26-planning-sot-drift"
|
|
215
|
+
exit 1
|
|
216
|
+
fi
|
|
217
|
+
|
|
218
|
+
echo "=== G-12: Planning SoT Consistency ==="
|
|
219
|
+
set +e
|
|
220
|
+
output=$(check_tree "$REPO_ROOT/specs" 2>&1)
|
|
221
|
+
actual_exit=$?
|
|
222
|
+
set -e
|
|
223
|
+
echo "$output"
|
|
224
|
+
|
|
225
|
+
if [[ $actual_exit -eq 0 ]]; then
|
|
226
|
+
echo -e "${GREEN}PASS${NC} planning SoT is consistent"
|
|
227
|
+
echo "G-12: PASS"
|
|
228
|
+
exit 0
|
|
229
|
+
fi
|
|
230
|
+
echo -e "${RED}FAIL${NC} planning SoT has drifted"
|
|
231
|
+
echo "G-12: FAIL — see BUG-2026-07-26-planning-sot-drift"
|
|
232
|
+
exit 1
|
package/scripts/land-branch.sh
CHANGED
|
@@ -36,12 +36,18 @@ COMMIT_MSG="${ARGS[1]:-}"
|
|
|
36
36
|
|
|
37
37
|
[ -n "$FEATURE_BRANCH" ] && [ -n "$COMMIT_MSG" ] || usage_land
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
# Both checks below judge the subject line, not the whole message. The length
|
|
40
|
+
# test used to read ${#COMMIT_MSG}, so any Conventional Commit carrying a body
|
|
41
|
+
# was rejected no matter how short its subject — the error text said "subject
|
|
42
|
+
# line" while the check measured the entire string.
|
|
43
|
+
COMMIT_SUBJECT="${COMMIT_MSG%%$'\n'*}"
|
|
44
|
+
|
|
45
|
+
if [[ ! "$COMMIT_SUBJECT" =~ $CONVENTIONAL_REGEX ]]; then
|
|
40
46
|
land_branch_deny "Commit message must follow Conventional Commits: <type>(<scope>): <subject>"
|
|
41
47
|
fi
|
|
42
48
|
|
|
43
|
-
if [ ${#
|
|
44
|
-
land_branch_deny "Commit subject line must be 72 characters or less"
|
|
49
|
+
if [ ${#COMMIT_SUBJECT} -gt 72 ]; then
|
|
50
|
+
land_branch_deny "Commit subject line must be 72 characters or less (got ${#COMMIT_SUBJECT})"
|
|
45
51
|
fi
|
|
46
52
|
|
|
47
53
|
# Block AI agent attribution (P1 — CONVENTIONS.md § Git Attribution)
|
|
Binary file
|
|
Binary file
|
|
@@ -14,6 +14,15 @@ GOLDEN_GATES=(
|
|
|
14
14
|
"g09-yaml-roundtrip:bash scripts/golden-g09-yaml-roundtrip.sh:false"
|
|
15
15
|
"g10-trace-anti-vacuity:bash scripts/golden-g10-trace-anti-vacuity.sh:false"
|
|
16
16
|
"g11-gitignore-venv:bash scripts/golden-g11-gitignore-venv.sh:false"
|
|
17
|
+
# Planning SoT consistency (BUG-2026-07-26-planning-sot-drift): live epic
|
|
18
|
+
# shards, execution-status.yaml and release-plan.yaml must agree. Self-test
|
|
19
|
+
# first so a vacuous gate is caught before its verdict is trusted.
|
|
20
|
+
"g12-status-selftest:bash scripts/golden-g12-status-consistency.sh --self-test:false"
|
|
21
|
+
"g12-status-consistency:bash scripts/golden-g12-status-consistency.sh:false"
|
|
22
|
+
# Anti-vacuity for the target-contract seam: every contract declared in
|
|
23
|
+
# targets.yaml must have a real assertion, and an unknown contract must FAIL
|
|
24
|
+
# rather than SKIP+exit 0 (seven contracts were fail-open before this).
|
|
25
|
+
"target-contracts:bash scripts/test-target-contracts.sh:false"
|
|
17
26
|
"install-helpers:bash scripts/test-install-helpers.sh:false"
|
|
18
27
|
"import-boundaries:bash scripts/check-import-boundaries.sh:false"
|
|
19
28
|
"skill-catalog:bash scripts/validate-skill-catalog.sh:false"
|
|
@@ -82,6 +82,33 @@ assert_agy_skills_nonempty() {
|
|
|
82
82
|
return 1
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
# Generic hooks-manifest assertion for every target that ships one under
|
|
86
|
+
# scripts/hooks/<target>/. Before this existed, cline/codebuddy/codex/kilocode/
|
|
87
|
+
# qwen/trae/windsurf all declared <target>_hooks_manifest in targets.yaml and
|
|
88
|
+
# every one of them fell through run_contract's default branch to SKIP + exit 0
|
|
89
|
+
# — seven declared contracts that could never fail.
|
|
90
|
+
assert_hooks_manifest() {
|
|
91
|
+
local id="$1"
|
|
92
|
+
local contract="$2"
|
|
93
|
+
local target="${contract%_hooks_manifest}"
|
|
94
|
+
local manifest="scripts/hooks/${target}/hooks-manifest.json"
|
|
95
|
+
|
|
96
|
+
if [[ ! -f "$manifest" ]]; then
|
|
97
|
+
echo "FAIL $id:$contract — $manifest missing"
|
|
98
|
+
return 1
|
|
99
|
+
fi
|
|
100
|
+
# verify-install.sh sources this file without lib/python-env.sh, so resolve an
|
|
101
|
+
# interpreter locally rather than relying on $PYTHON being set.
|
|
102
|
+
local py="${PYTHON:-}"
|
|
103
|
+
[[ -n "$py" ]] || py="$(command -v python3 || command -v python || true)"
|
|
104
|
+
if [[ -n "$py" ]] && ! "$py" -c "import json; json.load(open('$manifest'))" 2>/dev/null; then
|
|
105
|
+
echo "FAIL $id:$contract — $manifest is not valid JSON"
|
|
106
|
+
return 1
|
|
107
|
+
fi
|
|
108
|
+
echo "PASS $id:$contract"
|
|
109
|
+
return 0
|
|
110
|
+
}
|
|
111
|
+
|
|
85
112
|
run_contract() {
|
|
86
113
|
local id="$1"
|
|
87
114
|
local contract="$2"
|
|
@@ -90,10 +117,19 @@ run_contract() {
|
|
|
90
117
|
symlink_claude_md) assert_symlink_claude_md "$id" ;;
|
|
91
118
|
cursor_rules_nonempty) assert_cursor_rules_nonempty "$id" ;;
|
|
92
119
|
gemini_ext_exists) assert_gemini_ext_exists "$id" ;;
|
|
120
|
+
# gemini has its own adapter-level validator; keep it ahead of the generic
|
|
121
|
+
# pattern below so the more specific check wins.
|
|
93
122
|
gemini_hooks_manifest) assert_gemini_hooks_manifest "$id" ;;
|
|
123
|
+
*_hooks_manifest) assert_hooks_manifest "$id" "$contract" ;;
|
|
94
124
|
pi_skills_nonempty) assert_pi_skills_nonempty "$id" ;;
|
|
95
125
|
agy_skills_nonempty) assert_agy_skills_nonempty "$id" ;;
|
|
96
126
|
aider_bridge) assert_aider_bridge "$id" ;;
|
|
97
|
-
|
|
127
|
+
# A contract declared in targets.yaml with no assertion behind it is a
|
|
128
|
+
# broken contract, not a no-op. Failing here is what makes the registry an
|
|
129
|
+
# enforced interface rather than documentation.
|
|
130
|
+
*)
|
|
131
|
+
echo "FAIL $id:$contract — unknown contract (no assertion implemented)"
|
|
132
|
+
return 1
|
|
133
|
+
;;
|
|
98
134
|
esac
|
|
99
135
|
}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# sync-status-from-epics.sh — seed execution-status.yaml keys from epic shards
|
|
3
|
+
#
|
|
4
|
+
# SEEDER, NOT A RECONCILER. `keys` is preloaded from execution-status.yaml
|
|
5
|
+
# itself and applied with setdefault(), so an already-present key keeps its
|
|
6
|
+
# existing value; epic.yaml's stories[].status is never read. Running this does
|
|
7
|
+
# NOT pull a capsule's status into the SoT — it only adds missing keys and
|
|
8
|
+
# refreshes task counters. Drift between a capsule and the SoT is detected by
|
|
9
|
+
# scripts/golden-g12-status-consistency.sh (G-12), not corrected here.
|
|
10
|
+
# See specs/bugs/BUG-2026-07-26-planning-sot-drift.md.
|
|
3
11
|
set -euo pipefail
|
|
4
12
|
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
5
13
|
source "$(dirname "${BASH_SOURCE[0]}")/lib/skill-common.sh"
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# story: e37s08
|
|
3
|
+
# test-target-contracts.sh — anti-vacuity guard for run_contract.
|
|
4
|
+
#
|
|
5
|
+
# Every contract named in scripts/targets.yaml must have a real assertion behind
|
|
6
|
+
# it. run_contract used to end in `*) echo "SKIP ..."; return 0`, so seven
|
|
7
|
+
# declared contracts (cline/codebuddy/codex/kilocode/qwen/trae/windsurf
|
|
8
|
+
# _hooks_manifest) silently passed forever. This test fails if that default ever
|
|
9
|
+
# returns to being permissive, or if targets.yaml declares a contract nothing
|
|
10
|
+
# implements.
|
|
11
|
+
#
|
|
12
|
+
# Usage: bash scripts/test-target-contracts.sh
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/skill-common.sh"
|
|
15
|
+
resolve_repo_root
|
|
16
|
+
cd "$REPO_ROOT"
|
|
17
|
+
|
|
18
|
+
PASS=0
|
|
19
|
+
FAIL=0
|
|
20
|
+
pass() { echo "PASS: $1"; PASS=$((PASS + 1)); }
|
|
21
|
+
fail() { echo "FAIL: $1" >&2; FAIL=$((FAIL + 1)); }
|
|
22
|
+
|
|
23
|
+
echo "=== test-target-contracts.sh ==="
|
|
24
|
+
|
|
25
|
+
# shellcheck source=lib/target-contracts.sh
|
|
26
|
+
source "$REPO_ROOT/scripts/lib/target-contracts.sh"
|
|
27
|
+
|
|
28
|
+
# 1. An unknown contract MUST fail. This is the anti-vacuity assertion.
|
|
29
|
+
set +e
|
|
30
|
+
out=$(run_contract "faketarget" "definitely_not_a_real_contract" 2>&1)
|
|
31
|
+
rc=$?
|
|
32
|
+
set -e
|
|
33
|
+
if [[ $rc -ne 0 ]]; then
|
|
34
|
+
pass "unknown contract fails (exit $rc)"
|
|
35
|
+
else
|
|
36
|
+
fail "unknown contract returned 0 — run_contract is fail-open again: $out"
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# 2. Every contract declared in targets.yaml must resolve to an assertion.
|
|
40
|
+
# Probing with a nonexistent target id means a real assertion returns 1 with
|
|
41
|
+
# a FAIL line, while a missing implementation hits the default branch and
|
|
42
|
+
# reports "unknown contract".
|
|
43
|
+
declared=$(grep -E "^ - [a-z0-9_]+$" scripts/targets.yaml | sed 's/^ - //' | sort -u)
|
|
44
|
+
[[ -n "$declared" ]] || fail "no contracts parsed from targets.yaml"
|
|
45
|
+
|
|
46
|
+
for c in $declared; do
|
|
47
|
+
set +e
|
|
48
|
+
out=$(run_contract "__probe__" "$c" 2>&1)
|
|
49
|
+
set -e
|
|
50
|
+
if grep -q "unknown contract" <<<"$out"; then
|
|
51
|
+
fail "$c declared in targets.yaml but no assertion implements it"
|
|
52
|
+
else
|
|
53
|
+
pass "$c has an implementation"
|
|
54
|
+
fi
|
|
55
|
+
done
|
|
56
|
+
|
|
57
|
+
# 3. Every *_hooks_manifest target must actually ship a parseable manifest.
|
|
58
|
+
for c in $declared; do
|
|
59
|
+
case "$c" in
|
|
60
|
+
gemini_hooks_manifest) continue ;; # adapter-level validator, not a file check
|
|
61
|
+
*_hooks_manifest)
|
|
62
|
+
t="${c%_hooks_manifest}"
|
|
63
|
+
if [[ -f "scripts/hooks/${t}/hooks-manifest.json" ]]; then
|
|
64
|
+
pass "$t ships hooks-manifest.json"
|
|
65
|
+
else
|
|
66
|
+
fail "$t declares $c but scripts/hooks/${t}/hooks-manifest.json is missing"
|
|
67
|
+
fi
|
|
68
|
+
;;
|
|
69
|
+
esac
|
|
70
|
+
done
|
|
71
|
+
|
|
72
|
+
echo "test-target-contracts: $PASS passed, $FAIL failed"
|
|
73
|
+
[[ "$FAIL" -eq 0 ]] || exit 1
|
|
@@ -54,9 +54,15 @@ STORY_TAGS=$(grep -E '^#\s*story:' "$OLD_SKILL_MD" || true)
|
|
|
54
54
|
CURRENT_VERSION=$(jq -r '.version' package.json 2>/dev/null)
|
|
55
55
|
CREATED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
56
56
|
|
|
57
|
+
# A tombstone is still a live SKILL.md for its transition window, so it must
|
|
58
|
+
# carry the same required frontmatter as any other skill. Omitting `model:`
|
|
59
|
+
# made docs/references/model-profiles.md count one fewer skill than there are
|
|
60
|
+
# skill directories, which fails validate-doctrine's skill-count check.
|
|
57
61
|
cat > "$OLD_SKILL_MD" <<STUBEOF
|
|
58
62
|
---
|
|
59
63
|
name: ${OLD_NAME}
|
|
64
|
+
model: haiku
|
|
65
|
+
effort: light
|
|
60
66
|
description: "TOMBSTONE — renamed/merged to ${NEW_NAME}. This stub resolves for one release then is removed."
|
|
61
67
|
---
|
|
62
68
|
|
|
@@ -48,7 +48,7 @@ Skill-body instructional prose MUST follow [AGENTIC-STE.md](../../docs/AGENTIC-S
|
|
|
48
48
|
- Any reference materials to include?
|
|
49
49
|
- What specs/ output does it produce (if any)?
|
|
50
50
|
|
|
51
|
-
2. **Verify Principles** — Ensure the skill aligns with [PRINCIPLES.md](
|
|
51
|
+
2. **Verify Principles** — Ensure the skill aligns with [PRINCIPLES.md](../../docs/PRINCIPLES.md):
|
|
52
52
|
- Is it atomic (verb-noun)?
|
|
53
53
|
- Is it "deep" (simple interface, complex internal logic)?
|
|
54
54
|
- Does it include Hard Gates?
|
|
@@ -62,7 +62,7 @@ Skill-body instructional prose MUST follow [AGENTIC-STE.md](../../docs/AGENTIC-S
|
|
|
62
62
|
**Auto-skill from library README:** When user provides a library README or API docs URL, extract triggers and HARD GATEs.
|
|
63
63
|
Draft verify commands and specs/ output into SKILL.md. Do NOT invent APIs not in the source.
|
|
64
64
|
|
|
65
|
-
4. Add `model:` frontmatter (`haiku` | `sonnet` | `opus`) per [model-profiles.md](
|
|
65
|
+
4. Add `model:` frontmatter (`haiku` | `sonnet` | `opus`) per [model-profiles.md](../../docs/references/model-profiles.md).
|
|
66
66
|
|
|
67
67
|
> **STREAM CONTINUITY** — When writing file content, output in continuous chunks of ~200 lines. Do not pause. Continue immediately until complete. If you need time, emit a placeholder comment rather than going silent.
|
|
68
68
|
|
|
@@ -1,69 +1,15 @@
|
|
|
1
|
-
ARCHIVED: content absorbed into plan-work
|
|
2
|
-
|
|
3
1
|
---
|
|
4
2
|
name: define-success
|
|
5
|
-
model:
|
|
6
|
-
effort:
|
|
7
|
-
description:
|
|
3
|
+
model: haiku
|
|
4
|
+
effort: light
|
|
5
|
+
description: "TOMBSTONE — renamed/merged to plan-work. This stub resolves for one release then is removed."
|
|
8
6
|
---
|
|
9
7
|
|
|
10
|
-
#
|
|
11
|
-
|
|
12
|
-
Transform "do X" into "step → verify: <cmd>" pairs. This is the pre-flight check before `plan-work` or `develop-tdd` — it makes success observable and removes ambiguity about when you're done.
|
|
13
|
-
|
|
14
|
-
> **HARD GATE** — Success criteria must be testable and user-observable. "Code should be fast" is not testable. "Pageload latency < 2s" is testable.
|
|
15
|
-
|
|
16
|
-
## Why this matters
|
|
17
|
-
|
|
18
|
-
"Implement user authentication" is not a plan. It has no checkpoints, no evidence requirement, and no way to know if you're done. The Karpathy principle: every step must be independently verifiable with a runnable command. If you can't verify it, you can't prove it works.
|
|
19
|
-
|
|
20
|
-
## Process
|
|
21
|
-
|
|
22
|
-
### 1. Read the task statement
|
|
23
|
-
|
|
24
|
-
Take the task as stated (from conversation, or from `specs/epics/ (see slice-tasks)`, or from `specs/product/SCOPE_LATEST.yaml`).
|
|
25
|
-
|
|
26
|
-
### 2. Break into observable outcomes
|
|
27
|
-
|
|
28
|
-
For each thing the task requires, identify:
|
|
29
|
-
- The smallest unit of observable behavior that proves something works
|
|
30
|
-
- The command that proves it
|
|
31
|
-
|
|
32
|
-
Work at the level of behaviors (what the system does) not implementation steps (how you'll write the code).
|
|
33
|
-
|
|
34
|
-
### 3. Write the pairs
|
|
35
|
-
|
|
36
|
-
Format each pair as:
|
|
37
|
-
```
|
|
38
|
-
N. [What must be true] → verify: <runnable command>
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
Examples:
|
|
42
|
-
|
|
43
|
-
```
|
|
44
|
-
Task: "Add user registration to the API"
|
|
45
|
-
|
|
46
|
-
1. POST /users accepts {email, name} and returns {id, email, name} → verify: curl -s -X POST http://localhost:3000/users -H 'Content-Type: application/json' -d '{"email":"test@test.com","name":"Test"}' | jq .id
|
|
47
|
-
2. Duplicate email is rejected with 409 → verify: npm test -- user-registration.test.ts
|
|
48
|
-
3. Missing email is rejected with 400 and descriptive error → verify: npm test -- user-validation.test.ts
|
|
49
|
-
4. Password is hashed (never stored in plaintext) → verify: npm test -- user-security.test.ts
|
|
50
|
-
5. All existing tests still pass → verify: npm test
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
### 4. Challenge completeness
|
|
54
|
-
|
|
55
|
-
Ask yourself:
|
|
56
|
-
- Is there any behavior the task requires that isn't covered by a verify step?
|
|
57
|
-
- Is every verify step runnable right now without additional setup?
|
|
58
|
-
- Does the final step verify the whole thing end-to-end?
|
|
59
|
-
|
|
60
|
-
Add any missing pairs.
|
|
61
|
-
|
|
62
|
-
### 5. Output
|
|
8
|
+
# define-success (tombstoned)
|
|
63
9
|
|
|
64
|
-
|
|
10
|
+
This skill has been renamed or merged into `plan-work`. Use `plan-work` instead.
|
|
65
11
|
|
|
66
|
-
|
|
12
|
+
This stub exists for one release as a transition aid, then is removed once
|
|
13
|
+
`scripts/validate-tombstones.sh` flags it as expired.
|
|
67
14
|
|
|
68
|
-
## Verify
|
|
69
15
|
|
package/skills/deploy/SKILL.md
CHANGED
|
@@ -74,7 +74,7 @@ Platform-agnostic — supports multiple deployment targets via environment varia
|
|
|
74
74
|
|----------|---------|---------|
|
|
75
75
|
| Vercel | `VERCEL_TOKEN`, `VERCEL_PROJECT_ID` | `vercel deploy --prod --token $VERCEL_TOKEN` |
|
|
76
76
|
| Netlify | `NETLIFY_AUTH_TOKEN`, `NETLIFY_SITE_ID` | `netlify deploy --prod --auth $NETLIFY_AUTH_TOKEN --dir $ARTIFACT_DIR` |
|
|
77
|
-
|
|
|
77
|
+
| Platform MCP | MCP tool call | `mcp deploy` via your platform MCP server |
|
|
78
78
|
| rsync/SSH | `DEPLOY_SSH_USER`, `DEPLOY_SSH_HOST`, `DEPLOY_SSH_PATH` | `rsync -avz $ARTIFACT_DIR/ $DEPLOY_SSH_USER@$DEPLOY_SSH_HOST:$DEPLOY_SSH_PATH` |
|
|
79
79
|
| Custom | `DEPLOY_COMMAND` | Run any deploy command string |
|
|
80
80
|
|
|
@@ -65,7 +65,7 @@ Once the user has answered the main questions, probe for assumptions:
|
|
|
65
65
|
|
|
66
66
|
### 4. Synthesize and confirm
|
|
67
67
|
|
|
68
|
-
Summarize your understanding in 3–5 bullet points aligned with [countable-story-format.md](
|
|
68
|
+
Summarize your understanding in 3–5 bullet points aligned with [countable-story-format.md](../../docs/countable-story-format.md):
|
|
69
69
|
- The problem (feeds into §1 Business narrative)
|
|
70
70
|
- The solution and main flow (feeds into §5)
|
|
71
71
|
- The key constraints and alternative flows (feeds into §6)
|