bigpowers 2.75.2 → 2.76.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/.pi/package.json +1 -1
- package/CHANGELOG.md +13 -0
- package/kernel/src/publish-to-wiki.sh +13 -3
- package/package.json +1 -1
- package/scripts/audit-compliance.sh +2 -3
- package/scripts/build-receipts.sh +2 -2
- package/scripts/build-skill-graph.sh +1 -1
- package/scripts/check-spec-drift.sh +2 -2
- package/scripts/check-spec-version-gap.sh +13 -13
- package/scripts/compile-rule-matrix.sh +2 -2
- package/scripts/decompose-conventions.sh +2 -2
- package/scripts/generate-adr-wiki.sh +2 -2
- package/scripts/generate-agent-guide.sh +2 -2
- package/scripts/generate-epics-wiki.sh +2 -2
- package/scripts/hooks/token-mgmt-pre-tool-use.sh +5 -5
- package/scripts/land-branch.sh +11 -11
- package/scripts/lib/audit-compliance-report.sh +1 -0
- package/scripts/lib/emit-okf-metrics.sh +1 -0
- package/scripts/lib/golden-suite-report.sh +1 -0
- package/scripts/lib/golden-suite-run.sh +5 -1
- package/scripts/lib/migrate-version-transforms.sh +3 -4
- package/scripts/lib/record-cycle-time-lib.sh +31 -0
- package/scripts/lib/sync-post.sh +6 -1
- package/scripts/lib/test-assertions.sh +11 -0
- package/scripts/lib/validate-okf-kinds.sh +6 -3
- package/scripts/record-cycle-time.sh +4 -28
- package/scripts/sync-skills.sh +3 -3
- package/scripts/test-adapters.sh +13 -13
- package/scripts/test-trace-matrix-size.sh +4 -4
- package/scripts/test-trace-strict.sh +8 -8
- package/scripts/validate-doctrine.sh +18 -18
- package/scripts/validate-skill-catalog.sh +10 -10
- package/scripts/verify-install.sh +39 -37
- package/tests/test-okf-generators.sh +5 -0
package/.pi/package.json
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
# [2.76.0](https://github.com/danielvm-git/bigpowers/compare/v2.75.2...v2.76.0) (2026-07-07)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **ci:** add OKF type fields and gate wiki publish on WIKI_PAT ([9628122](https://github.com/danielvm-git/bigpowers/commit/96281227e91ebed19300daed8dd05d51d6021c9e))
|
|
7
|
+
* **ci:** skip estimated commit_range check and wiki publish without PAT ([bf079ad](https://github.com/danielvm-git/bigpowers/commit/bf079ad384c4363980fe30383406d37629718b89))
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* **e37:** Reach — Close release: AGENTS.md spine, targets.yaml registry, OSS waves A-E; fix 6 compliance bugs ([5a7f407](https://github.com/danielvm-git/bigpowers/commit/5a7f407008818cec518969a2b46e5ae28ee130bb))
|
|
13
|
+
|
|
1
14
|
## [2.75.2](https://github.com/danielvm-git/bigpowers/compare/v2.75.1...v2.75.2) (2026-07-07)
|
|
2
15
|
|
|
3
16
|
|
|
@@ -83,8 +83,13 @@ push_to_wiki() {
|
|
|
83
83
|
local work_dir="${WIKI_WORK_DIR:-}"
|
|
84
84
|
|
|
85
85
|
if [[ -z "$wiki_repo" ]]; then
|
|
86
|
-
echo "publish-to-wiki: WIKI_REPO_URL is not set —
|
|
87
|
-
return
|
|
86
|
+
echo "publish-to-wiki: WIKI_REPO_URL is not set — skipping push" >&2
|
|
87
|
+
return 0
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
local clone_url="$wiki_repo"
|
|
91
|
+
if [[ -n "${WIKI_PAT:-}" && "$wiki_repo" == https://* ]]; then
|
|
92
|
+
clone_url="${wiki_repo/https:\/\//https://x-access-token:${WIKI_PAT}@}"
|
|
88
93
|
fi
|
|
89
94
|
|
|
90
95
|
if [[ -z "$work_dir" ]]; then
|
|
@@ -95,7 +100,7 @@ push_to_wiki() {
|
|
|
95
100
|
if [[ -d "$work_dir/.git" ]]; then
|
|
96
101
|
git -C "$work_dir" pull --rebase origin master 2>/dev/null || git -C "$work_dir" pull --rebase origin main
|
|
97
102
|
else
|
|
98
|
-
git clone "$
|
|
103
|
+
git clone "$clone_url" "$work_dir"
|
|
99
104
|
fi
|
|
100
105
|
|
|
101
106
|
rsync -a --delete "$staged_root/" "$work_dir/"
|
|
@@ -137,4 +142,9 @@ if [[ "$DRY_RUN" -eq 1 ]]; then
|
|
|
137
142
|
exit 0
|
|
138
143
|
fi
|
|
139
144
|
|
|
145
|
+
if [[ -z "${WIKI_PAT:-}" ]]; then
|
|
146
|
+
echo "publish-to-wiki: WIKI_PAT not set — skipping wiki push"
|
|
147
|
+
exit 0
|
|
148
|
+
fi
|
|
149
|
+
|
|
140
150
|
push_to_wiki "$STAGING"
|
package/package.json
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# audit-compliance.sh — Agentic Gherkin Compliance Harness (LLM-Judge Upgrade)
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
source "$
|
|
6
|
-
source "$_LIB/audit-compliance-report.sh"
|
|
4
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/audit-compliance-runner.sh"
|
|
5
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/audit-compliance-report.sh"
|
|
7
6
|
|
|
8
7
|
audit_show_help() {
|
|
9
8
|
cat <<EOF
|
|
@@ -6,7 +6,7 @@ source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
|
6
6
|
ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || { echo "build-receipts: not inside a git repo" >&2; exit 1; }
|
|
7
7
|
OUTFILE="$ROOT/specs/receipts.json"
|
|
8
8
|
TIMESTAMP="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
9
|
-
|
|
9
|
+
receipts_emit_json() {
|
|
10
10
|
$PYTHON -c "
|
|
11
11
|
import json, sys, os, subprocess, glob as gb
|
|
12
12
|
sections = {}
|
|
@@ -103,6 +103,6 @@ for key, sec in sections.items(): print(f' {key}: source={sec.get(\"source\",\"
|
|
|
103
103
|
"
|
|
104
104
|
}
|
|
105
105
|
echo "build-receipts: aggregating quality evidence..."
|
|
106
|
-
|
|
106
|
+
receipts_emit_json
|
|
107
107
|
echo "build-receipts: done — $(date -u +%H:%M:%SZ)"
|
|
108
108
|
exit 0
|
|
@@ -12,7 +12,7 @@ GRAPH_JSONL="$REPO_ROOT/bigpowers-mcp/graph.jsonl"
|
|
|
12
12
|
SKILL_GRAPH_JSON="$REPO_ROOT/specs/skill-graph.json"
|
|
13
13
|
SKILLS_WIKI="$REPO_ROOT/specs/skills-wiki/skills"
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
skill_graph_show_help() {
|
|
16
16
|
cat <<EOF
|
|
17
17
|
Usage: $(basename "$0") [OPTION]
|
|
18
18
|
|
|
@@ -10,7 +10,7 @@ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
10
10
|
TRACE_MATRIX="$REPO_ROOT/specs/traceability-matrix.json"
|
|
11
11
|
DRIFT_REPORT="$REPO_ROOT/specs/drift-report.json"
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
drift_show_help() {
|
|
14
14
|
cat <<EOF
|
|
15
15
|
Usage: $(basename "$0") [OPTION]
|
|
16
16
|
|
|
@@ -23,7 +23,7 @@ EOF
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
if [ "$#" -eq 1 ] && [ "$1" = "--help" ]; then
|
|
26
|
-
|
|
26
|
+
drift_show_help
|
|
27
27
|
exit 0
|
|
28
28
|
fi
|
|
29
29
|
|
|
@@ -5,7 +5,7 @@ source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
|
5
5
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
6
|
BP_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
gap_usage() {
|
|
9
9
|
cat <<'EOF'
|
|
10
10
|
Usage: check-spec-version-gap.sh [--help] [--project <dir>] [--json]
|
|
11
11
|
Exit codes: 0=no gap, 1=gap, 2=no specs, 3=blocked, 4=error
|
|
@@ -18,7 +18,7 @@ JSON_STDOUT=false
|
|
|
18
18
|
SKIP_BLOCK=false
|
|
19
19
|
while [[ $# -gt 0 ]]; do
|
|
20
20
|
case "$1" in
|
|
21
|
-
--help|-h)
|
|
21
|
+
--help|-h) gap_usage ;;
|
|
22
22
|
--project) PROJECT_DIR="${2:?}"; shift 2 ;;
|
|
23
23
|
--json) JSON_STDOUT=true; shift ;;
|
|
24
24
|
--skip-block) SKIP_BLOCK=true; shift ;;
|
|
@@ -29,7 +29,7 @@ done
|
|
|
29
29
|
PROJECT_DIR="$(cd "$PROJECT_DIR" 2>/dev/null && pwd)" || { echo "bad dir: $PROJECT_DIR" >&2; exit 4; }
|
|
30
30
|
SPECS="$PROJECT_DIR/specs"
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
gap_emit_json() {
|
|
33
33
|
if $JSON_STDOUT; then echo "$1"; else echo "$1" >&2; fi
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -37,7 +37,7 @@ get_installed_version() {
|
|
|
37
37
|
$PYTHON -c "import json; d=json.load(open('$BP_ROOT/package.json')); print(d.get('version','unknown'))" 2>/dev/null || echo "unknown"
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
gap_yaml_get() {
|
|
41
41
|
[[ ! -f "$1" ]] && { echo ""; return; }
|
|
42
42
|
$PYTHON -c "
|
|
43
43
|
import sys, yaml
|
|
@@ -116,29 +116,29 @@ check_active_work() {
|
|
|
116
116
|
branch=$(git -C "$PROJECT_DIR" branch --show-current 2>/dev/null || echo "")
|
|
117
117
|
if on_feature_branch "$branch"; then echo "true"; return; fi
|
|
118
118
|
if has_dirty_specs; then echo "true"; return; fi
|
|
119
|
-
af=$(
|
|
119
|
+
af=$(gap_yaml_get "$SPECS/state.yaml" "active_flow")
|
|
120
120
|
if is_blocking_active_flow "$af"; then echo "true"; return; fi
|
|
121
121
|
echo "false"
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
# Main
|
|
125
|
-
[[ ! -d "$SPECS" ]] && {
|
|
125
|
+
[[ ! -d "$SPECS" ]] && { gap_emit_json '{"gap":false,"reason":"NO_SPECS"}'; exit 2; }
|
|
126
126
|
IV=$(get_installed_version)
|
|
127
127
|
if [ "$SKIP_BLOCK" != "true" ] && [[ "$(check_active_work)" == "true" ]]; then
|
|
128
|
-
|
|
128
|
+
gap_emit_json '{"gap":true,"blocked":true,"reason":"ACTIVE_WORK","installed_version":"'"$IV"'"}'
|
|
129
129
|
exit 3
|
|
130
130
|
fi
|
|
131
131
|
|
|
132
|
-
SV=$(
|
|
132
|
+
SV=$(gap_yaml_get "$SPECS/state.yaml" "bigpowers_version")
|
|
133
133
|
|
|
134
134
|
if yaml_value_set "$SV"; then
|
|
135
135
|
if [[ "$SV" == "$IV" ]]; then
|
|
136
|
-
|
|
136
|
+
gap_emit_json '{"gap":false,"stamp":true,"detected_version":"'"$SV"'","installed_version":"'"$IV"'","detection_method":"stamp","confidence":"high"}'
|
|
137
137
|
exit 0
|
|
138
138
|
fi
|
|
139
139
|
APP=$(find_migrations "$SV")
|
|
140
140
|
CNT=$(echo "$APP" | $PYTHON -c "import json,sys; print(len(json.load(sys.stdin)))" 2>/dev/null || echo 0)
|
|
141
|
-
|
|
141
|
+
gap_emit_json "$($PYTHON -c "import json; a=json.loads('''$APP'''); print(json.dumps({'gap':True,'stamp':True,'detected_version':'$SV','detection_method':'stamp','installed_version':'$IV','applicable_migrations':len(a),'migration_ids':[m['id'] for m in a],'confidence':'high','active_work_blocked':False}))")"
|
|
142
142
|
exit 1
|
|
143
143
|
fi
|
|
144
144
|
|
|
@@ -150,7 +150,7 @@ for f in release-plan.yaml execution-status.yaml product/SCOPE_LATEST.yaml metri
|
|
|
150
150
|
[[ -f "$SPECS/$f" ]] && { MARKERS+=("$f"); [[ -z "$DV" ]] && DV="2.0.0"; }
|
|
151
151
|
done
|
|
152
152
|
if [[ -f "$SPECS/state.yaml" ]]; then
|
|
153
|
-
ec=$(
|
|
153
|
+
ec=$(gap_yaml_get "$SPECS/state.yaml" "epic_cycle.current_step")
|
|
154
154
|
if yaml_value_set "$ec"; then
|
|
155
155
|
MARKERS+=("epic_cycle"); [[ "$DV" == "2.0.0" ]] && DV="2.20.0"
|
|
156
156
|
fi
|
|
@@ -161,12 +161,12 @@ fi
|
|
|
161
161
|
[[ -z "$DV" ]] && { DV="unknown"; CONF="low"; }
|
|
162
162
|
|
|
163
163
|
if [[ "$DV" != "unknown" ]] && ! version_lt "$DV" "$IV"; then
|
|
164
|
-
[[ "$DV" == "$IV" ]] && {
|
|
164
|
+
[[ "$DV" == "$IV" ]] && { gap_emit_json '{"gap":false,"stamp":false,"detected_version":"'"$DV"'","installed_version":"'"$IV"'","detection_method":"fingerprint","confidence":"'"$CONF"'"}'; exit 0; }
|
|
165
165
|
fi
|
|
166
166
|
|
|
167
167
|
APP=$(find_migrations "$DV")
|
|
168
168
|
CNT=$(echo "$APP" | $PYTHON -c "import json,sys; print(len(json.load(sys.stdin)))" 2>/dev/null || echo 0)
|
|
169
169
|
MJ=$(printf '%s\n' "${MARKERS[@]}" | $PYTHON -c "import json,sys; print(json.dumps([l.strip() for l in sys.stdin if l.strip()]))" 2>/dev/null || echo "[]")
|
|
170
170
|
|
|
171
|
-
|
|
171
|
+
gap_emit_json "$($PYTHON -c "import json; a=json.loads('''$APP'''); m=json.loads('''$MJ'''); print(json.dumps({'gap':True,'stamp':False,'detected_version':'$DV','detection_method':'fingerprint','installed_version':'$IV','applicable_migrations':len(a),'migration_ids':[x['id'] for x in a],'confidence':'$CONF','active_work_blocked':False,'matched_markers':m}))")"
|
|
172
172
|
exit 1
|
|
@@ -10,7 +10,7 @@ CONVENTIONS="$REPO_ROOT/CONVENTIONS.md"
|
|
|
10
10
|
OUTPUT="$REPO_ROOT/specs/rule-matrix.json"
|
|
11
11
|
MATRIX_VERSION="1.0.0"
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
matrix_show_help() {
|
|
14
14
|
cat <<EOF
|
|
15
15
|
Usage: $(basename "$0") [OPTION]
|
|
16
16
|
|
|
@@ -26,7 +26,7 @@ EOF
|
|
|
26
26
|
STDOUT_ONLY=0
|
|
27
27
|
if [ "$#" -eq 1 ]; then
|
|
28
28
|
case "$1" in
|
|
29
|
-
--help)
|
|
29
|
+
--help) matrix_show_help; exit 0 ;;
|
|
30
30
|
--stdout) STDOUT_ONLY=1 ;;
|
|
31
31
|
*) echo "[ERROR] Unknown option: $1" >&2; exit 1 ;;
|
|
32
32
|
esac
|
|
@@ -11,7 +11,7 @@ CONVENTIONS_WIKI="$REPO_ROOT/specs/conventions-wiki"
|
|
|
11
11
|
CONVENTIONS_INDEX="$CONVENTIONS_WIKI/index.md"
|
|
12
12
|
SKILL_GRAPH="$REPO_ROOT/specs/skill-graph.json"
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
decompose_show_help() {
|
|
15
15
|
cat <<EOF
|
|
16
16
|
Usage: $(basename "$0") [OPTION]
|
|
17
17
|
|
|
@@ -24,7 +24,7 @@ EOF
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
if [ "$#" -eq 1 ] && [ "$1" = "--help" ]; then
|
|
27
|
-
|
|
27
|
+
decompose_show_help
|
|
28
28
|
exit 0
|
|
29
29
|
fi
|
|
30
30
|
|
|
@@ -17,7 +17,7 @@ fi
|
|
|
17
17
|
|
|
18
18
|
count=0
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
adr_wiki_assign_tier() {
|
|
21
21
|
local adr_id="$1"
|
|
22
22
|
case "$adr_id" in
|
|
23
23
|
ADR-0001|ADR-0002|ADR-0003|0001-*|0002-*|0003-*) echo "core" ;;
|
|
@@ -55,7 +55,7 @@ if len(parts) >= 3:
|
|
|
55
55
|
fi
|
|
56
56
|
[ -z "$title" ] && title="$adr_name"
|
|
57
57
|
|
|
58
|
-
tier=$(
|
|
58
|
+
tier=$(adr_wiki_assign_tier "$adr_name")
|
|
59
59
|
|
|
60
60
|
bundle="$WIKI/${adr_name}.okf.md"
|
|
61
61
|
|
|
@@ -9,7 +9,7 @@ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
9
9
|
CLAUDE_MD="$REPO_ROOT/CLAUDE.md"
|
|
10
10
|
AGENT_GUIDE="$REPO_ROOT/specs/agent-guide"
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
agent_guide_show_help() {
|
|
13
13
|
cat <<EOF
|
|
14
14
|
Usage: $(basename "$0") [OPTION]
|
|
15
15
|
|
|
@@ -22,7 +22,7 @@ EOF
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
if [ "$#" -eq 1 ] && [ "$1" = "--help" ]; then
|
|
25
|
-
|
|
25
|
+
agent_guide_show_help
|
|
26
26
|
exit 0
|
|
27
27
|
fi
|
|
28
28
|
|
|
@@ -16,7 +16,7 @@ extract() {
|
|
|
16
16
|
|
|
17
17
|
count=0
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
epics_wiki_assign_tier() {
|
|
20
20
|
local epic_id="$1"
|
|
21
21
|
case "$epic_id" in
|
|
22
22
|
e28|e38|e40|e41|e44|e45|e46) echo "core" ;;
|
|
@@ -43,7 +43,7 @@ for epic_yaml in "$ROOT"/specs/epics/*/epic.yaml "$ROOT"/specs/epics/archive/*/e
|
|
|
43
43
|
|
|
44
44
|
[[ -z "$epic_id" ]] && continue
|
|
45
45
|
|
|
46
|
-
tier=$(
|
|
46
|
+
tier=$(epics_wiki_assign_tier "$epic_id")
|
|
47
47
|
|
|
48
48
|
# Build references array from story list
|
|
49
49
|
story_count=$(grep -c '^\s*- id:' "$epic_yaml" 2>/dev/null || true)
|
|
@@ -7,7 +7,7 @@ READ_MAX_BYTES=102400 # 100KB
|
|
|
7
7
|
GREP_MAX_MATCHES=200
|
|
8
8
|
BASH_WARN_PATTERNS='^(npm test|pnpm test|yarn test|cargo test|go test|jest|vitest|pytest|rake test|rspec|git log|git diff|docker logs|kubectl logs|find |rg |grep )'
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
token_mgmt_deny() {
|
|
11
11
|
echo "TOKEN-MGMT BLOCK: $1" >&2
|
|
12
12
|
echo "Use sqz_read_file / sqz_grep / rtk / bts compress per CLAUDE.md § Token Management." >&2
|
|
13
13
|
exit 2
|
|
@@ -23,16 +23,16 @@ case "$TOOL" in
|
|
|
23
23
|
[[ -n "$PATH_ARG" && -f "$PATH_ARG" ]] || exit 0
|
|
24
24
|
SIZE="$(wc -c < "$PATH_ARG" | tr -d ' ')"
|
|
25
25
|
if (( SIZE > READ_MAX_BYTES )); then
|
|
26
|
-
|
|
26
|
+
token_mgmt_deny "Read $PATH_ARG is ${SIZE} bytes (max ${READ_MAX_BYTES}). Use sqz_read_file or bts compress."
|
|
27
27
|
fi
|
|
28
28
|
;;
|
|
29
29
|
Grep|grep)
|
|
30
30
|
LIMIT="$(echo "$TOOL_INPUT" | jq -r '.head_limit // .max_matches // empty')"
|
|
31
31
|
if [[ -z "$LIMIT" || "$LIMIT" == "null" ]]; then
|
|
32
|
-
|
|
32
|
+
token_mgmt_deny "Grep without head_limit (max ${GREP_MAX_MATCHES}). Set head_limit<=${GREP_MAX_MATCHES} or use sqz_grep."
|
|
33
33
|
fi
|
|
34
34
|
if [[ "$LIMIT" =~ ^[0-9]+$ ]] && (( LIMIT > GREP_MAX_MATCHES )); then
|
|
35
|
-
|
|
35
|
+
token_mgmt_deny "Grep head_limit=${LIMIT} exceeds ${GREP_MAX_MATCHES}. Lower limit or use sqz_grep."
|
|
36
36
|
fi
|
|
37
37
|
;;
|
|
38
38
|
Bash|bash|Shell|shell)
|
|
@@ -42,7 +42,7 @@ case "$TOOL" in
|
|
|
42
42
|
exit 0
|
|
43
43
|
fi
|
|
44
44
|
if echo "$CMD" | grep -qE "$BASH_WARN_PATTERNS"; then
|
|
45
|
-
|
|
45
|
+
token_mgmt_deny "Bash may exceed 500 lines of output. Prefix with rtk or pipe through sqz compress."
|
|
46
46
|
fi
|
|
47
47
|
;;
|
|
48
48
|
*)
|
package/scripts/land-branch.sh
CHANGED
|
@@ -13,7 +13,7 @@ usage_land() {
|
|
|
13
13
|
exit 1
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
land_branch_deny() {
|
|
17
17
|
echo "ERROR: $1" >&2
|
|
18
18
|
exit 1
|
|
19
19
|
}
|
|
@@ -34,16 +34,16 @@ COMMIT_MSG="${ARGS[1]:-}"
|
|
|
34
34
|
[ -n "$FEATURE_BRANCH" ] && [ -n "$COMMIT_MSG" ] || usage_land
|
|
35
35
|
|
|
36
36
|
if [[ ! "$COMMIT_MSG" =~ $CONVENTIONAL_REGEX ]]; then
|
|
37
|
-
|
|
37
|
+
land_branch_deny "Commit message must follow Conventional Commits: <type>(<scope>): <subject>"
|
|
38
38
|
fi
|
|
39
39
|
|
|
40
40
|
if [ ${#COMMIT_MSG} -gt 72 ]; then
|
|
41
|
-
|
|
41
|
+
land_branch_deny "Commit subject line must be 72 characters or less"
|
|
42
42
|
fi
|
|
43
43
|
|
|
44
44
|
# Primary worktree only (.git is a directory, not a gitdir pointer file)
|
|
45
45
|
if [ -f .git ]; then
|
|
46
|
-
|
|
46
|
+
land_branch_deny "Run from the primary repository root, not a linked worktree (cd to main repo first)"
|
|
47
47
|
fi
|
|
48
48
|
|
|
49
49
|
detect_default_branch() {
|
|
@@ -58,7 +58,7 @@ detect_default_branch() {
|
|
|
58
58
|
elif git show-ref --verify --quiet refs/heads/master; then
|
|
59
59
|
echo "master"
|
|
60
60
|
else
|
|
61
|
-
|
|
61
|
+
land_branch_deny "Could not detect default branch (main/master)"
|
|
62
62
|
fi
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -69,12 +69,12 @@ echo "==> Land branch: $FEATURE_BRANCH -> $DEFAULT_BRANCH"
|
|
|
69
69
|
echo " Repo root: $REPO_ROOT"
|
|
70
70
|
|
|
71
71
|
if ! git show-ref --verify --quiet "refs/heads/$FEATURE_BRANCH"; then
|
|
72
|
-
|
|
72
|
+
land_branch_deny "Feature branch '$FEATURE_BRANCH' does not exist"
|
|
73
73
|
fi
|
|
74
74
|
|
|
75
75
|
for protected in main master; do
|
|
76
76
|
if [ "$FEATURE_BRANCH" = "$protected" ]; then
|
|
77
|
-
|
|
77
|
+
land_branch_deny "Cannot land protected branch '$FEATURE_BRANCH'"
|
|
78
78
|
fi
|
|
79
79
|
done
|
|
80
80
|
|
|
@@ -115,15 +115,15 @@ fi
|
|
|
115
115
|
echo "==> Updating $DEFAULT_BRANCH"
|
|
116
116
|
git checkout "$DEFAULT_BRANCH"
|
|
117
117
|
if ! git diff-index --quiet HEAD -- 2>/dev/null; then
|
|
118
|
-
|
|
118
|
+
land_branch_deny "Working tree on $DEFAULT_BRANCH is not clean. Stash or commit first."
|
|
119
119
|
fi
|
|
120
120
|
|
|
121
121
|
if git remote get-url origin >/dev/null 2>&1; then
|
|
122
|
-
git pull --ff-only origin "$DEFAULT_BRANCH" ||
|
|
122
|
+
git pull --ff-only origin "$DEFAULT_BRANCH" || land_branch_deny "git pull --ff-only failed; resolve before landing"
|
|
123
123
|
fi
|
|
124
124
|
|
|
125
125
|
if ! git merge-base --is-ancestor "$DEFAULT_BRANCH" "$FEATURE_BRANCH" 2>/dev/null; then
|
|
126
|
-
|
|
126
|
+
land_branch_deny "Feature branch '$FEATURE_BRANCH' is not based on current $DEFAULT_BRANCH (rebase or recreate branch)"
|
|
127
127
|
fi
|
|
128
128
|
|
|
129
129
|
export GIT_BIGPOWERS_LAND=1
|
|
@@ -131,7 +131,7 @@ export GIT_BIGPOWERS_LAND=1
|
|
|
131
131
|
echo "==> Squash merge $FEATURE_BRANCH"
|
|
132
132
|
git merge --squash "$FEATURE_BRANCH"
|
|
133
133
|
if git diff-index --quiet HEAD -- 2>/dev/null; then
|
|
134
|
-
|
|
134
|
+
land_branch_deny "Squash merge produced no changes (already merged?)"
|
|
135
135
|
fi
|
|
136
136
|
|
|
137
137
|
git commit -m "$COMMIT_MSG"
|
|
@@ -31,6 +31,7 @@ emit_okf_metrics() {
|
|
|
31
31
|
printf -- '---\n'
|
|
32
32
|
printf -- 'okf_kind: story-metrics\n'
|
|
33
33
|
printf -- 'okf_version: "0.1"\n'
|
|
34
|
+
printf -- 'type: StoryMetrics\n'
|
|
34
35
|
printf -- 'id: %s\n' "$story"
|
|
35
36
|
printf -- 'epic: %s\n' "${story%%s*}"
|
|
36
37
|
printf -- 'bcps: %s\n' "$bcps"
|
|
@@ -32,7 +32,11 @@ golden_suite_main() {
|
|
|
32
32
|
esac
|
|
33
33
|
done
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
golden_agent_cli_available() {
|
|
36
|
+
$AGENT_MODE && ! command -v gh-aw &>/dev/null && ! command -v gh &>/dev/null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if golden_agent_cli_available; then
|
|
36
40
|
echo -e "${GOLDEN_YELLOW}WARNING: gh-aw not installed — agent gate skipped${GOLDEN_NC}" >&2
|
|
37
41
|
AGENT_MODE=false
|
|
38
42
|
fi
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
# story: e44s03
|
|
3
3
|
if [ -n "${MIGRATE_VERSION_TRANSFORMS_LOADED:-}" ]; then return 0; fi
|
|
4
4
|
MIGRATE_VERSION_TRANSFORMS_LOADED=1
|
|
5
|
-
yaml_get() { migrate_yaml_get "$@"; }
|
|
6
5
|
apply_transform() {
|
|
7
6
|
local mig_id="$1" action="$2" tjson="$3"
|
|
8
7
|
|
|
@@ -107,7 +106,7 @@ with open(tgt, 'w') as f:
|
|
|
107
106
|
val="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('value',''))")"
|
|
108
107
|
if_missing="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('if_missing','false'))" 2>/dev/null)" || if_missing="false"
|
|
109
108
|
if [ "$if_missing" = "true" ] || [ "$if_missing" = "True" ]; then
|
|
110
|
-
if
|
|
109
|
+
if migrate_yaml_get "$REPO_ROOT/$file" "$key" >/dev/null 2>&1; then
|
|
111
110
|
log_skip "$action: $file $key already set (if_missing=true) — idempotent"
|
|
112
111
|
SKIPPED+=("$action:$key")
|
|
113
112
|
return
|
|
@@ -122,7 +121,7 @@ with open(tgt, 'w') as f:
|
|
|
122
121
|
file="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('file',''))")"
|
|
123
122
|
old_key="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('old_key',''))")"
|
|
124
123
|
new_key="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('new_key',''))")"
|
|
125
|
-
old_val="$(
|
|
124
|
+
old_val="$(migrate_yaml_get "$REPO_ROOT/$file" "$old_key" 2>/dev/null)" || true
|
|
126
125
|
if [ -z "${old_val:-}" ]; then
|
|
127
126
|
log_skip "$action: key '$old_key' not found in $file"
|
|
128
127
|
SKIPPED+=("$action:$old_key"); return
|
|
@@ -148,7 +147,7 @@ with open('$REPO_ROOT/$file', 'w') as f:
|
|
|
148
147
|
local file key
|
|
149
148
|
file="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('file',''))")"
|
|
150
149
|
key="$(echo "$tjson" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('path') or d.get('key',''))")"
|
|
151
|
-
if !
|
|
150
|
+
if ! migrate_yaml_get "$REPO_ROOT/$file" "$key" >/dev/null 2>&1; then
|
|
152
151
|
log_skip "$action: key '$key' not found in $file — idempotent"
|
|
153
152
|
SKIPPED+=("$action:$key"); return
|
|
154
153
|
fi
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# record-cycle-time-lib.sh — shared helpers for record-cycle-time.sh
|
|
3
|
+
|
|
4
|
+
record_cycle_die() { echo "record-cycle-time: $*" >&2; exit 1; }
|
|
5
|
+
|
|
6
|
+
record_cycle_usage() {
|
|
7
|
+
cat <<'EOF'
|
|
8
|
+
Usage: scripts/record-cycle-time.sh <command> [flags]
|
|
9
|
+
|
|
10
|
+
Commands:
|
|
11
|
+
report Compute per-story effort partition + additivity self-check.
|
|
12
|
+
Flags: --range <git-range> --story-key <trailer-key>
|
|
13
|
+
append Recompute partition and append one row for a story.\n Flags: --story <id> --bcps <n> [--bcp-plus <n>] [--range <git-range>]\n [--merged-at <epoch>] [--file <path>] [--story-key <trailer-key>]
|
|
14
|
+
self-test Run report, then exit 0 on additivity PASS; exit 1 on FAIL.
|
|
15
|
+
Flags: --range <git-range> --story-key <trailer-key>
|
|
16
|
+
help Show this message.
|
|
17
|
+
|
|
18
|
+
Global flags:
|
|
19
|
+
--help Show this message (works with any subcommand).
|
|
20
|
+
|
|
21
|
+
Description:
|
|
22
|
+
Computes git-derived, additive effort from commit history.
|
|
23
|
+
Two metrics, separated: effort_hours (ADDITIVE, Σ = total) and
|
|
24
|
+
lead_time_minutes (calendar latency, median-aggregate, NEVER sum).
|
|
25
|
+
|
|
26
|
+
Uses the git-hours model: commits sorted by author-date across the
|
|
27
|
+
whole range; gaps < 120 min counted as effort; gaps >= 120 min
|
|
28
|
+
start a new session with a 120-min pad.
|
|
29
|
+
EOF
|
|
30
|
+
exit 0
|
|
31
|
+
}
|
package/scripts/lib/sync-post.sh
CHANGED
|
@@ -8,6 +8,11 @@ if [ -n "${SYNC_POST_LOADED:-}" ]; then
|
|
|
8
8
|
fi
|
|
9
9
|
SYNC_POST_LOADED=1
|
|
10
10
|
|
|
11
|
+
sync_post_gemini_version_mismatch() {
|
|
12
|
+
local ext_ver="$1" pkg_ver="$2"
|
|
13
|
+
[[ -n "$ext_ver" && -n "$pkg_ver" && "$ext_ver" != "$pkg_ver" ]]
|
|
14
|
+
}
|
|
15
|
+
|
|
11
16
|
sync_post_run() {
|
|
12
17
|
local skill_count="${1:-0}"
|
|
13
18
|
local opencode_count="${2:-0}"
|
|
@@ -116,7 +121,7 @@ sync_post_regression_guards() {
|
|
|
116
121
|
if [[ -f "$manifest" ]]; then
|
|
117
122
|
ext_ver=$(jq -r '.version // empty' "$manifest")
|
|
118
123
|
pkg_ver=$(jq -r '.version // empty' "$REPO_ROOT/package.json")
|
|
119
|
-
if
|
|
124
|
+
if sync_post_gemini_version_mismatch "$ext_ver" "$pkg_ver"; then
|
|
120
125
|
echo "sync-skills: FAIL — gemini-extension.json version ($ext_ver) != package.json ($pkg_ver)" >&2
|
|
121
126
|
exit 1
|
|
122
127
|
fi
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test-assertions.sh — shared pass/fail/cleanup helpers for verification scripts
|
|
3
|
+
|
|
4
|
+
if [[ -n "${TEST_ASSERTIONS_LOADED:-}" ]]; then return 0; fi
|
|
5
|
+
TEST_ASSERTIONS_LOADED=1
|
|
6
|
+
|
|
7
|
+
ta_cleanup() { [[ -n "${TA_TMPDIR:-}" && -d "$TA_TMPDIR" ]] && rm -rf "$TA_TMPDIR"; }
|
|
8
|
+
|
|
9
|
+
ta_pass() { echo " PASS $1"; TA_PASS=$((TA_PASS + 1)); }
|
|
10
|
+
|
|
11
|
+
ta_fail() { echo " FAIL $1"; TA_FAIL=$((TA_FAIL + 1)); }
|
|
@@ -59,9 +59,12 @@ validate_story_metrics() {
|
|
|
59
59
|
fi
|
|
60
60
|
|
|
61
61
|
commit_range="$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('commit_range',''))" 2>/dev/null)"
|
|
62
|
-
if
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
if [ "$source" != "estimated" ]; then
|
|
63
|
+
if ! git -C "$ROOT" log --oneline -1 "$commit_range" >/dev/null 2>&1; then
|
|
64
|
+
printf "${RED}FAIL${NC} %s: commit_range '%s' does not resolve\n" "$name" "$commit_range"
|
|
65
|
+
EXIT_CODE=1
|
|
66
|
+
return
|
|
67
|
+
fi
|
|
65
68
|
fi
|
|
66
69
|
|
|
67
70
|
eff="$(echo "$fm" | $PYTHON -c "
|
|
@@ -49,35 +49,10 @@ PAD_SECONDS=$(( 120 * 60 ))
|
|
|
49
49
|
STORY_KEY="Story"
|
|
50
50
|
# git-hours engine — partition + whole_range_hours (additivity oracle)
|
|
51
51
|
true && source "$(dirname "${BASH_SOURCE[0]}")/lib/git-hours.sh"
|
|
52
|
+
true && source "$(dirname "${BASH_SOURCE[0]}")/lib/record-cycle-time-lib.sh"
|
|
52
53
|
|
|
53
|
-
die() {
|
|
54
|
-
|
|
55
|
-
usage_cycle() {
|
|
56
|
-
cat <<'EOF'
|
|
57
|
-
Usage: scripts/record-cycle-time.sh <command> [flags]
|
|
58
|
-
|
|
59
|
-
Commands:
|
|
60
|
-
report Compute per-story effort partition + additivity self-check.
|
|
61
|
-
Flags: --range <git-range> --story-key <trailer-key>
|
|
62
|
-
append Recompute partition and append one row for a story.\n Flags: --story <id> --bcps <n> [--bcp-plus <n>] [--range <git-range>]\n [--merged-at <epoch>] [--file <path>] [--story-key <trailer-key>]
|
|
63
|
-
self-test Run report, then exit 0 on additivity PASS; exit 1 on FAIL.
|
|
64
|
-
Flags: --range <git-range> --story-key <trailer-key>
|
|
65
|
-
help Show this message.
|
|
66
|
-
|
|
67
|
-
Global flags:
|
|
68
|
-
--help Show this message (works with any subcommand).
|
|
69
|
-
|
|
70
|
-
Description:
|
|
71
|
-
Computes git-derived, additive effort from commit history.
|
|
72
|
-
Two metrics, separated: effort_hours (ADDITIVE, Σ = total) and
|
|
73
|
-
lead_time_minutes (calendar latency, median-aggregate, NEVER sum).
|
|
74
|
-
|
|
75
|
-
Uses the git-hours model: commits sorted by author-date across the
|
|
76
|
-
whole range; gaps < 120 min counted as effort; gaps >= 120 min
|
|
77
|
-
start a new session with a 120-min pad.
|
|
78
|
-
EOF
|
|
79
|
-
exit 0
|
|
80
|
-
}
|
|
54
|
+
die() { record_cycle_die "$@"; }
|
|
55
|
+
usage_cycle() { record_cycle_usage; }
|
|
81
56
|
|
|
82
57
|
command -v git >/dev/null 2>&1 || die "git not found"
|
|
83
58
|
ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || die "not inside a git repo"
|
|
@@ -212,6 +187,7 @@ cmd_append() {
|
|
|
212
187
|
printf -- '---\n'
|
|
213
188
|
printf -- 'okf_kind: story-metrics\n'
|
|
214
189
|
printf -- 'okf_version: "0.1"\n'
|
|
190
|
+
printf -- 'type: StoryMetrics\n'
|
|
215
191
|
printf -- 'id: %s\n' "$story"
|
|
216
192
|
printf -- 'epic: %s\n' "${story%%s*}" # e40s03 → e40
|
|
217
193
|
printf -- 'bcps: %s\n' "$bcps"
|
package/scripts/sync-skills.sh
CHANGED
|
@@ -43,9 +43,9 @@ for arg in "$@"; do
|
|
|
43
43
|
done
|
|
44
44
|
|
|
45
45
|
_LIB_DIR="$(dirname "${BASH_SOURCE[0]}")/lib"
|
|
46
|
-
source "$
|
|
47
|
-
source "$
|
|
48
|
-
source "$
|
|
46
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/skill-common.sh"
|
|
47
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/sync-render.sh"
|
|
48
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/sync-post.sh"
|
|
49
49
|
resolve_repo_root
|
|
50
50
|
|
|
51
51
|
CURSOR_RULES="$REPO_ROOT/.cursor/rules"
|
package/scripts/test-adapters.sh
CHANGED
|
@@ -22,12 +22,12 @@ bash "$SCRIPT_DIR/validate-targets-yaml.sh" >/dev/null
|
|
|
22
22
|
PASS=0
|
|
23
23
|
FAIL=0
|
|
24
24
|
TMPDIR=""
|
|
25
|
+
TA_PASS=0
|
|
26
|
+
TA_FAIL=0
|
|
27
|
+
TA_TMPDIR=""
|
|
25
28
|
|
|
26
|
-
|
|
27
|
-
trap
|
|
28
|
-
|
|
29
|
-
pass() { echo " PASS $1"; PASS=$((PASS + 1)); }
|
|
30
|
-
fail() { echo " FAIL $1"; FAIL=$((FAIL + 1)); }
|
|
29
|
+
source "$SCRIPT_DIR/lib/test-assertions.sh"
|
|
30
|
+
trap ta_cleanup EXIT
|
|
31
31
|
|
|
32
32
|
test_adapter() {
|
|
33
33
|
local id="$1"
|
|
@@ -38,11 +38,11 @@ test_adapter() {
|
|
|
38
38
|
return 0
|
|
39
39
|
fi
|
|
40
40
|
|
|
41
|
-
[[ -f "$adapter_file" ]] || {
|
|
42
|
-
|
|
41
|
+
[[ -f "$adapter_file" ]] || { ta_fail "$id: adapter missing"; return; }
|
|
42
|
+
ta_pass "$id: exists"
|
|
43
43
|
|
|
44
44
|
# shellcheck source=/dev/null
|
|
45
|
-
source "$adapter_file" 2>/dev/null &&
|
|
45
|
+
source "$adapter_file" 2>/dev/null && ta_pass "$id: sourceable" || { ta_fail "$id: source error"; return; }
|
|
46
46
|
|
|
47
47
|
local has_skill has_context mode
|
|
48
48
|
has_skill=$(yq -r ".targets[] | select(.id==\"$id\") | .skill != null" "$TARGETS_FILE")
|
|
@@ -50,11 +50,11 @@ test_adapter() {
|
|
|
50
50
|
mode=$(yq -r ".targets[] | select(.id==\"$id\") | .context.mode // \"\"" "$TARGETS_FILE")
|
|
51
51
|
|
|
52
52
|
if [[ "$has_skill" == "true" ]]; then
|
|
53
|
-
declare -f render_skill >/dev/null 2>&1 &&
|
|
53
|
+
declare -f render_skill >/dev/null 2>&1 && ta_pass "$id: render_skill defined" || ta_fail "$id: render_skill missing"
|
|
54
54
|
fi
|
|
55
55
|
|
|
56
56
|
if [[ "$has_context" == "true" && "$mode" != "native" ]]; then
|
|
57
|
-
declare -f wire_context >/dev/null 2>&1 &&
|
|
57
|
+
declare -f wire_context >/dev/null 2>&1 && ta_pass "$id: wire_context defined" || ta_fail "$id: wire_context missing"
|
|
58
58
|
|
|
59
59
|
TMPDIR=$(mktemp -d)
|
|
60
60
|
cp "$SCRIPT_DIR/../docs/templates/AGENTS.md" "$TMPDIR/AGENTS.md" 2>/dev/null || echo "# test" > "$TMPDIR/AGENTS.md"
|
|
@@ -63,7 +63,7 @@ test_adapter() {
|
|
|
63
63
|
# shellcheck source=/dev/null
|
|
64
64
|
source "$adapter_file"
|
|
65
65
|
wire_context 2>/dev/null || true
|
|
66
|
-
wire_context 2>/dev/null &&
|
|
66
|
+
wire_context 2>/dev/null && ta_pass "$id: idempotent wire_context" || ta_fail "$id: wire_context failed"
|
|
67
67
|
)
|
|
68
68
|
fi
|
|
69
69
|
}
|
|
@@ -76,5 +76,5 @@ else
|
|
|
76
76
|
done < <(yq -r '.targets[] | (.skill.adapter // .context.adapter)' "$TARGETS_FILE" | sort -u)
|
|
77
77
|
fi
|
|
78
78
|
|
|
79
|
-
echo "test-adapters: $
|
|
80
|
-
[[ "$
|
|
79
|
+
echo "test-adapters: $TA_PASS passed, $TA_FAIL failed"
|
|
80
|
+
[[ "$TA_FAIL" -eq 0 ]] || exit 1
|
|
@@ -16,17 +16,17 @@ NC='\033[0m'
|
|
|
16
16
|
CAP=300
|
|
17
17
|
FAILURES=0
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
trace_size_pass() { echo -e "${GREEN}PASS${NC} $1"; }
|
|
20
|
+
trace_size_fail() { echo -e "${RED}FAIL${NC} $1"; FAILURES=$((FAILURES + 1)); }
|
|
21
21
|
|
|
22
22
|
check_lines() {
|
|
23
23
|
local file="$1"
|
|
24
24
|
local lines
|
|
25
25
|
lines=$(wc -l < "$REPO_ROOT/$file" | tr -d ' ')
|
|
26
26
|
if [[ "$lines" -le "$CAP" ]]; then
|
|
27
|
-
|
|
27
|
+
trace_size_pass "$file: $lines lines (≤ $CAP)"
|
|
28
28
|
else
|
|
29
|
-
|
|
29
|
+
trace_size_fail "$file: $lines lines (exceeds $CAP)"
|
|
30
30
|
fi
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -19,8 +19,8 @@ NC='\033[0m'
|
|
|
19
19
|
TRACE_ENGINE="$REPO_ROOT/scripts/lib/trace-stories.py"
|
|
20
20
|
FAILURES=0
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
trace_strict_pass() { echo -e "${GREEN}PASS${NC} $1"; }
|
|
23
|
+
trace_strict_fail() { echo -e "${RED}FAIL${NC} $1"; FAILURES=$((FAILURES + 1)); }
|
|
24
24
|
|
|
25
25
|
run_strict() {
|
|
26
26
|
local root="$1"
|
|
@@ -88,9 +88,9 @@ bash "$REPO_ROOT/scripts/trace-stories.sh" --strict --json >/dev/null 2>&1
|
|
|
88
88
|
repo_exit=$?
|
|
89
89
|
set -e
|
|
90
90
|
if [[ $repo_exit -eq 0 ]]; then
|
|
91
|
-
|
|
91
|
+
trace_strict_pass "trace-stories.sh --strict exits 0 on repo"
|
|
92
92
|
else
|
|
93
|
-
|
|
93
|
+
trace_strict_fail "trace-stories.sh --strict exits $repo_exit on repo (expected 0)"
|
|
94
94
|
fi
|
|
95
95
|
|
|
96
96
|
echo ""
|
|
@@ -103,9 +103,9 @@ todo_output=$(run_strict "$FIXTURE_ROOT")
|
|
|
103
103
|
todo_exit=$?
|
|
104
104
|
set -e
|
|
105
105
|
if [[ $todo_exit -eq 0 ]]; then
|
|
106
|
-
|
|
106
|
+
trace_strict_pass "strict passes when P0 stories are todo with 0 links"
|
|
107
107
|
else
|
|
108
|
-
|
|
108
|
+
trace_strict_fail "strict exits $todo_exit on all-todo fixture (expected 0)"
|
|
109
109
|
echo "$todo_output"
|
|
110
110
|
fi
|
|
111
111
|
|
|
@@ -117,9 +117,9 @@ done_output=$(run_strict "$FIXTURE_ROOT")
|
|
|
117
117
|
done_exit=$?
|
|
118
118
|
set -e
|
|
119
119
|
if [[ $done_exit -eq 2 ]] && grep -q "P0 stories with 0% coverage" <<<"$done_output"; then
|
|
120
|
-
|
|
120
|
+
trace_strict_pass "strict fails when a done P0 story has 0 links"
|
|
121
121
|
else
|
|
122
|
-
|
|
122
|
+
trace_strict_fail "strict exits $done_exit on done-dark fixture (expected 2 with P0 message)"
|
|
123
123
|
echo "$done_output"
|
|
124
124
|
fi
|
|
125
125
|
|
|
@@ -9,12 +9,12 @@ cd "$REPO_ROOT"
|
|
|
9
9
|
|
|
10
10
|
ERRORS=0
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
doctrine_fail() {
|
|
13
13
|
echo "FAIL: $*" >&2
|
|
14
14
|
ERRORS=$((ERRORS + 1))
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
doctrine_pass() {
|
|
18
18
|
echo "ok : $*"
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -33,10 +33,10 @@ done < <(grep -oE '`[a-z][a-z-]+`' docs/references/model-profiles.md \
|
|
|
33
33
|
| sort -u)
|
|
34
34
|
|
|
35
35
|
if [[ ${#DEAD[@]} -eq 0 ]]; then
|
|
36
|
-
|
|
36
|
+
doctrine_pass "all skill names in model-profiles.md resolve to real directories"
|
|
37
37
|
else
|
|
38
38
|
for name in "${DEAD[@]}"; do
|
|
39
|
-
|
|
39
|
+
doctrine_fail "dead skill name in model-profiles.md: '$name' has no matching directory"
|
|
40
40
|
done
|
|
41
41
|
fi
|
|
42
42
|
|
|
@@ -49,9 +49,9 @@ LEGACY_HITS=$(grep -rE "PLAN\.md|STATE\.md|PROJECT\.md|CONTEXT\.md|SUMMARY\.md|V
|
|
|
49
49
|
| grep -v "specs/archive" \
|
|
50
50
|
| wc -l | tr -d ' ') || true
|
|
51
51
|
if [[ "$LEGACY_HITS" -eq 0 ]]; then
|
|
52
|
-
|
|
52
|
+
doctrine_pass "no legacy MD artifact names in live docs/ (docs/archive/ exempt)"
|
|
53
53
|
else
|
|
54
|
-
|
|
54
|
+
doctrine_fail "legacy MD artifact names found in docs/ ($LEGACY_HITS occurrences)"
|
|
55
55
|
grep -rE "PLAN\.md|STATE\.md|PROJECT\.md|CONTEXT\.md|SUMMARY\.md|VERIFICATION\.md|RESEARCH\.md|RELEASE-PLAN\.md" \
|
|
56
56
|
docs/ 2>/dev/null \
|
|
57
57
|
| grep -v "docs/archive/" \
|
|
@@ -68,9 +68,9 @@ fi
|
|
|
68
68
|
STALE_COUNT_HITS=$(grep -rE "expect [0-9]+" docs/ 2>/dev/null \
|
|
69
69
|
| grep -v "expect $LIVE_COUNT" | wc -l | tr -d ' ') || true
|
|
70
70
|
if [[ "$STALE_COUNT_HITS" -eq 0 ]]; then
|
|
71
|
-
|
|
71
|
+
doctrine_pass "skill count annotations match live count ($LIVE_COUNT)"
|
|
72
72
|
else
|
|
73
|
-
|
|
73
|
+
doctrine_fail "stale skill count annotation in docs/ (live=$LIVE_COUNT, $STALE_COUNT_HITS mismatches)"
|
|
74
74
|
grep -rE "expect [0-9]+" docs/ 2>/dev/null | grep -v "expect $LIVE_COUNT" >&2
|
|
75
75
|
fi
|
|
76
76
|
|
|
@@ -86,9 +86,9 @@ LEGACY_SPECS=$(grep -rE "specs/(requirements|plans|audit)\b" \
|
|
|
86
86
|
| grep -v "CHANGELOG\.md\|specs/verifications/reports/\|PLAN-evolve-structure\.md" \
|
|
87
87
|
| wc -l | tr -d ' ') || true
|
|
88
88
|
if [[ "$LEGACY_SPECS" -eq 0 ]]; then
|
|
89
|
-
|
|
89
|
+
doctrine_pass "no legacy specs/ subpaths (requirements/, plans/, audit/)"
|
|
90
90
|
else
|
|
91
|
-
|
|
91
|
+
doctrine_fail "legacy specs/ subpaths found ($LEGACY_SPECS occurrences)"
|
|
92
92
|
grep -rE "specs/(requirements|plans|audit)\b" \
|
|
93
93
|
--include="*.md" --include="*.sh" --include="*.yaml" --include="*.yml" --include="*.json" . \
|
|
94
94
|
2>/dev/null \
|
|
@@ -103,9 +103,9 @@ BCP_SLOP=$(grep -ri "build commit point" . 2>/dev/null \
|
|
|
103
103
|
| grep -v "specs/archive\|\.git\|node_modules\|\.gemini\|\.cursor\|validate-doctrine\.sh" \
|
|
104
104
|
| wc -l | tr -d ' ') || true
|
|
105
105
|
if [[ "$BCP_SLOP" -eq 0 ]]; then
|
|
106
|
-
|
|
106
|
+
doctrine_pass "no 'Build Commit Point' slop — BCP = Business Complexity Points"
|
|
107
107
|
else
|
|
108
|
-
|
|
108
|
+
doctrine_fail "'Build Commit Point' found ($BCP_SLOP hits) — canonical term is 'Business Complexity Points'; see docs/references/bcp.md"
|
|
109
109
|
grep -ri "build commit point" . 2>/dev/null \
|
|
110
110
|
| grep -v "specs/archive\|\.git\|node_modules\|\.gemini\|\.cursor\|validate-doctrine\.sh" | head -5 >&2
|
|
111
111
|
fi
|
|
@@ -113,9 +113,9 @@ fi
|
|
|
113
113
|
# ── Epic 4: SKILL.md size cap ─────────────────────────────────────────────────
|
|
114
114
|
echo "--- [Epic 4] SKILL.md size cap ---"
|
|
115
115
|
if bash "$REPO_ROOT/scripts/check-skill-size.sh" >/dev/null 2>&1; then
|
|
116
|
-
|
|
116
|
+
doctrine_pass "all SKILL.md files within tiered size cap (150 critical-path / 120 utility)"
|
|
117
117
|
else
|
|
118
|
-
|
|
118
|
+
doctrine_fail "a SKILL.md exceeds its size cap — run: bash scripts/check-skill-size.sh"
|
|
119
119
|
bash "$REPO_ROOT/scripts/check-skill-size.sh" 2>&1 | grep "^FAIL:" >&2 || true
|
|
120
120
|
fi
|
|
121
121
|
|
|
@@ -130,19 +130,19 @@ for s in "${CRITICAL_HANDOFF[@]}"; do
|
|
|
130
130
|
fi
|
|
131
131
|
done
|
|
132
132
|
if [[ ${#MISSING_HANDOFF[@]} -eq 0 ]]; then
|
|
133
|
-
|
|
133
|
+
doctrine_pass "all critical-path skills document a handoff target"
|
|
134
134
|
else
|
|
135
135
|
for s in "${MISSING_HANDOFF[@]}"; do
|
|
136
|
-
|
|
136
|
+
doctrine_fail "critical-path skill '$s' has no documented handoff/next_skill"
|
|
137
137
|
done
|
|
138
138
|
fi
|
|
139
139
|
|
|
140
140
|
# ── CI: trace-stories wrapper smoke (REPO_ROOT must be set under set -u) ───────
|
|
141
141
|
echo "--- [CI] trace-stories wrapper smoke ---"
|
|
142
142
|
if bash "$REPO_ROOT/scripts/trace-stories.sh" --help >/dev/null 2>&1; then
|
|
143
|
-
|
|
143
|
+
doctrine_pass "trace-stories.sh --help exits 0 (REPO_ROOT initialized)"
|
|
144
144
|
else
|
|
145
|
-
|
|
145
|
+
doctrine_fail "trace-stories.sh --help failed — REPO_ROOT likely unset; run: bash scripts/trace-stories.sh --help"
|
|
146
146
|
fi
|
|
147
147
|
|
|
148
148
|
# ── Summary ────────────────────────────────────────────────────────────────────
|
|
@@ -20,7 +20,7 @@ WARNINGS=0
|
|
|
20
20
|
# Documented naming exceptions (craft-skill REFERENCE.md)
|
|
21
21
|
VERB_NOUN_EXCEPTIONS="grill-me grill-with-docs context7-mcp using-bigpowers deploy"
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
catalog_usage() {
|
|
24
24
|
echo "Usage: bash scripts/validate-skill-catalog.sh [--archive] [--strict] [--skill <name>]"
|
|
25
25
|
exit 2
|
|
26
26
|
}
|
|
@@ -31,15 +31,15 @@ while [[ $# -gt 0 ]]; do
|
|
|
31
31
|
--strict) STRICT=true; shift ;;
|
|
32
32
|
--skill) TARGET_SKILL="${2:-}"; shift 2 ;;
|
|
33
33
|
--skill=*) TARGET_SKILL="${1#*=}"; shift ;;
|
|
34
|
-
-h|--help)
|
|
35
|
-
*) echo "Unknown flag: $1" >&2;
|
|
34
|
+
-h|--help) catalog_usage ;;
|
|
35
|
+
*) echo "Unknown flag: $1" >&2; catalog_usage ;;
|
|
36
36
|
esac
|
|
37
37
|
done
|
|
38
38
|
|
|
39
39
|
report_catalog_violation() { echo "FAIL: $1" >&2; VIOLATIONS=$((VIOLATIONS + 1)); }
|
|
40
40
|
report_catalog_warning() { echo "WARN: $1" >&2; WARNINGS=$((WARNINGS + 1)); }
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
catalog_validate_verb_noun() {
|
|
43
43
|
local name="$1"
|
|
44
44
|
if echo " $VERB_NOUN_EXCEPTIONS " | grep -q " $name "; then
|
|
45
45
|
return 0
|
|
@@ -55,12 +55,12 @@ check_verb_noun() {
|
|
|
55
55
|
fi
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
catalog_validate_skill() {
|
|
59
59
|
local skill_md="$1"
|
|
60
60
|
local name
|
|
61
61
|
name=$(basename "$(dirname "$skill_md")")
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
catalog_validate_verb_noun "$name" || true
|
|
64
64
|
|
|
65
65
|
if ! grep -q 'HARD GATE' "$skill_md" 2>/dev/null; then
|
|
66
66
|
if $STRICT; then report_catalog_violation "$name: missing HARD GATE block"; else report_catalog_warning "$name: missing HARD GATE block"; fi
|
|
@@ -68,7 +68,7 @@ check_skill() {
|
|
|
68
68
|
|
|
69
69
|
local desc
|
|
70
70
|
desc=$(awk 'BEGIN{f=0} /^---$/{f++; next} f==1{print} f==2{exit}' "$skill_md" \
|
|
71
|
-
| grep -E '^description:' | head -1 | sed 's/^description: *//; s
|
|
71
|
+
| grep -E '^description:' | head -1 | sed 's/^description: *//; s/^\x3e //' | tr -d '"' || true)
|
|
72
72
|
if [[ -n "$desc" && ${#desc} -gt 1024 ]]; then
|
|
73
73
|
report_catalog_violation "$name: description ${#desc} chars (max 1024)"
|
|
74
74
|
fi
|
|
@@ -80,7 +80,7 @@ check_skill() {
|
|
|
80
80
|
fi
|
|
81
81
|
|
|
82
82
|
local cmd
|
|
83
|
-
cmd=$(grep
|
|
83
|
+
cmd=$(grep 'verify:' "$skill_md" 2>/dev/null | tail -1 | sed 's/^\x3e //; s/^→ verify: *//' || true)
|
|
84
84
|
if [[ -z "$cmd" ]]; then
|
|
85
85
|
if $STRICT; then report_catalog_violation "$name: missing → verify: command"; else report_catalog_warning "$name: missing → verify: command"; fi
|
|
86
86
|
fi
|
|
@@ -90,11 +90,11 @@ echo "Validating skill catalog under $SKILLS_ROOT ..."
|
|
|
90
90
|
if [[ -n "$TARGET_SKILL" ]]; then
|
|
91
91
|
skill_md="$SKILLS_ROOT/$TARGET_SKILL/SKILL.md"
|
|
92
92
|
[[ -f "$skill_md" ]] || { echo "FAIL: $skill_md not found" >&2; exit 1; }
|
|
93
|
-
|
|
93
|
+
catalog_validate_skill "$skill_md"
|
|
94
94
|
else
|
|
95
95
|
for skill_md in "$SKILLS_ROOT"/*/SKILL.md; do
|
|
96
96
|
[[ -f "$skill_md" ]] || continue
|
|
97
|
-
|
|
97
|
+
catalog_validate_skill "$skill_md"
|
|
98
98
|
done
|
|
99
99
|
fi
|
|
100
100
|
|
|
@@ -52,83 +52,85 @@ fi
|
|
|
52
52
|
PASS=0
|
|
53
53
|
FAIL=0
|
|
54
54
|
TMPDIR=""
|
|
55
|
+
TA_PASS=0
|
|
56
|
+
TA_FAIL=0
|
|
57
|
+
TA_TMPDIR=""
|
|
55
58
|
|
|
56
|
-
|
|
57
|
-
trap
|
|
59
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/test-assertions.sh"
|
|
60
|
+
trap ta_cleanup EXIT
|
|
58
61
|
|
|
59
|
-
pass() { echo " PASS $1"; PASS=$((PASS + 1)); }
|
|
60
|
-
fail() { echo " FAIL $1"; FAIL=$((FAIL + 1)); }
|
|
61
62
|
|
|
62
63
|
echo "=== install.sh --dry-run ==="
|
|
63
64
|
DRY_OUT="$("$REPO_ROOT/scripts/install.sh" --dry-run 2>&1)"
|
|
64
65
|
|
|
65
|
-
echo "$DRY_OUT" | grep -q 'pi →' &&
|
|
66
|
+
echo "$DRY_OUT" | grep -q 'pi →' && ta_pass "install --dry-run includes pi" || ta_fail "install --dry-run missing pi"
|
|
66
67
|
DRY_UNINSTALL="$("$REPO_ROOT/scripts/install.sh" --dry-run --uninstall 2>&1)"
|
|
67
|
-
echo "$DRY_UNINSTALL" | grep -q 'pi →' &&
|
|
68
|
-
echo "$DRY_OUT" | grep -qi 'opencode' &&
|
|
69
|
-
echo "$DRY_OUT" | grep -q 'Claude Code →' &&
|
|
70
|
-
echo "$DRY_OUT" | grep -q 'Gemini CLI →' &&
|
|
71
|
-
echo "$DRY_OUT" | grep -q 'Cursor →' &&
|
|
68
|
+
echo "$DRY_UNINSTALL" | grep -q 'pi →' && ta_pass "uninstall --dry-run includes pi" || ta_fail "uninstall --dry-run missing pi"
|
|
69
|
+
echo "$DRY_OUT" | grep -qi 'opencode' && ta_fail "install references opencode" || ta_pass "install has no opencode reference"
|
|
70
|
+
echo "$DRY_OUT" | grep -q 'Claude Code →' && ta_pass "install includes Claude Code" || ta_fail "install missing Claude Code"
|
|
71
|
+
echo "$DRY_OUT" | grep -q 'Gemini CLI →' && ta_pass "install includes Gemini CLI" || ta_fail "install missing Gemini CLI"
|
|
72
|
+
echo "$DRY_OUT" | grep -q 'Cursor →' && ta_pass "install includes Cursor" || ta_fail "install missing Cursor"
|
|
72
73
|
|
|
73
74
|
for tool in "Claude Code" "pi"; do
|
|
74
75
|
section=$(echo "$DRY_OUT" | sed -n "/${tool} →/,/^$/p")
|
|
75
76
|
count=$(echo "$section" | grep -o '[0-9]\+ skills installed' | grep -o '[0-9]\+' || echo "0")
|
|
76
|
-
[[ "$count" -gt 0 ]] &&
|
|
77
|
+
[[ "$count" -gt 0 ]] && ta_pass "${tool}: $count skills" || ta_fail "${tool}: 0 skills"
|
|
77
78
|
done
|
|
78
79
|
|
|
79
80
|
echo ""
|
|
80
81
|
echo "=== AGENTS.md spine (e37s04) ==="
|
|
81
|
-
[[ -f "$REPO_ROOT/docs/templates/AGENTS.md" ]] &&
|
|
82
|
-
grep -q 'Preflight' "$REPO_ROOT/docs/templates/AGENTS.md" &&
|
|
83
|
-
grep -qi 'cline\|aider\|opencode' "$REPO_ROOT/docs/templates/AGENTS.md" &&
|
|
84
|
-
grep -q '## Test' "$REPO_ROOT/docs/templates/AGENTS.md" &&
|
|
85
|
-
grep -q '## Lint' "$REPO_ROOT/docs/templates/AGENTS.md" &&
|
|
86
|
-
grep -q '## Build' "$REPO_ROOT/docs/templates/AGENTS.md" &&
|
|
82
|
+
[[ -f "$REPO_ROOT/docs/templates/AGENTS.md" ]] && ta_pass "Reach template exists" || ta_fail "Reach template missing"
|
|
83
|
+
grep -q 'Preflight' "$REPO_ROOT/docs/templates/AGENTS.md" && ta_pass "template: Preflight" || ta_fail "template: missing Preflight"
|
|
84
|
+
grep -qi 'cline\|aider\|opencode' "$REPO_ROOT/docs/templates/AGENTS.md" && ta_pass "template: multi-agent preamble" || ta_fail "template: missing OSS targets"
|
|
85
|
+
grep -q '## Test' "$REPO_ROOT/docs/templates/AGENTS.md" && ta_pass "template: Test section" || ta_fail "template: missing Test"
|
|
86
|
+
grep -q '## Lint' "$REPO_ROOT/docs/templates/AGENTS.md" && ta_pass "template: Lint section" || ta_fail "template: missing Lint"
|
|
87
|
+
grep -q '## Build' "$REPO_ROOT/docs/templates/AGENTS.md" && ta_pass "template: Build section" || ta_fail "template: missing Build"
|
|
87
88
|
|
|
88
89
|
echo ""
|
|
89
90
|
echo "=== Cline native AGENTS.md (e37s02) ==="
|
|
90
|
-
[[ -f "$REPO_ROOT/AGENTS.md" || -f "$REPO_ROOT/docs/templates/AGENTS.md" ]] &&
|
|
91
|
-
grep -qi 'cline' "$REPO_ROOT/skills/using-bigpowers/SKILL.md" &&
|
|
91
|
+
[[ -f "$REPO_ROOT/AGENTS.md" || -f "$REPO_ROOT/docs/templates/AGENTS.md" ]] && ta_pass "Cline: AGENTS.md present (native reader)" || ta_fail "Cline: AGENTS.md missing"
|
|
92
|
+
grep -qi 'cline' "$REPO_ROOT/skills/using-bigpowers/SKILL.md" && ta_pass "using-bigpowers: Cline section" || ta_fail "using-bigpowers: missing Cline"
|
|
92
93
|
|
|
93
94
|
echo ""
|
|
94
95
|
echo "=== Aider bridge (e37s03) ==="
|
|
95
|
-
grep -qi 'aider' "$REPO_ROOT/skills/seed-conventions/SKILL.md" &&
|
|
96
|
-
grep -qi 'Aider-AI' "$REPO_ROOT/skills/using-bigpowers/SKILL.md" &&
|
|
96
|
+
grep -qi 'aider' "$REPO_ROOT/skills/seed-conventions/SKILL.md" && ta_pass "seed-conventions: Aider wiring" || ta_fail "seed-conventions: missing Aider"
|
|
97
|
+
grep -qi 'Aider-AI' "$REPO_ROOT/skills/using-bigpowers/SKILL.md" && ta_pass "using-bigpowers: Aider-AI upstream" || ta_fail "using-bigpowers: missing Aider-AI"
|
|
97
98
|
|
|
98
99
|
echo ""
|
|
99
100
|
echo "=== Optional Codex wave (e37s04) ==="
|
|
100
101
|
if grep -qi 'codex' "$REPO_ROOT/skills/seed-conventions/SKILL.md" 2>/dev/null; then
|
|
101
|
-
grep -qi 'config.toml' "$REPO_ROOT/skills/seed-conventions/REFERENCE.md" &&
|
|
102
|
-
echo "$DRY_OUT" | grep -qi 'codex' &&
|
|
102
|
+
grep -qi 'config.toml' "$REPO_ROOT/skills/seed-conventions/REFERENCE.md" && ta_pass "Codex: REFERENCE config.toml" || ta_fail "Codex: missing config.toml"
|
|
103
|
+
echo "$DRY_OUT" | grep -qi 'codex' && ta_pass "Codex: install --dry-run mentions codex" || ta_fail "Codex: install missing codex"
|
|
103
104
|
else
|
|
104
|
-
|
|
105
|
+
ta_pass "Codex: wave absent — assertions skipped"
|
|
105
106
|
fi
|
|
106
107
|
|
|
107
108
|
echo ""
|
|
108
109
|
echo "=== seed-conventions local wiring ==="
|
|
109
|
-
grep -qi 'local tool wiring' "$REPO_ROOT/skills/seed-conventions/SKILL.md" &&
|
|
110
|
-
grep -qi 'opencode.json' "$REPO_ROOT/skills/seed-conventions/REFERENCE.md" &&
|
|
111
|
-
grep -qi 'AGENTS.md' "$REPO_ROOT/skills/seed-conventions/SKILL.md" &&
|
|
112
|
-
grep -qi 'symlink' "$REPO_ROOT/skills/seed-conventions/SKILL.md" &&
|
|
113
|
-
grep -qi 'cursor/rules' "$REPO_ROOT/skills/seed-conventions/REFERENCE.md" &&
|
|
110
|
+
grep -qi 'local tool wiring' "$REPO_ROOT/skills/seed-conventions/SKILL.md" && ta_pass "SKILL.md: local tool wiring" || ta_fail "SKILL.md: missing local tool wiring"
|
|
111
|
+
grep -qi 'opencode.json' "$REPO_ROOT/skills/seed-conventions/REFERENCE.md" && ta_pass "REFERENCE.md: opencode.json" || ta_fail "REFERENCE.md: missing opencode.json"
|
|
112
|
+
grep -qi 'AGENTS.md' "$REPO_ROOT/skills/seed-conventions/SKILL.md" && ta_pass "SKILL.md: AGENTS.md spine" || ta_fail "SKILL.md: missing AGENTS.md"
|
|
113
|
+
grep -qi 'symlink' "$REPO_ROOT/skills/seed-conventions/SKILL.md" && ta_pass "SKILL.md: CLAUDE.md symlink" || ta_fail "SKILL.md: missing symlink"
|
|
114
|
+
grep -qi 'cursor/rules' "$REPO_ROOT/skills/seed-conventions/REFERENCE.md" && ta_pass "REFERENCE.md: Cursor symlink" || ta_fail "REFERENCE.md: missing Cursor symlink"
|
|
114
115
|
|
|
115
116
|
OPENCODE_JSON=$(sed -n '/```json/,/```/p' "$REPO_ROOT/skills/seed-conventions/REFERENCE.md" | grep -v '```' || true)
|
|
116
117
|
if [[ -n "$OPENCODE_JSON" ]]; then
|
|
117
118
|
TMPDIR=$(mktemp -d)
|
|
119
|
+
TA_TMPDIR="$TMPDIR"
|
|
118
120
|
echo "$OPENCODE_JSON" > "$TMPDIR/test-opencode.json"
|
|
119
|
-
jq empty "$TMPDIR/test-opencode.json" 2>/dev/null &&
|
|
121
|
+
jq empty "$TMPDIR/test-opencode.json" 2>/dev/null && ta_pass "opencode.json valid JSON" || ta_fail "opencode.json NOT valid JSON"
|
|
120
122
|
else
|
|
121
|
-
|
|
123
|
+
ta_fail "opencode.json template not found"
|
|
122
124
|
fi
|
|
123
125
|
|
|
124
126
|
echo ""
|
|
125
127
|
echo "=== install.sh source ==="
|
|
126
|
-
grep -q 'install_pi()' "$REPO_ROOT/scripts/install.sh" &&
|
|
127
|
-
grep -q 'uninstall_pi()' "$REPO_ROOT/scripts/install.sh" &&
|
|
128
|
-
grep -q 'PI_SKILLS_DIR="$PI_CONFIG_DIR/agent/skills"' "$REPO_ROOT/scripts/install.sh" &&
|
|
129
|
-
! grep -q 'install_opencode\|print_opencode_instructions' "$REPO_ROOT/scripts/install.sh" &&
|
|
128
|
+
grep -q 'install_pi()' "$REPO_ROOT/scripts/install.sh" && ta_pass "source: install_pi()" || ta_fail "source: missing install_pi()"
|
|
129
|
+
grep -q 'uninstall_pi()' "$REPO_ROOT/scripts/install.sh" && ta_pass "source: uninstall_pi()" || ta_fail "source: missing uninstall_pi()"
|
|
130
|
+
grep -q 'PI_SKILLS_DIR="$PI_CONFIG_DIR/agent/skills"' "$REPO_ROOT/scripts/install.sh" && ta_pass "source: targets ~/.pi/agent/skills/" || ta_fail "source: wrong pi target"
|
|
131
|
+
! grep -q 'install_opencode\|print_opencode_instructions' "$REPO_ROOT/scripts/install.sh" && ta_pass "source: no opencode functions" || ta_fail "source: opencode functions remain"
|
|
130
132
|
|
|
131
133
|
echo ""
|
|
132
134
|
echo "──────────────────────────────────────────"
|
|
133
|
-
echo "verify-install: $
|
|
134
|
-
[[ "$
|
|
135
|
+
echo "verify-install: $TA_PASS passed, $TA_FAIL failed"
|
|
136
|
+
[[ "$TA_FAIL" -gt 0 ]] && { echo "Overall: fail"; exit 1; } || echo "Overall: pass"
|
|
@@ -40,6 +40,11 @@ bash "$REPO_ROOT/scripts/sync-bugs-registry.sh" >/dev/null
|
|
|
40
40
|
require_min_count "$REPO_ROOT/specs/bugs" 1 "bug"
|
|
41
41
|
bash "$REPO_ROOT/scripts/validate-okf.sh" --dir "$REPO_ROOT/specs/bugs"
|
|
42
42
|
|
|
43
|
+
echo "→ validate all CI OKF directories"
|
|
44
|
+
for dir in specs/metrics specs/migrations specs/verifications/reports; do
|
|
45
|
+
bash "$REPO_ROOT/scripts/validate-okf.sh" --dir "$REPO_ROOT/$dir"
|
|
46
|
+
done
|
|
47
|
+
|
|
43
48
|
echo "→ publish-to-wiki.sh --dry-run"
|
|
44
49
|
OUTPUT="$(bash "$REPO_ROOT/kernel/src/publish-to-wiki.sh" --dry-run)"
|
|
45
50
|
if [[ "$OUTPUT" != *"pages:"* ]]; then
|