@ikieaneh/opencode-kit 0.5.5 → 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/package.json +1 -1
- package/src/analytics.sh +79 -0
- package/src/init.sh +12 -0
- package/src/new-skill.sh +60 -0
- package/src/status.sh +114 -0
- package/src/verify.sh +1 -1
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/init.sh
CHANGED
|
@@ -142,6 +142,18 @@ if [ "$PLUGIN_MODE" = false ]; then
|
|
|
142
142
|
chmod +x .opencode/src/doctor.sh
|
|
143
143
|
echo " ✅ doctor.sh (executable)"
|
|
144
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
|
+
|
|
145
157
|
# --- Copy agent templates (pre-flight gates) ---
|
|
146
158
|
for agent in orchestrator planner task-manager code-reviewer learner fixer; do
|
|
147
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
|