@ikieaneh/opencode-kit 0.6.1 → 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/.opencode/plugins/opencode-kit.js +101 -84
- package/package.json +9 -7
- package/src/init.sh +2 -2
- 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 -152
- package/src/analytics.sh +0 -80
- package/src/diff.sh +0 -95
- 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 -67
- package/src/update.sh +0 -181
- package/src/verify.sh +0 -67
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"
|
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."
|