@ikieaneh/opencode-kit 0.6.2 → 0.6.3
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 +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
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
# Plugin Architecture — Implementation Plan
|
|
2
|
-
|
|
3
|
-
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development
|
|
4
|
-
|
|
5
|
-
**Goal:** Convert opencode-kit from a per-project scaffold to a global OpenCode plugin with local-override config
|
|
6
|
-
|
|
7
|
-
**Architecture:** Pure JS plugin (superpowers pattern) using `@opencode-ai/plugin` SDK. Global config at `~/.config/opencode-kit/`, project override at `.opencode/`. Plugin registers skills + system prompt transform.
|
|
8
|
-
|
|
9
|
-
**Tech Stack:** Node.js, @opencode-ai/plugin SDK, shell scripts
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
### Task 1: Plugin Entry Point
|
|
14
|
-
|
|
15
|
-
**Files:**
|
|
16
|
-
- Create: `.opencode/plugins/opencode-kit.js` — main plugin JS
|
|
17
|
-
- Create: `.claude-plugin/plugin.json` — metadata
|
|
18
|
-
- Modify: `package.json` — add plugin entry, `@opencode-ai/plugin` dependency
|
|
19
|
-
|
|
20
|
-
- [ ] Create `.opencode/plugins/opencode-kit.js` — system prompt transform hook that injects contract loading + preflight
|
|
21
|
-
- [ ] Create `.claude-plugin/plugin.json` — name, description, version, author
|
|
22
|
-
- [ ] Update `package.json` — `"main": ".opencode/plugins/opencode-kit.js"`, add `@opencode-ai/plugin` dep
|
|
23
|
-
|
|
24
|
-
### Task 2: Global Config Resolution
|
|
25
|
-
|
|
26
|
-
**Files:**
|
|
27
|
-
- Create: `src/global-config.sh` — resolve config from local → global → plugin default
|
|
28
|
-
|
|
29
|
-
- [ ] Write resolution chain: `.opencode/` → `~/.config/opencode-kit/` → plugin `templates/`
|
|
30
|
-
- [ ] Add `init-global` command to copy plugin defaults to `~/.config/opencode-kit/`
|
|
31
|
-
|
|
32
|
-
### Task 3: Plugin Schema
|
|
33
|
-
|
|
34
|
-
**Files:**
|
|
35
|
-
- Create: `templates/opencode-kit.schema.json` — validate opencode.json agent config
|
|
36
|
-
|
|
37
|
-
- [ ] Schema defines default agents (orchestrator, planner, task-manager, etc.)
|
|
38
|
-
- [ ] Documents plugin ordering requirement (must be first in plugin array)
|
|
39
|
-
|
|
40
|
-
### Task 4: Init Coexistence
|
|
41
|
-
|
|
42
|
-
**Files:**
|
|
43
|
-
- Modify: `src/init.sh` — detect plugin, skip plugin-handled tasks
|
|
44
|
-
|
|
45
|
-
- [ ] If plugin detected (via `.opencode/plugins/opencode-kit.js`), skip skill/agent scaffolding
|
|
46
|
-
- [ ] Only scaffold per-project data: contract.json goal/scope, STATE.md, PROJECT.md
|
|
47
|
-
|
|
48
|
-
### Task 5: Documentation
|
|
49
|
-
|
|
50
|
-
**Files:**
|
|
51
|
-
- Modify: `README.md` — plugin installation, ordering requirement
|
|
52
|
-
- Modify: `CHANGELOG.md`
|
|
53
|
-
|
|
54
|
-
- [ ] Add "Install as plugin" section to README
|
|
55
|
-
- [ ] Document global vs local resolution
|
package/src/adr.sh
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# opencode-kit ADR — auto-generate Architecture Decision Record
|
|
3
|
-
# Usage: bash src/adr.sh <title>
|
|
4
|
-
# Then enter context, decision, alternatives, consequences interactively.
|
|
5
|
-
set -euo pipefail
|
|
6
|
-
|
|
7
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
-
# shellcheck source=./platform.sh
|
|
9
|
-
. "$SCRIPT_DIR/platform.sh"
|
|
10
|
-
|
|
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
|
-
# --- Check contract exists ---
|
|
20
|
-
if [ ! -f "$CONTRACT_FILE" ]; then
|
|
21
|
-
echo -e "${RED}❌ $CONTRACT_FILE not found. Run 'opencode-kit init' first.${NC}"
|
|
22
|
-
exit 1
|
|
23
|
-
fi
|
|
24
|
-
|
|
25
|
-
# --- Parse args ---
|
|
26
|
-
TITLE=""
|
|
27
|
-
CONTEXT=""
|
|
28
|
-
DECISION=""
|
|
29
|
-
ALTERNATIVES=""
|
|
30
|
-
CONSEQUENCES=""
|
|
31
|
-
|
|
32
|
-
while [ $# -gt 0 ]; do
|
|
33
|
-
case "$1" in
|
|
34
|
-
--title|-t) TITLE="$2"; shift 2 ;;
|
|
35
|
-
--context|-c) CONTEXT="$2"; shift 2 ;;
|
|
36
|
-
--decision|-d) DECISION="$2"; shift 2 ;;
|
|
37
|
-
--alternatives|-a) ALTERNATIVES="$2"; shift 2 ;;
|
|
38
|
-
--consequences|-q) CONSEQUENCES="$2"; shift 2 ;;
|
|
39
|
-
*) TITLE="$1"; shift ;;
|
|
40
|
-
esac
|
|
41
|
-
done
|
|
42
|
-
|
|
43
|
-
# --- Interactive mode if no title provided ---
|
|
44
|
-
if [ -z "$TITLE" ]; then
|
|
45
|
-
echo -e "${CYAN}[opencode-kit ADR]${NC} Interactive mode"
|
|
46
|
-
echo ""
|
|
47
|
-
read -r -p "Title: " TITLE
|
|
48
|
-
[ -z "$TITLE" ] && { echo -e "${RED}Title required${NC}"; exit 1; }
|
|
49
|
-
read -r -p "Context (why this decision?): " CONTEXT
|
|
50
|
-
read -r -p "Decision (what we decided): " DECISION
|
|
51
|
-
read -r -p "Alternatives considered: " ALTERNATIVES
|
|
52
|
-
read -r -p "Consequences (positive + negative): " CONSEQUENCES
|
|
53
|
-
fi
|
|
54
|
-
|
|
55
|
-
# --- Validate required fields ---
|
|
56
|
-
if [ -z "$TITLE" ]; then
|
|
57
|
-
echo -e "${RED}❌ Title is required. Use: bash src/adr.sh \"Your Decision Title\"${NC}"
|
|
58
|
-
exit 1
|
|
59
|
-
fi
|
|
60
|
-
|
|
61
|
-
# --- Compute next ADR ID ---
|
|
62
|
-
NEXT_ID=$(CONTRACT_FILE="$CONTRACT_FILE" $PYTHON_CMD -c "
|
|
63
|
-
import json, os
|
|
64
|
-
with open(os.environ['CONTRACT_FILE']) as f: d = json.load(f)
|
|
65
|
-
log = d.get('decisions', {}).get('adr_log', [])
|
|
66
|
-
if not log:
|
|
67
|
-
print('ADR-001')
|
|
68
|
-
else:
|
|
69
|
-
last = 0
|
|
70
|
-
for entry in log:
|
|
71
|
-
try:
|
|
72
|
-
num = int(entry.get('id','ADR-000').replace('ADR-',''))
|
|
73
|
-
if num > last: last = num
|
|
74
|
-
except ValueError:
|
|
75
|
-
continue
|
|
76
|
-
print(f'ADR-{last+1:03d}')
|
|
77
|
-
")
|
|
78
|
-
|
|
79
|
-
# --- Check for duplicate title ---
|
|
80
|
-
DUP=$(CONTRACT_FILE="$CONTRACT_FILE" TITLE="$TITLE" $PYTHON_CMD -c "
|
|
81
|
-
import json, os
|
|
82
|
-
with open(os.environ['CONTRACT_FILE']) as f: d = json.load(f)
|
|
83
|
-
log = d.get('decisions', {}).get('adr_log', [])
|
|
84
|
-
search_title = os.environ.get('TITLE', '').lower().strip()
|
|
85
|
-
for entry in log:
|
|
86
|
-
if entry.get('title','').lower().strip() == search_title:
|
|
87
|
-
print(entry.get('id',''))
|
|
88
|
-
break
|
|
89
|
-
" 2>/dev/null)
|
|
90
|
-
if [ -n "$DUP" ]; then
|
|
91
|
-
echo -e "${YELLOW}⚠️ Duplicate title found: $DUP — '$TITLE'${NC}"
|
|
92
|
-
echo " Skipping. Update existing ADR instead."
|
|
93
|
-
exit 0
|
|
94
|
-
fi
|
|
95
|
-
|
|
96
|
-
# --- Build ADR entry via Python (avoids shell injection) ---
|
|
97
|
-
ENTRY_FILE=$(mktemp /tmp/opencode-adr-entry-XXXXX.json)
|
|
98
|
-
trap 'rm -f "$ENTRY_FILE"' EXIT INT TERM
|
|
99
|
-
|
|
100
|
-
TITLE="$TITLE" CONTEXT="$CONTEXT" DECISION="$DECISION" ALTERNATIVES="$ALTERNATIVES" CONSEQUENCES="$CONSEQUENCES" NEXT_ID="$NEXT_ID" ENTRY_FILE="$ENTRY_FILE" $PYTHON_CMD -c "
|
|
101
|
-
import json, os
|
|
102
|
-
from datetime import date
|
|
103
|
-
|
|
104
|
-
entry = {
|
|
105
|
-
'id': os.environ['NEXT_ID'],
|
|
106
|
-
'date': date.today().isoformat(),
|
|
107
|
-
'title': os.environ.get('TITLE', ''),
|
|
108
|
-
'context': os.environ.get('CONTEXT', ''),
|
|
109
|
-
'decision': os.environ.get('DECISION', ''),
|
|
110
|
-
'alternatives': os.environ.get('ALTERNATIVES', ''),
|
|
111
|
-
'consequences': os.environ.get('CONSEQUENCES', '')
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
with open(os.environ['ENTRY_FILE'], 'w') as f:
|
|
115
|
-
json.dump(entry, f, indent=2)
|
|
116
|
-
print('Entry written')
|
|
117
|
-
"
|
|
118
|
-
|
|
119
|
-
# --- Inject into contract.json ---
|
|
120
|
-
CONTRACT_FILE="$CONTRACT_FILE" ENTRY_FILE="$ENTRY_FILE" $PYTHON_CMD -c "
|
|
121
|
-
import json, os
|
|
122
|
-
|
|
123
|
-
with open(os.environ['CONTRACT_FILE']) as f:
|
|
124
|
-
contract = json.load(f)
|
|
125
|
-
|
|
126
|
-
with open(os.environ['ENTRY_FILE']) as f:
|
|
127
|
-
entry = json.load(f)
|
|
128
|
-
|
|
129
|
-
if 'decisions' not in contract:
|
|
130
|
-
contract['decisions'] = {}
|
|
131
|
-
if 'adr_log' not in contract['decisions']:
|
|
132
|
-
contract['decisions']['adr_log'] = []
|
|
133
|
-
|
|
134
|
-
contract['decisions']['adr_log'].append(entry)
|
|
135
|
-
|
|
136
|
-
with open(os.environ['CONTRACT_FILE'], 'w') as f:
|
|
137
|
-
json.dump(contract, f, indent=2)
|
|
138
|
-
|
|
139
|
-
print(json.dumps(entry, indent=2))
|
|
140
|
-
"
|
|
141
|
-
|
|
142
|
-
rm -f "$ENTRY_FILE"
|
|
143
|
-
|
|
144
|
-
echo ""
|
|
145
|
-
echo -e "${GREEN}[opencode-kit] ✅ ADR recorded: $NEXT_ID${NC}"
|
|
146
|
-
echo " Title: $TITLE"
|
|
147
|
-
echo " File: $CONTRACT_FILE"
|
|
148
|
-
echo " Next: Persist via: lean-ctx ctx_knowledge remember key orchestration-contract value \"\$(cat $CONTRACT_FILE)\""
|
package/src/analytics.sh
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
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
|
-
# 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:-table}"
|
|
20
|
-
|
|
21
|
-
echo -e "${CYAN}📊 opencode-kit Analytics${NC}"
|
|
22
|
-
echo ""
|
|
23
|
-
|
|
24
|
-
if [ ! -f "$TELEMETRY_DIR/phases.jsonl" ] || [ ! -f "$TELEMETRY_DIR/summary.json" ]; then
|
|
25
|
-
echo -e "${YELLOW}No telemetry data yet. Run a few phases first.${NC}"
|
|
26
|
-
exit 0
|
|
27
|
-
fi
|
|
28
|
-
|
|
29
|
-
if [ -z "$PYTHON_CMD" ]; then
|
|
30
|
-
echo -e "${RED}Python required for analytics.${NC}"
|
|
31
|
-
exit 1
|
|
32
|
-
fi
|
|
33
|
-
|
|
34
|
-
$PYTHON_CMD -c "
|
|
35
|
-
import json
|
|
36
|
-
|
|
37
|
-
# Load phases
|
|
38
|
-
phases = []
|
|
39
|
-
try:
|
|
40
|
-
with open('$TELEMETRY_DIR/phases.jsonl') as f:
|
|
41
|
-
for line in f:
|
|
42
|
-
line = line.strip()
|
|
43
|
-
if line:
|
|
44
|
-
phases.append(json.loads(line))
|
|
45
|
-
except:
|
|
46
|
-
pass
|
|
47
|
-
|
|
48
|
-
# Load summary
|
|
49
|
-
summary = {}
|
|
50
|
-
try:
|
|
51
|
-
with open('$TELEMETRY_DIR/summary.json') as f:
|
|
52
|
-
summary = json.load(f)
|
|
53
|
-
except:
|
|
54
|
-
pass
|
|
55
|
-
|
|
56
|
-
if not phases:
|
|
57
|
-
print(' No phases recorded')
|
|
58
|
-
exit(0)
|
|
59
|
-
|
|
60
|
-
total_ms = sum(p.get('elapsed_ms', 0) for p in phases)
|
|
61
|
-
avg_ms = total_ms / len(phases) if phases else 0
|
|
62
|
-
|
|
63
|
-
print(f' Total sessions: {len(phases)}')
|
|
64
|
-
print(f' Total time: {total_ms/1000:.1f}s ({total_ms/60000:.1f}m)')
|
|
65
|
-
print(f' Avg per phase: {avg_ms/1000:.1f}s')
|
|
66
|
-
print('')
|
|
67
|
-
print(f' Phase Breakdown:')
|
|
68
|
-
for p in phases:
|
|
69
|
-
frm = p.get('from', '?')
|
|
70
|
-
to = p.get('to', '?')
|
|
71
|
-
ms = p.get('elapsed_ms', 0)
|
|
72
|
-
bar = '#' * max(1, int(ms / max(1, total_ms) * 30))
|
|
73
|
-
print(f' {frm:16s} → {to:16s} {ms/1000:6.1f}s {bar}')
|
|
74
|
-
|
|
75
|
-
# Cost estimate (rough: ~$0.15/1M tokens, ~1000 tok/s)
|
|
76
|
-
est_tokens = int(total_ms / 1000 * 1000) # rough: 1000 tok/sec
|
|
77
|
-
est_cost = est_tokens / 1_000_000 * 0.15
|
|
78
|
-
print(f'')
|
|
79
|
-
print(f' Estimated tokens: {est_tokens:,} (~{est_cost:.4f} USD at $0.15/1M tok)')
|
|
80
|
-
" 2>/dev/null || echo " ⚠️ Analytics failed"
|
package/src/diff.sh
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# opencode-kit diff — compare contract state between branches
|
|
3
|
-
# Usage: bash src/diff.sh [branch1] [branch2]
|
|
4
|
-
# Default: compares current branch vs main
|
|
5
|
-
set -euo pipefail
|
|
6
|
-
|
|
7
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
-
# shellcheck source=./platform.sh
|
|
9
|
-
. "$SCRIPT_DIR/platform.sh"
|
|
10
|
-
|
|
11
|
-
CONTRACT_FILE=".opencode/orchestration/contract.json"
|
|
12
|
-
BRANCH_A="${1:-main}"
|
|
13
|
-
BRANCH_B="${2:-HEAD}"
|
|
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
|
-
echo -e "${CYAN}📋 opencode-kit diff: $BRANCH_A ↔ $BRANCH_B${NC}"
|
|
22
|
-
echo ""
|
|
23
|
-
|
|
24
|
-
# Extract contract from a git ref
|
|
25
|
-
get_contract_state() {
|
|
26
|
-
local branch="$1"
|
|
27
|
-
local content
|
|
28
|
-
content=$(git show "$branch:$CONTRACT_FILE" 2>/dev/null || echo "")
|
|
29
|
-
if [ -z "$content" ]; then
|
|
30
|
-
echo ""
|
|
31
|
-
return 1
|
|
32
|
-
fi
|
|
33
|
-
echo "$content"
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
CONTRACT_A=$(get_contract_state "$BRANCH_A")
|
|
37
|
-
CONTRACT_B=$(get_contract_state "$BRANCH_B")
|
|
38
|
-
|
|
39
|
-
if [ -z "$CONTRACT_A" ] && [ -z "$CONTRACT_B" ]; then
|
|
40
|
-
echo -e "${YELLOW}No contract found in either branch.${NC}"
|
|
41
|
-
exit 0
|
|
42
|
-
fi
|
|
43
|
-
|
|
44
|
-
# Show state diff
|
|
45
|
-
# Write contract data to temp files for safe Python consumption
|
|
46
|
-
CONTRACT_A_FILE=$(mktemp /tmp/opencode-diff-a-XXXXXX.json 2>/dev/null || mktemp /tmp/opencode-diff-a-XXXXXX)
|
|
47
|
-
CONTRACT_B_FILE=$(mktemp /tmp/opencode-diff-b-XXXXXX.json 2>/dev/null || mktemp /tmp/opencode-diff-b-XXXXXX)
|
|
48
|
-
trap 'rm -f "$CONTRACT_A_FILE" "$CONTRACT_B_FILE"' EXIT INT TERM
|
|
49
|
-
|
|
50
|
-
echo "$CONTRACT_A" > "$CONTRACT_A_FILE"
|
|
51
|
-
echo "$CONTRACT_B" > "$CONTRACT_B_FILE"
|
|
52
|
-
|
|
53
|
-
if [ -n "$PYTHON_CMD" ]; then
|
|
54
|
-
$PYTHON_CMD -c "
|
|
55
|
-
import json, sys
|
|
56
|
-
|
|
57
|
-
def get_state(filepath):
|
|
58
|
-
try:
|
|
59
|
-
with open(filepath) as f:
|
|
60
|
-
content = f.read().strip()
|
|
61
|
-
if not content:
|
|
62
|
-
return None
|
|
63
|
-
d = json.loads(content)
|
|
64
|
-
return {
|
|
65
|
-
'state': d.get('state', '?'),
|
|
66
|
-
'goal': (d.get('requirements', {}) or {}).get('goal', '?'),
|
|
67
|
-
'score': (d.get('score', {}) or {}).get('combined', '?'),
|
|
68
|
-
'phases': (d.get('metrics', {}) or {}).get('phases_completed', []),
|
|
69
|
-
'adrs': len((d.get('decisions', {}) or {}).get('adr_log', [])),
|
|
70
|
-
'version': d.get('contract_version', '?')
|
|
71
|
-
}
|
|
72
|
-
except:
|
|
73
|
-
return None
|
|
74
|
-
|
|
75
|
-
a = get_state('$CONTRACT_A_FILE')
|
|
76
|
-
b = get_state('$CONTRACT_B_FILE')
|
|
77
|
-
|
|
78
|
-
if a and b:
|
|
79
|
-
print(f' Field $BRANCH_A $BRANCH_B')
|
|
80
|
-
print(f' {\"-\"*50}')
|
|
81
|
-
for field in ['state', 'goal', 'score', 'version']:
|
|
82
|
-
va = str(a.get(field, '?'))[:20]
|
|
83
|
-
vb = str(b.get(field, '?'))[:20]
|
|
84
|
-
marker = ' ←→' if va != vb else ' '
|
|
85
|
-
print(f' {field:20s} {va:20s} {marker} {vb:20s}')
|
|
86
|
-
print(f' phases {len(a.get(\"phases\",[])):3d} completed {len(b.get(\"phases\",[])):3d} completed')
|
|
87
|
-
print(f' ADRs {a.get(\"adrs\",0):3d} recorded {b.get(\"adrs\",0):3d} recorded')
|
|
88
|
-
elif a and not b:
|
|
89
|
-
print(f' Contract exists in $BRANCH_A but NOT in $BRANCH_B')
|
|
90
|
-
print(f' State: {a.get(\"state\",\"?\")}')
|
|
91
|
-
elif b and not a:
|
|
92
|
-
print(f' Contract exists in $BRANCH_B but NOT in $BRANCH_A')
|
|
93
|
-
print(f' State: {b.get(\"state\",\"?\")}')
|
|
94
|
-
" 2>/dev/null || echo -e "${YELLOW}Could not parse contract JSON${NC}"
|
|
95
|
-
|
|
96
|
-
rm -f "$CONTRACT_A_FILE" "$CONTRACT_B_FILE"
|
|
97
|
-
fi
|
|
98
|
-
|
|
99
|
-
# Raw git diff
|
|
100
|
-
echo ""
|
|
101
|
-
echo -e "${CYAN}Raw diff:${NC}"
|
|
102
|
-
if git diff "$BRANCH_A" "$BRANCH_B" -- "$CONTRACT_FILE" 2>/dev/null | head -40; then
|
|
103
|
-
if ! git diff --exit-code "$BRANCH_A" "$BRANCH_B" -- "$CONTRACT_FILE" &>/dev/null; then
|
|
104
|
-
:
|
|
105
|
-
fi
|
|
106
|
-
fi
|
|
107
|
-
echo ""
|
|
108
|
-
echo -e "Run: ${GREEN}bash .opencode/src/diff.sh${NC} (default: main vs HEAD)"
|
|
109
|
-
echo -e " ${GREEN}bash .opencode/src/diff.sh staging feature-x${NC} (custom branches)"
|
package/src/doctor.sh
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
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
|
-
# shellcheck source=./platform.sh
|
|
9
|
-
. "$SCRIPT_DIR/platform.sh"
|
|
10
|
-
. "$SCRIPT_DIR/global-config.sh"
|
|
11
|
-
|
|
12
|
-
RULES_FILE=".opencode/rules/rules.json"
|
|
13
|
-
CONTRACT_FILE=".opencode/orchestration/contract.json"
|
|
14
|
-
OPENCODE_JSON="opencode.json"
|
|
15
|
-
|
|
16
|
-
RED='\033[0;31m'
|
|
17
|
-
GREEN='\033[0;32m'
|
|
18
|
-
YELLOW='\033[1;33m'
|
|
19
|
-
CYAN='\033[0;36m'
|
|
20
|
-
NC='\033[0m'
|
|
21
|
-
|
|
22
|
-
ISSUES=0
|
|
23
|
-
mode="${1:-}"
|
|
24
|
-
|
|
25
|
-
echo -e "${CYAN}🔍 opencode-kit doctor${NC}"
|
|
26
|
-
echo ""
|
|
27
|
-
|
|
28
|
-
# === 1. Contract check ===
|
|
29
|
-
echo -e "${CYAN}[CONTRACT]${NC} Checking orchestration contract..."
|
|
30
|
-
if [ ! -f "$CONTRACT_FILE" ]; then
|
|
31
|
-
echo -e " ${RED}❌ contract.json not found — run 'opencode-kit init'${NC}"
|
|
32
|
-
ISSUES=$((ISSUES + 1))
|
|
33
|
-
else
|
|
34
|
-
if [ -n "$PYTHON_CMD" ]; then
|
|
35
|
-
STATE=$($PYTHON_CMD -c "import json; d=json.load(open('$CONTRACT_FILE')); print(d.get('state','?'))" 2>/dev/null || echo "parse_error")
|
|
36
|
-
if [ "$STATE" = "parse_error" ]; then
|
|
37
|
-
echo -e " ${RED}❌ contract.json is malformed JSON${NC}"
|
|
38
|
-
ISSUES=$((ISSUES + 1))
|
|
39
|
-
else
|
|
40
|
-
echo -e " ✅ State: $STATE"
|
|
41
|
-
fi
|
|
42
|
-
fi
|
|
43
|
-
fi
|
|
44
|
-
|
|
45
|
-
# === 1b. Contract schema validation ===
|
|
46
|
-
SCHEMA_FILE=".opencode/templates/contract.schema.json"
|
|
47
|
-
if [ -f "$CONTRACT_FILE" ] && [ -f "$SCHEMA_FILE" ] && [ -n "$PYTHON_CMD" ]; then
|
|
48
|
-
if $PYTHON_CMD -c "
|
|
49
|
-
import json, sys
|
|
50
|
-
with open('$CONTRACT_FILE') as f: contract = json.load(f)
|
|
51
|
-
with open('$SCHEMA_FILE') as f: schema = json.load(f)
|
|
52
|
-
# Basic required-field validation
|
|
53
|
-
errors = []
|
|
54
|
-
if not contract.get('state'): errors.append('Missing state')
|
|
55
|
-
if not contract.get('requirements', {}).get('goal'): errors.append('Missing requirements.goal')
|
|
56
|
-
if contract.get('state') not in ['INIT','PLAN','PLAN_SCORED','EXECUTE','EXECUTE_SCORED','REVIEW','REVIEW_SCORED','COMPLETE','BLOCKED']:
|
|
57
|
-
errors.append('Invalid state')
|
|
58
|
-
if errors:
|
|
59
|
-
print('; '.join(errors))
|
|
60
|
-
sys.exit(1)
|
|
61
|
-
else:
|
|
62
|
-
print('OK')
|
|
63
|
-
" 2>/dev/null; then
|
|
64
|
-
echo -e " ✅ Contract schema valid"
|
|
65
|
-
else
|
|
66
|
-
echo -e " ⚠️ Contract schema issues found (non-blocking)"
|
|
67
|
-
ISSUES=$((ISSUES + 1))
|
|
68
|
-
fi
|
|
69
|
-
fi
|
|
70
|
-
|
|
71
|
-
# === 2. Rules check ===
|
|
72
|
-
echo -e "${CYAN}[RULES]${NC} Checking rules.json..."
|
|
73
|
-
if [ ! -f "$RULES_FILE" ]; then
|
|
74
|
-
echo -e " ${RED}❌ rules.json not found${NC}"
|
|
75
|
-
ISSUES=$((ISSUES + 1))
|
|
76
|
-
else
|
|
77
|
-
if [ -n "$PYTHON_CMD" ]; then
|
|
78
|
-
RULE_COUNT=$($PYTHON_CMD -c "import json; d=json.load(open('$RULES_FILE')); print(len(d.get('rules',[])))" 2>/dev/null || echo "0")
|
|
79
|
-
echo -e " ✅ $RULE_COUNT rules loaded"
|
|
80
|
-
fi
|
|
81
|
-
fi
|
|
82
|
-
|
|
83
|
-
# === 3a. MCP CLI checks ===
|
|
84
|
-
echo -e "${CYAN}[MCP]${NC} Checking required MCPs..."
|
|
85
|
-
if [ -f "$RULES_FILE" ] && [ -n "$PYTHON_CMD" ]; then
|
|
86
|
-
$PYTHON_CMD -c "
|
|
87
|
-
import json, subprocess, sys
|
|
88
|
-
with open('$RULES_FILE') as f:
|
|
89
|
-
rules = json.load(f)
|
|
90
|
-
mcps = rules.get('required_mcps', {})
|
|
91
|
-
mcps.pop('description', None)
|
|
92
|
-
for name, cfg in mcps.items():
|
|
93
|
-
cli = cfg.get('check_cli', '')
|
|
94
|
-
severity = cfg.get('severity', 'optional')
|
|
95
|
-
result = subprocess.run(cli, shell=True, capture_output=True, timeout=5)
|
|
96
|
-
ok = result.returncode == 0
|
|
97
|
-
if ok:
|
|
98
|
-
print(f' ✅ {name}: available')
|
|
99
|
-
elif severity == 'required':
|
|
100
|
-
print(f' ❌ {name}: MISSING (required)')
|
|
101
|
-
sys.exit(1)
|
|
102
|
-
else:
|
|
103
|
-
print(f' ⚠️ {name}: not detected (optional)')
|
|
104
|
-
" 2>/dev/null || ISSUES=$((ISSUES + 1))
|
|
105
|
-
fi
|
|
106
|
-
|
|
107
|
-
# === 3b. MCP connectivity ===
|
|
108
|
-
echo -e "${CYAN}[MCP_CONNECT]${NC} Testing MCP connectivity..."
|
|
109
|
-
if command -v lean-ctx &>/dev/null; then
|
|
110
|
-
if lean-ctx ctx_knowledge status &>/dev/null 2>&1; then
|
|
111
|
-
echo -e " ✅ lean-ctx MCP: responding"
|
|
112
|
-
else
|
|
113
|
-
echo -e " ⚠️ lean-ctx CLI found but MCP not responding (expected in CI)"
|
|
114
|
-
fi
|
|
115
|
-
fi
|
|
116
|
-
|
|
117
|
-
# === 4. Git branch ===
|
|
118
|
-
echo -e "${CYAN}[GIT]${NC} Checking branch..."
|
|
119
|
-
BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
|
|
120
|
-
if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
|
|
121
|
-
echo -e " ${YELLOW}⚠️ On '$BRANCH' — create a feature branch for development${NC}"
|
|
122
|
-
else
|
|
123
|
-
echo -e " ✅ Branch: $BRANCH"
|
|
124
|
-
fi
|
|
125
|
-
|
|
126
|
-
# === 5. Lean-ctx persistence ===
|
|
127
|
-
echo -e "${CYAN}[PERSIST]${NC} Checking persistence..."
|
|
128
|
-
if command -v lean-ctx &>/dev/null; then
|
|
129
|
-
echo -e " ✅ lean-ctx CLI available"
|
|
130
|
-
LEAN_OK=$(lean-ctx ctx_knowledge recall --query "orchestration-contract" &>/dev/null && echo "yes" || echo "no")
|
|
131
|
-
if [ "$LEAN_OK" = "yes" ]; then
|
|
132
|
-
echo -e " ✅ Contract found in lean-ctx"
|
|
133
|
-
else
|
|
134
|
-
echo -e " ⚠️ Contract not in lean-ctx (file fallback active)"
|
|
135
|
-
fi
|
|
136
|
-
else
|
|
137
|
-
echo -e " ⚠️ lean-ctx not detected (file fallback active)"
|
|
138
|
-
fi
|
|
139
|
-
|
|
140
|
-
# === 6. Plugin in opencode.json ===
|
|
141
|
-
echo -e "${CYAN}[PLUGIN]${NC} Checking plugin configuration..."
|
|
142
|
-
if [ -f "$OPENCODE_JSON" ]; then
|
|
143
|
-
if grep -q "@ikieaneh/opencode-kit" "$OPENCODE_JSON" 2>/dev/null; then
|
|
144
|
-
echo -e " ✅ Plugin registered in opencode.json"
|
|
145
|
-
else
|
|
146
|
-
echo -e " ${YELLOW}⚠️ Plugin not found in opencode.json — add to your plugin array${NC}"
|
|
147
|
-
fi
|
|
148
|
-
else
|
|
149
|
-
echo -e " ${YELLOW}⚠️ No opencode.json found${NC}"
|
|
150
|
-
fi
|
|
151
|
-
|
|
152
|
-
# === Summary ===
|
|
153
|
-
echo ""
|
|
154
|
-
if [ "$ISSUES" -eq 0 ]; then
|
|
155
|
-
echo -e "${GREEN}✅ All checks passed. System healthy.${NC}"
|
|
156
|
-
exit 0
|
|
157
|
-
else
|
|
158
|
-
echo -e "${RED}❌ $ISSUES issue(s) found. Review warnings above.${NC}"
|
|
159
|
-
exit 1
|
|
160
|
-
fi
|
package/src/global-config.sh
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# opencode-kit global-config — resolve config from local → global → plugin default
|
|
3
|
-
# Usage: source src/global-config.sh && resolve_config "contract.json"
|
|
4
|
-
set -euo pipefail
|
|
5
|
-
|
|
6
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
7
|
-
# shellcheck source=./platform.sh
|
|
8
|
-
. "$SCRIPT_DIR/platform.sh"
|
|
9
|
-
|
|
10
|
-
PLUGIN_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
11
|
-
GLOBAL_CONFIG_DIR="$HOME/.config/opencode-kit"
|
|
12
|
-
|
|
13
|
-
# Resolve a config file from the lookup chain:
|
|
14
|
-
# 1. .opencode/<path> (project override)
|
|
15
|
-
# 2. ~/.config/opencode-kit/<path> (global defaults)
|
|
16
|
-
# 3. <plugin>/<path> (plugin defaults)
|
|
17
|
-
# Returns: path to the first file found, or empty string if none found
|
|
18
|
-
resolve_config() {
|
|
19
|
-
local rel_path="$1"
|
|
20
|
-
|
|
21
|
-
# 1. Project override
|
|
22
|
-
if [ -f ".opencode/$rel_path" ]; then
|
|
23
|
-
echo ".opencode/$rel_path"
|
|
24
|
-
return 0
|
|
25
|
-
fi
|
|
26
|
-
|
|
27
|
-
# 2. Global defaults
|
|
28
|
-
if [ -f "$GLOBAL_CONFIG_DIR/$rel_path" ]; then
|
|
29
|
-
echo "$GLOBAL_CONFIG_DIR/$rel_path"
|
|
30
|
-
return 0
|
|
31
|
-
fi
|
|
32
|
-
|
|
33
|
-
# 3. Plugin defaults (templates/)
|
|
34
|
-
if [ -f "$PLUGIN_ROOT/templates/$rel_path" ]; then
|
|
35
|
-
echo "$PLUGIN_ROOT/templates/$rel_path"
|
|
36
|
-
return 0
|
|
37
|
-
fi
|
|
38
|
-
|
|
39
|
-
# 4. Plugin defaults (root/)
|
|
40
|
-
if [ -f "$PLUGIN_ROOT/$rel_path" ]; then
|
|
41
|
-
echo "$PLUGIN_ROOT/$rel_path"
|
|
42
|
-
return 0
|
|
43
|
-
fi
|
|
44
|
-
|
|
45
|
-
echo ""
|
|
46
|
-
return 1
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
# Initialize global config from plugin defaults
|
|
50
|
-
# Copies templates/* and rules/* to ~/.config/opencode-kit/
|
|
51
|
-
init_global_config() {
|
|
52
|
-
echo "[opencode-kit] Initializing global config at $GLOBAL_CONFIG_DIR"
|
|
53
|
-
|
|
54
|
-
mkdir -p "$GLOBAL_CONFIG_DIR/orchestration" "$GLOBAL_CONFIG_DIR/rules"
|
|
55
|
-
|
|
56
|
-
# Copy contract template
|
|
57
|
-
if [ ! -f "$GLOBAL_CONFIG_DIR/orchestration/contract.json" ]; then
|
|
58
|
-
cp "$PLUGIN_ROOT/templates/contract.json" "$GLOBAL_CONFIG_DIR/orchestration/contract.json"
|
|
59
|
-
echo " ✅ contract.json → global config"
|
|
60
|
-
fi
|
|
61
|
-
|
|
62
|
-
# Copy rules
|
|
63
|
-
if [ ! -f "$GLOBAL_CONFIG_DIR/rules/rules.json" ]; then
|
|
64
|
-
cp "$PLUGIN_ROOT/rules/rules.json" "$GLOBAL_CONFIG_DIR/rules/rules.json"
|
|
65
|
-
echo " ✅ rules.json → global config"
|
|
66
|
-
fi
|
|
67
|
-
|
|
68
|
-
echo "[opencode-kit] ✅ Global config initialized"
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
# Detect if the opencode-kit plugin is active
|
|
72
|
-
is_plugin_active() {
|
|
73
|
-
# Check if .opencode/plugins/opencode-kit.js exists (sign of plugin mode)
|
|
74
|
-
[ -f ".opencode/plugins/opencode-kit.js" ] && return 0
|
|
75
|
-
# Check if ~/.config/opencode-kit exists (sign of global config)
|
|
76
|
-
[ -d "$GLOBAL_CONFIG_DIR" ] && return 0
|
|
77
|
-
return 1
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
# Export functions
|
|
81
|
-
export -f resolve_config init_global_config is_plugin_active
|
|
82
|
-
export GLOBAL_CONFIG_DIR
|
package/src/new-skill.sh
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
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"
|