@ikieaneh/opencode-kit 0.5.4 → 0.5.6
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/.opencode/plugins/opencode-kit.js +12 -0
- package/package.json +1 -1
- package/src/analytics.sh +79 -0
- package/src/doctor.sh +123 -0
- package/src/init.sh +16 -0
- package/src/new-skill.sh +60 -0
- package/src/status.sh +114 -0
- package/src/verify.sh +1 -1
- package/templates/contract.json +2 -1
|
@@ -171,8 +171,20 @@ export const OpencodeKitPlugin = async ({ client, directory }) => {
|
|
|
171
171
|
config.skills = config.skills || {};
|
|
172
172
|
config.skills.paths = config.skills.paths || [];
|
|
173
173
|
|
|
174
|
+
// Detect if other plugins might conflict with opencode-kit's system prompt
|
|
175
|
+
if (config.plugins && Array.isArray(config.plugins)) {
|
|
176
|
+
const kitIndex = config.plugins.findIndex(p =>
|
|
177
|
+
typeof p === 'string' && p.includes('opencode-kit')
|
|
178
|
+
);
|
|
179
|
+
if (kitIndex > 0) {
|
|
180
|
+
const firstPlugin = config.plugins[0];
|
|
181
|
+
log('warn', `Plugin ordering conflict: opencode-kit should be FIRST, but found '${firstPlugin}' at position 0 and opencode-kit at position ${kitIndex}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
174
185
|
// Register user project skills FIRST (higher priority)
|
|
175
186
|
const userSkillsDir = path.join(projectDir, '.opencode/skills');
|
|
187
|
+
const userSkillsDir = path.join(projectDir, '.opencode/skills');
|
|
176
188
|
if (fs.existsSync(userSkillsDir) && !config.skills.paths.includes(userSkillsDir)) {
|
|
177
189
|
config.skills.paths.push(userSkillsDir);
|
|
178
190
|
log('info', `Registered user skills: ${userSkillsDir}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ikieaneh/opencode-kit",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.6",
|
|
4
4
|
"description": "Standardized OpenCode orchestration framework — contract-based, rules-enforced, zero-touch agent workflow. Install as plugin.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "RizkiRachman",
|
package/src/analytics.sh
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# opencode-kit analytics — aggregate telemetry across phases
|
|
3
|
+
# Usage: bash src/analytics.sh [--json]
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
7
|
+
. "$SCRIPT_DIR/platform.sh"
|
|
8
|
+
|
|
9
|
+
TELEMETRY_DIR=".opencode/telemetry"
|
|
10
|
+
CONTRACT_FILE=".opencode/orchestration/contract.json"
|
|
11
|
+
|
|
12
|
+
RED='\033[0;31m'
|
|
13
|
+
GREEN='\033[0;32m'
|
|
14
|
+
YELLOW='\033[1;33m'
|
|
15
|
+
CYAN='\033[0;36m'
|
|
16
|
+
NC='\033[0m'
|
|
17
|
+
|
|
18
|
+
MODE="${1:-table}"
|
|
19
|
+
|
|
20
|
+
echo -e "${CYAN}📊 opencode-kit Analytics${NC}"
|
|
21
|
+
echo ""
|
|
22
|
+
|
|
23
|
+
if [ ! -f "$TELEMETRY_DIR/phases.jsonl" ] || [ ! -f "$TELEMETRY_DIR/summary.json" ]; then
|
|
24
|
+
echo -e "${YELLOW}No telemetry data yet. Run a few phases first.${NC}"
|
|
25
|
+
exit 0
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
if [ -z "$PYTHON_CMD" ]; then
|
|
29
|
+
echo -e "${RED}Python required for analytics.${NC}"
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
$PYTHON_CMD -c "
|
|
34
|
+
import json
|
|
35
|
+
|
|
36
|
+
# Load phases
|
|
37
|
+
phases = []
|
|
38
|
+
try:
|
|
39
|
+
with open('$TELEMETRY_DIR/phases.jsonl') as f:
|
|
40
|
+
for line in f:
|
|
41
|
+
line = line.strip()
|
|
42
|
+
if line:
|
|
43
|
+
phases.append(json.loads(line))
|
|
44
|
+
except:
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
# Load summary
|
|
48
|
+
summary = {}
|
|
49
|
+
try:
|
|
50
|
+
with open('$TELEMETRY_DIR/summary.json') as f:
|
|
51
|
+
summary = json.load(f)
|
|
52
|
+
except:
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
if not phases:
|
|
56
|
+
print(' No phases recorded')
|
|
57
|
+
exit(0)
|
|
58
|
+
|
|
59
|
+
total_ms = sum(p.get('elapsed_ms', 0) for p in phases)
|
|
60
|
+
avg_ms = total_ms / len(phases) if phases else 0
|
|
61
|
+
|
|
62
|
+
print(f' Total sessions: {len(phases)}')
|
|
63
|
+
print(f' Total time: {total_ms/1000:.1f}s ({total_ms/60000:.1f}m)')
|
|
64
|
+
print(f' Avg per phase: {avg_ms/1000:.1f}s')
|
|
65
|
+
print('')
|
|
66
|
+
print(f' Phase Breakdown:')
|
|
67
|
+
for p in phases:
|
|
68
|
+
frm = p.get('from', '?')
|
|
69
|
+
to = p.get('to', '?')
|
|
70
|
+
ms = p.get('elapsed_ms', 0)
|
|
71
|
+
bar = '█' * max(1, int(ms / max(1, total_ms) * 30))
|
|
72
|
+
print(f' {frm:16s} → {to:16s} {ms/1000:6.1f}s {bar}')
|
|
73
|
+
|
|
74
|
+
# Cost estimate (rough: ~$0.15/1M tokens, ~1000 tok/s)
|
|
75
|
+
est_tokens = int(total_ms / 1000 * 1000) # rough: 1000 tok/sec
|
|
76
|
+
est_cost = est_tokens / 1_000_000 * 0.15
|
|
77
|
+
print(f'')
|
|
78
|
+
print(f' Estimated tokens: {est_tokens:,} (~{est_cost:.4f} USD at $0.15/1M tok)')
|
|
79
|
+
" 2>/dev/null || echo " ⚠️ Analytics failed"
|
package/src/doctor.sh
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# opencode-kit doctor — diagnostic command
|
|
3
|
+
# Checks: MCPs, contract, rules, permissions, git branch, agent configs
|
|
4
|
+
# Usage: bash src/doctor.sh [--json] [--fix]
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
+
. "$SCRIPT_DIR/platform.sh"
|
|
9
|
+
. "$SCRIPT_DIR/global-config.sh"
|
|
10
|
+
|
|
11
|
+
RULES_FILE=".opencode/rules/rules.json"
|
|
12
|
+
CONTRACT_FILE=".opencode/orchestration/contract.json"
|
|
13
|
+
OPENCODE_JSON="opencode.json"
|
|
14
|
+
|
|
15
|
+
RED='\033[0;31m'
|
|
16
|
+
GREEN='\033[0;32m'
|
|
17
|
+
YELLOW='\033[1;33m'
|
|
18
|
+
CYAN='\033[0;36m'
|
|
19
|
+
NC='\033[0m'
|
|
20
|
+
|
|
21
|
+
ISSUES=0
|
|
22
|
+
mode="${1:-}"
|
|
23
|
+
|
|
24
|
+
echo -e "${CYAN}🔍 opencode-kit doctor${NC}"
|
|
25
|
+
echo ""
|
|
26
|
+
|
|
27
|
+
# === 1. Contract check ===
|
|
28
|
+
echo -e "${CYAN}[CONTRACT]${NC} Checking orchestration contract..."
|
|
29
|
+
if [ ! -f "$CONTRACT_FILE" ]; then
|
|
30
|
+
echo -e " ${RED}❌ contract.json not found — run 'opencode-kit init'${NC}"
|
|
31
|
+
ISSUES=$((ISSUES + 1))
|
|
32
|
+
else
|
|
33
|
+
if [ -n "$PYTHON_CMD" ]; then
|
|
34
|
+
STATE=$($PYTHON_CMD -c "import json; d=json.load(open('$CONTRACT_FILE')); print(d.get('state','?'))" 2>/dev/null || echo "parse_error")
|
|
35
|
+
if [ "$STATE" = "parse_error" ]; then
|
|
36
|
+
echo -e " ${RED}❌ contract.json is malformed JSON${NC}"
|
|
37
|
+
ISSUES=$((ISSUES + 1))
|
|
38
|
+
else
|
|
39
|
+
echo -e " ✅ State: $STATE"
|
|
40
|
+
fi
|
|
41
|
+
fi
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# === 2. Rules check ===
|
|
45
|
+
echo -e "${CYAN}[RULES]${NC} Checking rules.json..."
|
|
46
|
+
if [ ! -f "$RULES_FILE" ]; then
|
|
47
|
+
echo -e " ${RED}❌ rules.json not found${NC}"
|
|
48
|
+
ISSUES=$((ISSUES + 1))
|
|
49
|
+
else
|
|
50
|
+
if [ -n "$PYTHON_CMD" ]; then
|
|
51
|
+
RULE_COUNT=$($PYTHON_CMD -c "import json; d=json.load(open('$RULES_FILE')); print(len(d.get('rules',[])))" 2>/dev/null || echo "0")
|
|
52
|
+
echo -e " ✅ $RULE_COUNT rules loaded"
|
|
53
|
+
fi
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# === 3. MCP checks ===
|
|
57
|
+
echo -e "${CYAN}[MCP]${NC} Checking required MCPs..."
|
|
58
|
+
if [ -f "$RULES_FILE" ] && [ -n "$PYTHON_CMD" ]; then
|
|
59
|
+
$PYTHON_CMD -c "
|
|
60
|
+
import json, subprocess, sys
|
|
61
|
+
with open('$RULES_FILE') as f:
|
|
62
|
+
rules = json.load(f)
|
|
63
|
+
mcps = rules.get('required_mcps', {})
|
|
64
|
+
mcps.pop('description', None)
|
|
65
|
+
for name, cfg in mcps.items():
|
|
66
|
+
cli = cfg.get('check_cli', '')
|
|
67
|
+
severity = cfg.get('severity', 'optional')
|
|
68
|
+
result = subprocess.run(cli, shell=True, capture_output=True, timeout=5)
|
|
69
|
+
ok = result.returncode == 0
|
|
70
|
+
if ok:
|
|
71
|
+
print(f' ✅ {name}: available')
|
|
72
|
+
elif severity == 'required':
|
|
73
|
+
print(f' ❌ {name}: MISSING (required)')
|
|
74
|
+
sys.exit(1)
|
|
75
|
+
else:
|
|
76
|
+
print(f' ⚠️ {name}: not detected (optional)')
|
|
77
|
+
" 2>/dev/null || ISSUES=$((ISSUES + 1))
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
# === 4. Git branch ===
|
|
81
|
+
echo -e "${CYAN}[GIT]${NC} Checking branch..."
|
|
82
|
+
BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
|
|
83
|
+
if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
|
|
84
|
+
echo -e " ${YELLOW}⚠️ On '$BRANCH' — create a feature branch for development${NC}"
|
|
85
|
+
else
|
|
86
|
+
echo -e " ✅ Branch: $BRANCH"
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
# === 5. Lean-ctx persistence ===
|
|
90
|
+
echo -e "${CYAN}[PERSIST]${NC} Checking persistence..."
|
|
91
|
+
if command -v lean-ctx &>/dev/null; then
|
|
92
|
+
echo -e " ✅ lean-ctx CLI available"
|
|
93
|
+
LEAN_OK=$(lean-ctx ctx_knowledge recall --query "orchestration-contract" &>/dev/null && echo "yes" || echo "no")
|
|
94
|
+
if [ "$LEAN_OK" = "yes" ]; then
|
|
95
|
+
echo -e " ✅ Contract found in lean-ctx"
|
|
96
|
+
else
|
|
97
|
+
echo -e " ⚠️ Contract not in lean-ctx (file fallback active)"
|
|
98
|
+
fi
|
|
99
|
+
else
|
|
100
|
+
echo -e " ⚠️ lean-ctx not detected (file fallback active)"
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
# === 6. Plugin in opencode.json ===
|
|
104
|
+
echo -e "${CYAN}[PLUGIN]${NC} Checking plugin configuration..."
|
|
105
|
+
if [ -f "$OPENCODE_JSON" ]; then
|
|
106
|
+
if grep -q "@ikieaneh/opencode-kit" "$OPENCODE_JSON" 2>/dev/null; then
|
|
107
|
+
echo -e " ✅ Plugin registered in opencode.json"
|
|
108
|
+
else
|
|
109
|
+
echo -e " ${YELLOW}⚠️ Plugin not found in opencode.json — add to your plugin array${NC}"
|
|
110
|
+
fi
|
|
111
|
+
else
|
|
112
|
+
echo -e " ${YELLOW}⚠️ No opencode.json found${NC}"
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
# === Summary ===
|
|
116
|
+
echo ""
|
|
117
|
+
if [ "$ISSUES" -eq 0 ]; then
|
|
118
|
+
echo -e "${GREEN}✅ All checks passed. System healthy.${NC}"
|
|
119
|
+
exit 0
|
|
120
|
+
else
|
|
121
|
+
echo -e "${RED}❌ $ISSUES issue(s) found. Review warnings above.${NC}"
|
|
122
|
+
exit 1
|
|
123
|
+
fi
|
package/src/init.sh
CHANGED
|
@@ -138,6 +138,22 @@ if [ "$PLUGIN_MODE" = false ]; then
|
|
|
138
138
|
chmod +x .opencode/src/telemetry.sh
|
|
139
139
|
echo " ✅ telemetry.sh (executable)"
|
|
140
140
|
|
|
141
|
+
cp "$KIT_DIR/src/doctor.sh" .opencode/src/doctor.sh
|
|
142
|
+
chmod +x .opencode/src/doctor.sh
|
|
143
|
+
echo " ✅ doctor.sh (executable)"
|
|
144
|
+
|
|
145
|
+
cp "$KIT_DIR/src/status.sh" .opencode/src/status.sh
|
|
146
|
+
chmod +x .opencode/src/status.sh
|
|
147
|
+
echo " ✅ status.sh (executable)"
|
|
148
|
+
|
|
149
|
+
cp "$KIT_DIR/src/new-skill.sh" .opencode/src/new-skill.sh
|
|
150
|
+
chmod +x .opencode/src/new-skill.sh
|
|
151
|
+
echo " ✅ new-skill.sh (executable)"
|
|
152
|
+
|
|
153
|
+
cp "$KIT_DIR/src/analytics.sh" .opencode/src/analytics.sh
|
|
154
|
+
chmod +x .opencode/src/analytics.sh
|
|
155
|
+
echo " ✅ analytics.sh (executable)"
|
|
156
|
+
|
|
141
157
|
# --- Copy agent templates (pre-flight gates) ---
|
|
142
158
|
for agent in orchestrator planner task-manager code-reviewer learner fixer; do
|
|
143
159
|
if [ -f "$KIT_DIR/templates/agents/$agent.md" ]; then
|
package/src/new-skill.sh
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# opencode-kit new skill — scaffold a new skill SKILL.md
|
|
3
|
+
# Usage: bash src/new-skill.sh <skill-name> [description]
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
RED='\033[0;31m'
|
|
7
|
+
GREEN='\033[0;32m'
|
|
8
|
+
CYAN='\033[0;36m'
|
|
9
|
+
NC='\033[0m'
|
|
10
|
+
|
|
11
|
+
NAME="${1:-}"
|
|
12
|
+
DESC="${2:-A custom skill for this project}"
|
|
13
|
+
SKILLS_DIR=".opencode/skills"
|
|
14
|
+
|
|
15
|
+
if [ -z "$NAME" ]; then
|
|
16
|
+
echo -e "${RED}Usage: bash src/new-skill.sh <skill-name> [description]${NC}"
|
|
17
|
+
echo " Example: bash src/new-skill.sh python-conventions \"Python/Django conventions\""
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
SKILL_PATH="$SKILLS_DIR/$NAME"
|
|
22
|
+
SKILL_FILE="$SKILL_PATH/SKILL.md"
|
|
23
|
+
|
|
24
|
+
if [ -d "$SKILL_PATH" ]; then
|
|
25
|
+
echo -e "${RED}❌ Skill already exists: $SKILL_PATH${NC}"
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
mkdir -p "$SKILL_PATH"
|
|
30
|
+
|
|
31
|
+
cat > "$SKILL_FILE" << SKILLEOF
|
|
32
|
+
---
|
|
33
|
+
description: $DESC
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
# $NAME
|
|
37
|
+
|
|
38
|
+
## Conventions
|
|
39
|
+
|
|
40
|
+
...
|
|
41
|
+
|
|
42
|
+
## Commands
|
|
43
|
+
|
|
44
|
+
| Action | Command |
|
|
45
|
+
|--------|---------|
|
|
46
|
+
| Test | ... |
|
|
47
|
+
| Build | ... |
|
|
48
|
+
| Format | ... |
|
|
49
|
+
|
|
50
|
+
## Rules
|
|
51
|
+
|
|
52
|
+
...
|
|
53
|
+
SKILLEOF
|
|
54
|
+
|
|
55
|
+
echo -e "${GREEN}✅ Skill created: $SKILL_FILE${NC}"
|
|
56
|
+
echo ""
|
|
57
|
+
echo " To use it, add to opencode.json:"
|
|
58
|
+
echo ' "skills": ["'$NAME'", "orchestration-template"]'
|
|
59
|
+
echo ""
|
|
60
|
+
echo " Or load it ad-hoc with: /skill $NAME"
|
package/src/status.sh
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
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
|
+
. "$SCRIPT_DIR/platform.sh"
|
|
8
|
+
|
|
9
|
+
CONTRACT_FILE=".opencode/orchestration/contract.json"
|
|
10
|
+
RULES_FILE=".opencode/rules/rules.json"
|
|
11
|
+
TELEMETRY_DIR=".opencode/telemetry"
|
|
12
|
+
|
|
13
|
+
RED='\033[0;31m'
|
|
14
|
+
GREEN='\033[0;32m'
|
|
15
|
+
YELLOW='\033[1;33m'
|
|
16
|
+
CYAN='\033[0;36m'
|
|
17
|
+
BOLD='\033[1m'
|
|
18
|
+
NC='\033[0m'
|
|
19
|
+
|
|
20
|
+
echo ""
|
|
21
|
+
echo -e "${CYAN}${BOLD}╔══════════════════════════════════════════╗${NC}"
|
|
22
|
+
echo -e "${CYAN}${BOLD}║ opencode-kit Dashboard ║${NC}"
|
|
23
|
+
echo -e "${CYAN}${BOLD}╚══════════════════════════════════════════╝${NC}"
|
|
24
|
+
echo ""
|
|
25
|
+
|
|
26
|
+
# === Contract State ===
|
|
27
|
+
if [ -f "$CONTRACT_FILE" ] && [ -n "$PYTHON_CMD" ]; then
|
|
28
|
+
$PYTHON_CMD -c "
|
|
29
|
+
import json
|
|
30
|
+
with open('$CONTRACT_FILE') as f:
|
|
31
|
+
c = json.load(f)
|
|
32
|
+
|
|
33
|
+
state = c.get('state', 'UNKNOWN')
|
|
34
|
+
ver = c.get('contract_version', '?')
|
|
35
|
+
goal = c.get('requirements', {}).get('goal', 'Not set')
|
|
36
|
+
phases = c.get('metrics', {}).get('phases_completed', [])
|
|
37
|
+
score = c.get('score', {}).get('combined', 0)
|
|
38
|
+
verdict = c.get('score', {}).get('verdict', 'PENDING')
|
|
39
|
+
adrs = len(c.get('decisions', {}).get('adr_log', []))
|
|
40
|
+
ext_skills = c.get('governance', {}).get('extension_skills', [])
|
|
41
|
+
|
|
42
|
+
# State color
|
|
43
|
+
state_colors = {
|
|
44
|
+
'INIT': '\033[0;36m',
|
|
45
|
+
'PLAN': '\033[1;33m',
|
|
46
|
+
'PLAN_SCORED': '\033[1;33m',
|
|
47
|
+
'EXECUTE': '\033[0;32m',
|
|
48
|
+
'EXECUTE_SCORED': '\033[0;32m',
|
|
49
|
+
'REVIEW': '\033[0;34m',
|
|
50
|
+
'REVIEW_SCORED': '\033[0;34m',
|
|
51
|
+
'COMPLETE': '\033[0;32m',
|
|
52
|
+
'BLOCKED': '\033[0;31m',
|
|
53
|
+
}
|
|
54
|
+
color = state_colors.get(state, '\033[0m')
|
|
55
|
+
nc = '\033[0m'
|
|
56
|
+
|
|
57
|
+
print(f' ${BOLD}Contract State:${NC} {color}{state}{nc} (v{ver})')
|
|
58
|
+
print(f' ${BOLD}Goal:${NC} {goal[:70]}...' if len(goal) > 70 else f' ${BOLD}Goal:${NC} {goal}')
|
|
59
|
+
print(f' ${BOLD}Score:${NC} {score}/100 ({verdict})')
|
|
60
|
+
print(f' ${BOLD}Phases:${NC} {len(phases)} completed: {\" → \".join(phases[-4:])}')
|
|
61
|
+
print(f' ${BOLD}ADRs:${NC} {adrs} recorded')
|
|
62
|
+
if ext_skills:
|
|
63
|
+
print(f' ${BOLD}Extension Skills:${NC} {\", \".join(ext_skills)}')
|
|
64
|
+
" 2>/dev/null || echo " ⚠️ Could not parse contract"
|
|
65
|
+
else
|
|
66
|
+
echo -e " ${YELLOW}⚠️ No contract found${NC}"
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# === Telemetry ===
|
|
70
|
+
echo ""
|
|
71
|
+
echo -e "${BOLD}⏱ Telemetry${NC}"
|
|
72
|
+
if [ -f "$TELEMETRY_DIR/summary.json" ] && [ -n "$PYTHON_CMD" ]; then
|
|
73
|
+
$PYTHON_CMD -c "
|
|
74
|
+
import json
|
|
75
|
+
with open('$TELEMETRY_DIR/summary.json') as f:
|
|
76
|
+
t = json.load(f)
|
|
77
|
+
total_s = t.get('total_elapsed_s', 0)
|
|
78
|
+
phases = t.get('phases_completed', [])
|
|
79
|
+
print(f' Total time: {total_s}s across {len(phases)} phases')
|
|
80
|
+
" 2>/dev/null
|
|
81
|
+
else
|
|
82
|
+
echo -e " ${YELLOW}No telemetry data yet${NC}"
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# === Rules ===
|
|
86
|
+
echo ""
|
|
87
|
+
echo -e "${BOLD}📋 Rules${NC}"
|
|
88
|
+
if [ -f "$RULES_FILE" ] && [ -n "$PYTHON_CMD" ]; then
|
|
89
|
+
$PYTHON_CMD -c "
|
|
90
|
+
import json
|
|
91
|
+
with open('$RULES_FILE') as f:
|
|
92
|
+
r = json.load(f)
|
|
93
|
+
rules = r.get('rules', [])
|
|
94
|
+
critical = [x for x in rules if x.get('severity') == 'CRITICAL']
|
|
95
|
+
high = [x for x in rules if x.get('severity') == 'HIGH']
|
|
96
|
+
required_mcps = list(r.get('required_mcps', {}).keys())
|
|
97
|
+
required_mcps = [m for m in required_mcps if m != 'description']
|
|
98
|
+
mcps = list(r.get('required_mcps', {}).keys())
|
|
99
|
+
mcps = [m for m in mcps if m != 'description']
|
|
100
|
+
print(f' {len(critical)} CRITICAL rules, {len(high)} HIGH rules')
|
|
101
|
+
if mcps:
|
|
102
|
+
print(f' Required MCPs: {\", \".join(mcps)}')
|
|
103
|
+
" 2>/dev/null
|
|
104
|
+
else
|
|
105
|
+
echo -e " ${YELLOW}No rules.json${NC}"
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
# === Quick actions ===
|
|
109
|
+
echo ""
|
|
110
|
+
echo -e "${BOLD}⚡ Quick Actions${NC}"
|
|
111
|
+
echo " bash .opencode/src/doctor.sh — Run diagnostics"
|
|
112
|
+
echo " bash .opencode/src/telemetry.sh — View telemetry details"
|
|
113
|
+
echo " bash .opencode/src/adr.sh — Record new ADR"
|
|
114
|
+
echo ""
|
package/src/verify.sh
CHANGED
|
@@ -41,7 +41,7 @@ mkdir -p .opencode/telemetry 2>/dev/null
|
|
|
41
41
|
echo " ✅ telemetry directory ready"
|
|
42
42
|
|
|
43
43
|
# --- Check 4: scripts executable ---
|
|
44
|
-
for script in ".opencode/src/preflight.sh" ".opencode/src/postflight.sh" ".opencode/src/telemetry.sh"; do
|
|
44
|
+
for script in ".opencode/src/preflight.sh" ".opencode/src/postflight.sh" ".opencode/src/telemetry.sh" ".opencode/src/doctor.sh" ".opencode/src/status.sh"; do
|
|
45
45
|
if [ -x "$script" ]; then
|
|
46
46
|
echo " ✅ $script (executable)"
|
|
47
47
|
elif [ -f "$script" ]; then
|