@ikieaneh/opencode-kit 0.6.2 → 0.6.4
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/README.md +1 -3
- package/package.json +9 -7
- package/templates/opencode-kit.schema.json +1 -0
- package/templates/superpowers-contract.json +1 -0
- package/.claude-plugin/plugin.json +0 -23
- package/docs/examples/QUICKSTART.md +0 -123
- package/docs/examples/extension-skill-template.md +0 -108
- package/docs/examples/model-configs.md +0 -117
- package/docs/guides/contract-protocol.md +0 -67
- package/docs/guides/scoring-pipeline.md +0 -60
- package/docs/guides/troubleshooting.md +0 -78
- package/docs/images/logo.svg +0 -9
- package/docs/plans/2026-06-11-plugin-architecture.md +0 -55
- package/src/adr.sh +0 -148
- package/src/analytics.sh +0 -80
- package/src/diff.sh +0 -109
- package/src/doctor.sh +0 -160
- package/src/global-config.sh +0 -82
- package/src/new-skill.sh +0 -60
- package/src/platform.sh +0 -34
- package/src/postflight.py +0 -211
- package/src/postflight.sh +0 -82
- package/src/preflight.sh +0 -138
- package/src/status.sh +0 -113
- package/src/telemetry.sh +0 -83
- package/src/update.sh +0 -188
- package/src/verify.sh +0 -67
package/src/platform.sh
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# opencode-kit platform — cross-platform detection and helpers
|
|
3
|
-
# Source this from any script: . "$(dirname "$0")/platform.sh"
|
|
4
|
-
set -euo pipefail
|
|
5
|
-
|
|
6
|
-
# --- OS Detection ---
|
|
7
|
-
OS="unknown"
|
|
8
|
-
case "$(uname -s)" in
|
|
9
|
-
Darwin) OS="macos" ;;
|
|
10
|
-
Linux) OS="linux" ;;
|
|
11
|
-
*) OS="other" ;;
|
|
12
|
-
esac
|
|
13
|
-
|
|
14
|
-
# --- Architecture ---
|
|
15
|
-
ARCH="unknown"
|
|
16
|
-
case "$(uname -m)" in
|
|
17
|
-
arm64|aarch64) ARCH="arm64" ;;
|
|
18
|
-
x86_64|amd64) ARCH="amd64" ;;
|
|
19
|
-
*) ARCH="other" ;;
|
|
20
|
-
esac
|
|
21
|
-
|
|
22
|
-
# --- Python command (python3 on macOS, python on some Linux) ---
|
|
23
|
-
PYTHON_CMD=""
|
|
24
|
-
if command -v python3 &>/dev/null; then
|
|
25
|
-
PYTHON_CMD="python3"
|
|
26
|
-
elif command -v python &>/dev/null; then
|
|
27
|
-
PYTHON_CMD="python"
|
|
28
|
-
fi
|
|
29
|
-
|
|
30
|
-
# --- Bash compatibility ---
|
|
31
|
-
BASH_VERSION=$(bash --version | head -1 | grep -oE '[0-9]+\.[0-9]+' | head -1 || echo "0")
|
|
32
|
-
|
|
33
|
-
# --- Export ---
|
|
34
|
-
export OS ARCH PYTHON_CMD BASH_VERSION
|
package/src/postflight.py
DELETED
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""postflight helper — batch all contract/telemetry/STATE.md operations in one call.
|
|
3
|
-
|
|
4
|
-
Replaces 11 separate inline Python invocations from postflight.sh with a single
|
|
5
|
-
script that handles: contract read/migration, phase telemetry, summary update,
|
|
6
|
-
STATE.md generation, and contract file persistence.
|
|
7
|
-
|
|
8
|
-
Usage:
|
|
9
|
-
postflight.py CONTRACT_FILE TELEMETRY_DIR STATE_FILE STATE_BACKUP_DIR TEMPLATE_FILE [NEW_STATE]
|
|
10
|
-
|
|
11
|
-
Returns JSON to stdout with extracted values for shell-level echo/log messages.
|
|
12
|
-
"""
|
|
13
|
-
import json
|
|
14
|
-
import os
|
|
15
|
-
import sys
|
|
16
|
-
import datetime
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def main() -> int:
|
|
20
|
-
# --- Parse arguments ---
|
|
21
|
-
contract_file = sys.argv[1] if len(sys.argv) > 1 else '.opencode/orchestration/contract.json'
|
|
22
|
-
telemetry_dir = sys.argv[2] if len(sys.argv) > 2 else '.opencode/telemetry'
|
|
23
|
-
state_file = sys.argv[3] if len(sys.argv) > 3 else 'STATE.md'
|
|
24
|
-
state_backup_dir = sys.argv[4] if len(sys.argv) > 4 else '.opencode/state'
|
|
25
|
-
template_file = sys.argv[5] if len(sys.argv) > 5 else '.opencode/templates/contract.json'
|
|
26
|
-
new_state = sys.argv[6] if len(sys.argv) > 6 else None
|
|
27
|
-
|
|
28
|
-
start_time_file = os.path.join(telemetry_dir, '.phase_start')
|
|
29
|
-
phases_file = os.path.join(telemetry_dir, 'phases.jsonl')
|
|
30
|
-
summary_file = os.path.join(telemetry_dir, 'summary.json')
|
|
31
|
-
|
|
32
|
-
now = datetime.datetime.now(datetime.UTC)
|
|
33
|
-
now_iso = now.strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
34
|
-
|
|
35
|
-
# --- Load contract (file or template fallback) ---
|
|
36
|
-
contract = {}
|
|
37
|
-
if os.path.exists(contract_file):
|
|
38
|
-
with open(contract_file) as f:
|
|
39
|
-
contract = json.load(f)
|
|
40
|
-
elif os.path.exists(template_file):
|
|
41
|
-
with open(template_file) as f:
|
|
42
|
-
contract = json.load(f)
|
|
43
|
-
|
|
44
|
-
prev_state = contract.get('state', 'INIT')
|
|
45
|
-
|
|
46
|
-
# --- Update state if provided ---
|
|
47
|
-
if new_state:
|
|
48
|
-
contract['state'] = new_state
|
|
49
|
-
|
|
50
|
-
current_state = contract.get('state', 'UNKNOWN')
|
|
51
|
-
|
|
52
|
-
# --- Read start time and calculate phase elapsed ---
|
|
53
|
-
os.makedirs(telemetry_dir, exist_ok=True)
|
|
54
|
-
phase_elapsed_ms = 0
|
|
55
|
-
if os.path.exists(start_time_file):
|
|
56
|
-
with open(start_time_file) as f:
|
|
57
|
-
raw = f.read().strip()
|
|
58
|
-
if raw:
|
|
59
|
-
try:
|
|
60
|
-
start_ts = int(raw)
|
|
61
|
-
phase_elapsed_ms = int((now.timestamp() - start_ts) * 1000)
|
|
62
|
-
except ValueError:
|
|
63
|
-
pass
|
|
64
|
-
os.remove(start_time_file)
|
|
65
|
-
|
|
66
|
-
# --- Read last 'to' state from phases.jsonl for telemetry ---
|
|
67
|
-
last_to_state = 'INIT'
|
|
68
|
-
if os.path.exists(phases_file):
|
|
69
|
-
lines = [l.strip() for l in open(phases_file) if l.strip()]
|
|
70
|
-
if lines:
|
|
71
|
-
try:
|
|
72
|
-
last_to_state = json.loads(lines[-1]).get('to', 'INIT')
|
|
73
|
-
except json.JSONDecodeError:
|
|
74
|
-
pass
|
|
75
|
-
|
|
76
|
-
# --- Record phase telemetry ---
|
|
77
|
-
phase_entry = {
|
|
78
|
-
'ts': now_iso,
|
|
79
|
-
'from': last_to_state,
|
|
80
|
-
'to': current_state,
|
|
81
|
-
'elapsed_ms': phase_elapsed_ms,
|
|
82
|
-
}
|
|
83
|
-
with open(phases_file, 'a') as f:
|
|
84
|
-
f.write(json.dumps(phase_entry) + '\n')
|
|
85
|
-
|
|
86
|
-
# --- Contract migration (merge missing fields from template) ---
|
|
87
|
-
migrated = False
|
|
88
|
-
if os.path.exists(template_file):
|
|
89
|
-
with open(template_file) as f:
|
|
90
|
-
template = json.load(f)
|
|
91
|
-
|
|
92
|
-
old_ver = contract.get('contract_version', '0.0.0')
|
|
93
|
-
new_ver = template.get('contract_version', '0.5.2')
|
|
94
|
-
|
|
95
|
-
needs_migration = (
|
|
96
|
-
old_ver != new_ver
|
|
97
|
-
or not all(k in contract for k in ['state', 'requirements', 'governance', 'score'])
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
if needs_migration:
|
|
101
|
-
for key in template:
|
|
102
|
-
if key not in contract:
|
|
103
|
-
contract[key] = template[key]
|
|
104
|
-
if 'extension_skills' not in contract.get('governance', {}):
|
|
105
|
-
contract.setdefault('governance', {})['extension_skills'] = []
|
|
106
|
-
contract['contract_version'] = new_ver
|
|
107
|
-
migrated = True
|
|
108
|
-
|
|
109
|
-
# --- Write contract to primary location ---
|
|
110
|
-
os.makedirs(os.path.dirname(contract_file) or '.', exist_ok=True)
|
|
111
|
-
with open(contract_file, 'w') as f:
|
|
112
|
-
json.dump(contract, f, indent=2)
|
|
113
|
-
|
|
114
|
-
# --- Backup contract to state backup dir ---
|
|
115
|
-
os.makedirs(state_backup_dir, exist_ok=True)
|
|
116
|
-
with open(os.path.join(state_backup_dir, 'contract.json'), 'w') as f:
|
|
117
|
-
json.dump(contract, f, indent=2)
|
|
118
|
-
|
|
119
|
-
# --- Update telemetry summary.json ---
|
|
120
|
-
total_ms = 0
|
|
121
|
-
phases = []
|
|
122
|
-
if os.path.exists(phases_file):
|
|
123
|
-
with open(phases_file) as f:
|
|
124
|
-
for line in f:
|
|
125
|
-
line = line.strip()
|
|
126
|
-
if not line:
|
|
127
|
-
continue
|
|
128
|
-
try:
|
|
129
|
-
entry = json.loads(line)
|
|
130
|
-
total_ms += entry.get('elapsed_ms', 0)
|
|
131
|
-
phases.append(entry.get('to', ''))
|
|
132
|
-
except json.JSONDecodeError:
|
|
133
|
-
pass
|
|
134
|
-
|
|
135
|
-
summary = {
|
|
136
|
-
'phases_completed': phases,
|
|
137
|
-
'total_elapsed_ms': total_ms,
|
|
138
|
-
'total_elapsed_s': round(total_ms / 1000, 1),
|
|
139
|
-
'updated_at': now_iso,
|
|
140
|
-
}
|
|
141
|
-
with open(summary_file, 'w') as f:
|
|
142
|
-
json.dump(summary, f, indent=2)
|
|
143
|
-
|
|
144
|
-
# --- Extract values for STATE.md ---
|
|
145
|
-
retry = contract.get('retry', {})
|
|
146
|
-
score_obj = contract.get('score', {})
|
|
147
|
-
state_val = contract.get('state', 'UNKNOWN')
|
|
148
|
-
phase_val = retry.get('current_phase', 'none')
|
|
149
|
-
score_combined = str(score_obj.get('combined', '?'))
|
|
150
|
-
|
|
151
|
-
# Issues / blockers
|
|
152
|
-
issues = retry.get('issues', [])
|
|
153
|
-
issues_blockers = '\n'.join(f'- {i}' for i in issues) if issues else 'None'
|
|
154
|
-
|
|
155
|
-
# ADRs (last 3)
|
|
156
|
-
adr_log = contract.get('decisions', {}).get('adr_log', [])
|
|
157
|
-
if adr_log:
|
|
158
|
-
adr_lines = '\n'.join(
|
|
159
|
-
f'- {e.get("id", "?")}: {e.get("title", "")}' for e in adr_log[-3:]
|
|
160
|
-
)
|
|
161
|
-
else:
|
|
162
|
-
adr_lines = 'No ADRs recorded'
|
|
163
|
-
|
|
164
|
-
# Last completed phase
|
|
165
|
-
metrics_phases = contract.get('metrics', {}).get('phases_completed', [])
|
|
166
|
-
last_phase = metrics_phases[-1] if metrics_phases else 'INIT'
|
|
167
|
-
|
|
168
|
-
# --- Write STATE.md ---
|
|
169
|
-
state_md = (
|
|
170
|
-
'# Project State\n'
|
|
171
|
-
'\n'
|
|
172
|
-
'## Current Focus\n'
|
|
173
|
-
f'Agent orchestration — {state_val} (phase: {phase_val}). Score: {score_combined}.\n'
|
|
174
|
-
'\n'
|
|
175
|
-
'## Known Blockers\n'
|
|
176
|
-
f'{issues_blockers}\n'
|
|
177
|
-
'\n'
|
|
178
|
-
'## Active Decisions\n'
|
|
179
|
-
f'{adr_lines}\n'
|
|
180
|
-
'\n'
|
|
181
|
-
'## Recent Changes\n'
|
|
182
|
-
f'- Last state transition: {last_phase}\n'
|
|
183
|
-
)
|
|
184
|
-
state_dir = os.path.dirname(state_file)
|
|
185
|
-
if state_dir:
|
|
186
|
-
os.makedirs(state_dir, exist_ok=True)
|
|
187
|
-
with open(state_file, 'w') as f:
|
|
188
|
-
f.write(state_md)
|
|
189
|
-
|
|
190
|
-
# --- Output JSON summary for shell script consumption ---
|
|
191
|
-
output = {
|
|
192
|
-
'state': state_val,
|
|
193
|
-
'phase': phase_val,
|
|
194
|
-
'score': score_combined,
|
|
195
|
-
'prev_state': prev_state,
|
|
196
|
-
'current_state': current_state,
|
|
197
|
-
'phase_elapsed_ms': phase_elapsed_ms,
|
|
198
|
-
'phase_elapsed_s': round(phase_elapsed_ms / 1000, 1),
|
|
199
|
-
'migrated': migrated,
|
|
200
|
-
'contract_version': contract.get('contract_version', '?'),
|
|
201
|
-
'phases_count': len(phases),
|
|
202
|
-
'total_elapsed_s': summary['total_elapsed_s'],
|
|
203
|
-
'issues_count': len(issues),
|
|
204
|
-
'last_to_state': last_to_state,
|
|
205
|
-
}
|
|
206
|
-
print(json.dumps(output))
|
|
207
|
-
return 0
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if __name__ == '__main__':
|
|
211
|
-
sys.exit(main())
|
package/src/postflight.sh
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# opencode-kit postflight — persist contract + telemetry + update STATE.md
|
|
3
|
-
# Run after every delegation or phase change.
|
|
4
|
-
set -euo pipefail
|
|
5
|
-
|
|
6
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
7
|
-
# shellcheck source=./platform.sh
|
|
8
|
-
. "$SCRIPT_DIR/platform.sh"
|
|
9
|
-
|
|
10
|
-
CONTRACT_KEY="orchestration-contract"
|
|
11
|
-
CONTRACT_FILE=".opencode/orchestration/contract.json"
|
|
12
|
-
STATE_FILE="STATE.md"
|
|
13
|
-
TELEMETRY_DIR=".opencode/telemetry"
|
|
14
|
-
STATE_BACKUP_DIR=".opencode/state"
|
|
15
|
-
TEMPLATE_FILE=".opencode/templates/contract.json"
|
|
16
|
-
|
|
17
|
-
mkdir -p "$TELEMETRY_DIR" "$STATE_BACKUP_DIR"
|
|
18
|
-
|
|
19
|
-
echo "[opencode-kit] Post-flight: persisting state..."
|
|
20
|
-
|
|
21
|
-
# --- Resolve contract: try lean-ctx first, fall back to file ---
|
|
22
|
-
LEAN_CTX_CONTRACT=$(lean-ctx ctx_knowledge recall --query "$CONTRACT_KEY" 2>/dev/null || true)
|
|
23
|
-
if [ -n "$LEAN_CTX_CONTRACT" ]; then
|
|
24
|
-
echo "$LEAN_CTX_CONTRACT" > "$CONTRACT_FILE"
|
|
25
|
-
fi
|
|
26
|
-
|
|
27
|
-
# --- Single Python call: batch all contract/telemetry/STATE.md operations ---
|
|
28
|
-
PYTHON_OUTPUT=""
|
|
29
|
-
STATE=""
|
|
30
|
-
if [ -n "$PYTHON_CMD" ]; then
|
|
31
|
-
PYTHON_OUTPUT=$($PYTHON_CMD "$SCRIPT_DIR/postflight.py" \
|
|
32
|
-
"$CONTRACT_FILE" "$TELEMETRY_DIR" "$STATE_FILE" \
|
|
33
|
-
"$STATE_BACKUP_DIR" "$TEMPLATE_FILE" \
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
# Single Python parse call: extract all values as pipe-delimited string
|
|
37
|
-
IFS='|' read -r STATE PHASE SCORE PREV_STATE CURRENT_STATE ELAPSED_S MIGRATED CONTRACT_VER PHASES_COUNT TOTAL_ELAPSED_S LAST_TO_STATE <<< \
|
|
38
|
-
"$(echo "$PYTHON_OUTPUT" | $PYTHON_CMD -c "
|
|
39
|
-
import sys, json
|
|
40
|
-
d = json.load(sys.stdin)
|
|
41
|
-
print('|'.join([str(d.get(k, '')) for k in [
|
|
42
|
-
'state', 'phase', 'score', 'prev_state', 'current_state',
|
|
43
|
-
'phase_elapsed_s', 'migrated', 'contract_version',
|
|
44
|
-
'phases_count', 'total_elapsed_s', 'last_to_state'
|
|
45
|
-
]]))
|
|
46
|
-
" 2>/dev/null || echo "UNKNOWN|none|?|INIT|UNKNOWN|0|false|?|0|0|INIT")"
|
|
47
|
-
|
|
48
|
-
# --- Echo status messages ---
|
|
49
|
-
echo " 📊 Telemetry: $PREV_STATE → $CURRENT_STATE (${ELAPSED_S}s)"
|
|
50
|
-
|
|
51
|
-
if [ "$MIGRATED" = "true" ]; then
|
|
52
|
-
echo " 🔄 Contract migrated to v${CONTRACT_VER}"
|
|
53
|
-
fi
|
|
54
|
-
|
|
55
|
-
echo " 📝 Contract state: $STATE (phase: $PHASE, score: $SCORE)"
|
|
56
|
-
echo " ✅ STATE.md synced"
|
|
57
|
-
|
|
58
|
-
echo " 📈 Telemetry summary: ${PHASES_COUNT} phases, ${TOTAL_ELAPSED_S}s total"
|
|
59
|
-
else
|
|
60
|
-
echo " ⚠️ Python not available, skipping postflight processing"
|
|
61
|
-
fi
|
|
62
|
-
|
|
63
|
-
# --- Persist to lean-ctx ---
|
|
64
|
-
if [ -n "$PYTHON_CMD" ] && [ -f "$CONTRACT_FILE" ]; then
|
|
65
|
-
CONTRACT_JSON=$(cat "$CONTRACT_FILE")
|
|
66
|
-
if lean-ctx ctx_knowledge remember \
|
|
67
|
-
category architecture \
|
|
68
|
-
key "$CONTRACT_KEY" \
|
|
69
|
-
value "$CONTRACT_JSON" 2>/dev/null; then
|
|
70
|
-
echo " ✅ Contract persisted to lean-ctx"
|
|
71
|
-
fi
|
|
72
|
-
fi
|
|
73
|
-
|
|
74
|
-
# File fallback always written by Python script to $STATE_BACKUP_DIR/contract.json
|
|
75
|
-
echo " ✅ Contract persisted to file: $STATE_BACKUP_DIR/contract.json"
|
|
76
|
-
|
|
77
|
-
# --- Save ctx_session ---
|
|
78
|
-
lean-ctx ctx_session save 2>/dev/null && \
|
|
79
|
-
echo " ✅ Session saved" || \
|
|
80
|
-
echo " ⚠️ ctx_session save skipped (not available)"
|
|
81
|
-
|
|
82
|
-
echo "[opencode-kit] ✅ Post-flight complete."
|
package/src/preflight.sh
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# ⛔ opencode-kit preflight — MANDATORY enforcement gate
|
|
3
|
-
# Must run before any tool call. Exits with error if rules violated.
|
|
4
|
-
set -euo pipefail
|
|
5
|
-
|
|
6
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
7
|
-
# shellcheck source=./platform.sh
|
|
8
|
-
. "$SCRIPT_DIR/platform.sh"
|
|
9
|
-
|
|
10
|
-
CONTRACT_KEY="orchestration-contract"
|
|
11
|
-
RULES_FILE=".opencode/rules/rules.json"
|
|
12
|
-
CONTRACT_FILE=".opencode/orchestration/contract.json"
|
|
13
|
-
|
|
14
|
-
RED='\033[0;31m'
|
|
15
|
-
GREEN='\033[0;32m'
|
|
16
|
-
YELLOW='\033[1;33m'
|
|
17
|
-
NC='\033[0m'
|
|
18
|
-
|
|
19
|
-
echo "[opencode-kit] ⛔ Pre-flight check..."
|
|
20
|
-
|
|
21
|
-
# --- Check 1: contract.json exists on disk ---
|
|
22
|
-
if [ ! -f "$CONTRACT_FILE" ]; then
|
|
23
|
-
echo -e "${RED}⛔ FAILED: $CONTRACT_FILE not found. Run 'opencode-kit init' first.${NC}"
|
|
24
|
-
exit 1
|
|
25
|
-
fi
|
|
26
|
-
echo " ✅ contract.json exists"
|
|
27
|
-
|
|
28
|
-
# --- Check 2: not on main ---
|
|
29
|
-
BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
|
|
30
|
-
if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
|
|
31
|
-
echo -e "${RED}⛔ FAILED: On '$BRANCH' branch. Create a feature branch first.${NC}"
|
|
32
|
-
echo " → git checkout -b feature/<YYYYMMDD>-<description>"
|
|
33
|
-
exit 1
|
|
34
|
-
fi
|
|
35
|
-
echo " ✅ Branch: $BRANCH (safe)"
|
|
36
|
-
|
|
37
|
-
# --- Check 3: MCP Availability (from rules.json) ---
|
|
38
|
-
echo ""
|
|
39
|
-
echo " Checking MCP availability from rules.json..."
|
|
40
|
-
|
|
41
|
-
MCP_FAIL=0
|
|
42
|
-
|
|
43
|
-
if [ -n "$PYTHON_CMD" ] && [ -f "$RULES_FILE" ]; then
|
|
44
|
-
# Parse required_mcps from rules.json
|
|
45
|
-
$PYTHON_CMD -c "
|
|
46
|
-
import json, sys, subprocess, os
|
|
47
|
-
|
|
48
|
-
with open('$RULES_FILE') as f:
|
|
49
|
-
rules = json.load(f)
|
|
50
|
-
|
|
51
|
-
mcps = rules.get('required_mcps', {})
|
|
52
|
-
if not isinstance(mcps, dict) or 'description' in mcps:
|
|
53
|
-
# Skip the meta-description field
|
|
54
|
-
mcps = {k: v for k, v in mcps.items() if k != 'description' and isinstance(v, dict)}
|
|
55
|
-
|
|
56
|
-
if not mcps:
|
|
57
|
-
print(' ℹ️ No required_mcps defined in rules.json — skipping MCP checks')
|
|
58
|
-
sys.exit(0)
|
|
59
|
-
|
|
60
|
-
failures = []
|
|
61
|
-
for name, cfg in mcps.items():
|
|
62
|
-
cli_check = cfg.get('check_cli', '')
|
|
63
|
-
tool_check = cfg.get('check_tool', '')
|
|
64
|
-
severity = cfg.get('severity', 'optional')
|
|
65
|
-
desc = cfg.get('description', name)
|
|
66
|
-
|
|
67
|
-
available = False
|
|
68
|
-
# Try CLI check first
|
|
69
|
-
if cli_check:
|
|
70
|
-
try:
|
|
71
|
-
result = subprocess.run(cli_check, shell=True, capture_output=True, timeout=5)
|
|
72
|
-
if result.returncode == 0:
|
|
73
|
-
available = True
|
|
74
|
-
except:
|
|
75
|
-
pass
|
|
76
|
-
|
|
77
|
-
# Try tool check as fallback
|
|
78
|
-
if not available and tool_check:
|
|
79
|
-
try:
|
|
80
|
-
result = subprocess.run(tool_check, shell=True, capture_output=True, timeout=5)
|
|
81
|
-
if result.returncode == 0:
|
|
82
|
-
available = True
|
|
83
|
-
except:
|
|
84
|
-
pass
|
|
85
|
-
|
|
86
|
-
if available:
|
|
87
|
-
print(f' ✅ {name}: available — {desc}')
|
|
88
|
-
elif severity == 'required':
|
|
89
|
-
print(f' ❌ {name}: NOT DETECTED — {desc}')
|
|
90
|
-
failures.append(name)
|
|
91
|
-
else:
|
|
92
|
-
print(f' ⚠️ {name}: not detected — {desc} (optional)')
|
|
93
|
-
|
|
94
|
-
if failures:
|
|
95
|
-
print('')
|
|
96
|
-
for name in failures:
|
|
97
|
-
print(f' → Ensure {name} is configured in opencode.json MCP servers')
|
|
98
|
-
sys.exit(1)
|
|
99
|
-
else:
|
|
100
|
-
sys.exit(0)
|
|
101
|
-
" 2>&1 || MCP_FAIL=1
|
|
102
|
-
fi
|
|
103
|
-
|
|
104
|
-
echo ""
|
|
105
|
-
|
|
106
|
-
# --- Check 4: rules.json exists ---
|
|
107
|
-
if [ ! -f "$RULES_FILE" ]; then
|
|
108
|
-
echo -e "${YELLOW}⚠️ WARNING: $RULES_FILE not found. Rules enforcement disabled.${NC}"
|
|
109
|
-
else
|
|
110
|
-
echo " ✅ rules.json found"
|
|
111
|
-
fi
|
|
112
|
-
|
|
113
|
-
# --- Telemetry: record phase start ---
|
|
114
|
-
mkdir -p .opencode/telemetry
|
|
115
|
-
echo $(date +%s) > .opencode/telemetry/.phase_start
|
|
116
|
-
|
|
117
|
-
# --- Check 5: contract state validation ---
|
|
118
|
-
if [ -n "$PYTHON_CMD" ] && [ -f "$CONTRACT_FILE" ]; then
|
|
119
|
-
STATE=$($PYTHON_CMD -c "
|
|
120
|
-
import json,sys
|
|
121
|
-
try:
|
|
122
|
-
with open('$CONTRACT_FILE') as f: d=json.load(f)
|
|
123
|
-
print(d.get('state','UNKNOWN'))
|
|
124
|
-
except: print('PARSE_ERROR')
|
|
125
|
-
" 2>/dev/null)
|
|
126
|
-
if [ "$STATE" = "PARSE_ERROR" ] || [ "$STATE" = "UNKNOWN" ]; then
|
|
127
|
-
echo -e "${YELLOW} ⚠️ Contract state: unknown — contract.json may be malformed${NC}"
|
|
128
|
-
else
|
|
129
|
-
echo " ✅ Contract state: $STATE"
|
|
130
|
-
fi
|
|
131
|
-
fi
|
|
132
|
-
|
|
133
|
-
# --- Final verdict ---
|
|
134
|
-
if [ "$MCP_FAIL" -eq 1 ]; then
|
|
135
|
-
echo -e "${YELLOW}[opencode-kit] ⛔ Pre-flight completed with WARNINGS. Missing MCPs may cause failures.${NC}"
|
|
136
|
-
else
|
|
137
|
-
echo "[opencode-kit] ✅ Pre-flight passed. All MCPs available. Proceed."
|
|
138
|
-
fi
|
package/src/status.sh
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# opencode-kit status — pretty terminal dashboard
|
|
3
|
-
# Usage: bash src/status.sh
|
|
4
|
-
set -euo pipefail
|
|
5
|
-
|
|
6
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
7
|
-
# shellcheck source=./platform.sh
|
|
8
|
-
. "$SCRIPT_DIR/platform.sh"
|
|
9
|
-
|
|
10
|
-
CONTRACT_FILE=".opencode/orchestration/contract.json"
|
|
11
|
-
RULES_FILE=".opencode/rules/rules.json"
|
|
12
|
-
TELEMETRY_DIR=".opencode/telemetry"
|
|
13
|
-
|
|
14
|
-
RED='\033[0;31m'
|
|
15
|
-
GREEN='\033[0;32m'
|
|
16
|
-
YELLOW='\033[1;33m'
|
|
17
|
-
CYAN='\033[0;36m'
|
|
18
|
-
BOLD='\033[1m'
|
|
19
|
-
NC='\033[0m'
|
|
20
|
-
|
|
21
|
-
echo ""
|
|
22
|
-
echo -e "${CYAN}${BOLD}##══════════════════════════════════════════╗${NC}"
|
|
23
|
-
echo -e "${CYAN}${BOLD}# opencode-kit Dashboard #${NC}"
|
|
24
|
-
echo -e "${CYAN}${BOLD}##══════════════════════════════════════════╝${NC}"
|
|
25
|
-
echo ""
|
|
26
|
-
|
|
27
|
-
# === Contract State ===
|
|
28
|
-
if [ -f "$CONTRACT_FILE" ] && [ -n "$PYTHON_CMD" ]; then
|
|
29
|
-
$PYTHON_CMD -c "
|
|
30
|
-
import json
|
|
31
|
-
with open('$CONTRACT_FILE') as f:
|
|
32
|
-
c = json.load(f)
|
|
33
|
-
|
|
34
|
-
state = c.get('state', 'UNKNOWN')
|
|
35
|
-
ver = c.get('contract_version', '?')
|
|
36
|
-
goal = c.get('requirements', {}).get('goal', 'Not set')
|
|
37
|
-
phases = c.get('metrics', {}).get('phases_completed', [])
|
|
38
|
-
score = c.get('score', {}).get('combined', 0)
|
|
39
|
-
verdict = c.get('score', {}).get('verdict', 'PENDING')
|
|
40
|
-
adrs = len(c.get('decisions', {}).get('adr_log', []))
|
|
41
|
-
ext_skills = c.get('governance', {}).get('extension_skills', [])
|
|
42
|
-
|
|
43
|
-
# State color
|
|
44
|
-
state_colors = {
|
|
45
|
-
'INIT': '\033[0;36m',
|
|
46
|
-
'PLAN': '\033[1;33m',
|
|
47
|
-
'PLAN_SCORED': '\033[1;33m',
|
|
48
|
-
'EXECUTE': '\033[0;32m',
|
|
49
|
-
'EXECUTE_SCORED': '\033[0;32m',
|
|
50
|
-
'REVIEW': '\033[0;34m',
|
|
51
|
-
'REVIEW_SCORED': '\033[0;34m',
|
|
52
|
-
'COMPLETE': '\033[0;32m',
|
|
53
|
-
'BLOCKED': '\033[0;31m',
|
|
54
|
-
}
|
|
55
|
-
color = state_colors.get(state, '\033[0m')
|
|
56
|
-
nc = '\033[0m'
|
|
57
|
-
|
|
58
|
-
print(f' ${BOLD}Contract State:${NC} {color}{state}{nc} (v{ver})')
|
|
59
|
-
print(f' ${BOLD}Goal:${NC} {goal[:70]}...' if len(goal) > 70 else f' ${BOLD}Goal:${NC} {goal}')
|
|
60
|
-
print(f' ${BOLD}Score:${NC} {score}/100 ({verdict})')
|
|
61
|
-
print(f' ${BOLD}Phases:${NC} {len(phases)} completed: {\" → \".join(phases[-4:])}')
|
|
62
|
-
print(f' ${BOLD}ADRs:${NC} {adrs} recorded')
|
|
63
|
-
if ext_skills:
|
|
64
|
-
print(f' ${BOLD}Extension Skills:${NC} {\", \".join(ext_skills)}')
|
|
65
|
-
" 2>/dev/null || echo " ⚠️ Could not parse contract"
|
|
66
|
-
else
|
|
67
|
-
echo -e " ${YELLOW}⚠️ No contract found${NC}"
|
|
68
|
-
fi
|
|
69
|
-
|
|
70
|
-
# === Telemetry ===
|
|
71
|
-
echo ""
|
|
72
|
-
echo -e "${BOLD}⏱ Telemetry${NC}"
|
|
73
|
-
if [ -f "$TELEMETRY_DIR/summary.json" ] && [ -n "$PYTHON_CMD" ]; then
|
|
74
|
-
$PYTHON_CMD -c "
|
|
75
|
-
import json
|
|
76
|
-
with open('$TELEMETRY_DIR/summary.json') as f:
|
|
77
|
-
t = json.load(f)
|
|
78
|
-
total_s = t.get('total_elapsed_s', 0)
|
|
79
|
-
phases = t.get('phases_completed', [])
|
|
80
|
-
print(f' Total time: {total_s}s across {len(phases)} phases')
|
|
81
|
-
" 2>/dev/null
|
|
82
|
-
else
|
|
83
|
-
echo -e " ${YELLOW}No telemetry data yet${NC}"
|
|
84
|
-
fi
|
|
85
|
-
|
|
86
|
-
# === Rules ===
|
|
87
|
-
echo ""
|
|
88
|
-
echo -e "${BOLD}📋 Rules${NC}"
|
|
89
|
-
if [ -f "$RULES_FILE" ] && [ -n "$PYTHON_CMD" ]; then
|
|
90
|
-
$PYTHON_CMD -c "
|
|
91
|
-
import json
|
|
92
|
-
with open('$RULES_FILE') as f:
|
|
93
|
-
r = json.load(f)
|
|
94
|
-
rules = r.get('rules', [])
|
|
95
|
-
critical = [x for x in rules if x.get('severity') == 'CRITICAL']
|
|
96
|
-
high = [x for x in rules if x.get('severity') == 'HIGH']
|
|
97
|
-
mcps = list(r.get('required_mcps', {}).keys())
|
|
98
|
-
mcps = [m for m in mcps if m != 'description']
|
|
99
|
-
print(f' {len(critical)} CRITICAL rules, {len(high)} HIGH rules')
|
|
100
|
-
if mcps:
|
|
101
|
-
print(f' Required MCPs: {\", \".join(mcps)}')
|
|
102
|
-
" 2>/dev/null
|
|
103
|
-
else
|
|
104
|
-
echo -e " ${YELLOW}No rules.json${NC}"
|
|
105
|
-
fi
|
|
106
|
-
|
|
107
|
-
# === Quick actions ===
|
|
108
|
-
echo ""
|
|
109
|
-
echo -e "${BOLD}⚡ Quick Actions${NC}"
|
|
110
|
-
echo " bash .opencode/src/doctor.sh — Run diagnostics"
|
|
111
|
-
echo " bash .opencode/src/telemetry.sh — View telemetry details"
|
|
112
|
-
echo " bash .opencode/src/adr.sh — Record new ADR"
|
|
113
|
-
echo ""
|
package/src/telemetry.sh
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# opencode-kit telemetry — view phase telemetry
|
|
3
|
-
# Usage: bash src/telemetry.sh [--json|--summary|--phases]
|
|
4
|
-
set -euo pipefail
|
|
5
|
-
|
|
6
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
7
|
-
# shellcheck source=./platform.sh
|
|
8
|
-
. "$SCRIPT_DIR/platform.sh"
|
|
9
|
-
|
|
10
|
-
TELEMETRY_DIR=".opencode/telemetry"
|
|
11
|
-
CONTRACT_FILE=".opencode/orchestration/contract.json"
|
|
12
|
-
|
|
13
|
-
RED='\033[0;31m'
|
|
14
|
-
GREEN='\033[0;32m'
|
|
15
|
-
YELLOW='\033[1;33m'
|
|
16
|
-
CYAN='\033[0;36m'
|
|
17
|
-
NC='\033[0m'
|
|
18
|
-
|
|
19
|
-
MODE="${1:-summary}"
|
|
20
|
-
|
|
21
|
-
echo -e "${CYAN}[opencode-kit] 📊 Telemetry${NC}"
|
|
22
|
-
echo ""
|
|
23
|
-
|
|
24
|
-
case "$MODE" in
|
|
25
|
-
--json)
|
|
26
|
-
if [ -f "$TELEMETRY_DIR/summary.json" ]; then
|
|
27
|
-
cat "$TELEMETRY_DIR/summary.json"
|
|
28
|
-
else
|
|
29
|
-
echo -e "${YELLOW}No telemetry data yet. Run a phase first.${NC}"
|
|
30
|
-
fi
|
|
31
|
-
;;
|
|
32
|
-
--phases)
|
|
33
|
-
if [ -f "$TELEMETRY_DIR/phases.jsonl" ]; then
|
|
34
|
-
echo "Phase transitions:"
|
|
35
|
-
$PYTHON_CMD -c "
|
|
36
|
-
import sys, json
|
|
37
|
-
for line in sys.stdin:
|
|
38
|
-
line = line.strip()
|
|
39
|
-
if not line:
|
|
40
|
-
continue
|
|
41
|
-
d = json.loads(line)
|
|
42
|
-
from_ = d.get('from', '?')
|
|
43
|
-
to_ = d.get('to', '?')
|
|
44
|
-
ms = d.get('elapsed_ms', 0)
|
|
45
|
-
print(f' {from_:20s} → {to_:20s} {ms/1000:5.1f}s')
|
|
46
|
-
" < "$TELEMETRY_DIR/phases.jsonl" 2>/dev/null || echo -e "${YELLOW}Could not parse phase data${NC}"
|
|
47
|
-
else
|
|
48
|
-
echo -e "${YELLOW}No phase data yet.${NC}"
|
|
49
|
-
fi
|
|
50
|
-
;;
|
|
51
|
-
--summary|*)
|
|
52
|
-
if [ -f "$TELEMETRY_DIR/summary.json" ]; then
|
|
53
|
-
read -r TOTAL_S PHASES < <(TELEMETRY_DIR="$TELEMETRY_DIR" $PYTHON_CMD -c "
|
|
54
|
-
import json, os
|
|
55
|
-
d = json.load(open(os.environ['TELEMETRY_DIR'] + '/summary.json'))
|
|
56
|
-
print(d.get('total_elapsed_s', 0))
|
|
57
|
-
print(len(d.get('phases_completed', [])))
|
|
58
|
-
" 2>/dev/null || echo -e "0\n0")
|
|
59
|
-
echo " Total elapsed: ${TOTAL_S}s"
|
|
60
|
-
echo " Phases completed: $PHASES"
|
|
61
|
-
echo ""
|
|
62
|
-
echo " Latest phases:"
|
|
63
|
-
tail -5 "$TELEMETRY_DIR/phases.jsonl" 2>/dev/null | $PYTHON_CMD -c "
|
|
64
|
-
import sys, json
|
|
65
|
-
for line in sys.stdin:
|
|
66
|
-
line = line.strip()
|
|
67
|
-
if not line:
|
|
68
|
-
continue
|
|
69
|
-
d = json.loads(line)
|
|
70
|
-
ts = d.get('ts', '?')
|
|
71
|
-
if ts != '?':
|
|
72
|
-
ts = ts.split('T')[1].split('.')[0] if 'T' in ts else ts
|
|
73
|
-
from_ = d.get('from', '?')
|
|
74
|
-
to_ = d.get('to', '?')
|
|
75
|
-
ms = d.get('elapsed_ms', 0)
|
|
76
|
-
print(f' {ts} {from_} → {to_} ({ms//1000}s)')
|
|
77
|
-
" 2>/dev/null || echo -e "${YELLOW}Could not parse phase data${NC}"
|
|
78
|
-
else
|
|
79
|
-
echo -e "${YELLOW}No telemetry data yet. Run a phase first.${NC}"
|
|
80
|
-
echo " Phases are recorded automatically by postflight.sh"
|
|
81
|
-
fi
|
|
82
|
-
;;
|
|
83
|
-
esac
|