@ikieaneh/opencode-kit 0.5.0

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/src/adr.sh ADDED
@@ -0,0 +1,139 @@
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
+ . "$SCRIPT_DIR/platform.sh"
9
+
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
+ # --- Check contract exists ---
19
+ if [ ! -f "$CONTRACT_FILE" ]; then
20
+ echo -e "${RED}❌ $CONTRACT_FILE not found. Run 'opencode-kit init' first.${NC}"
21
+ exit 1
22
+ fi
23
+
24
+ # --- Parse args ---
25
+ TITLE=""
26
+ CONTEXT=""
27
+ DECISION=""
28
+ ALTERNATIVES=""
29
+ CONSEQUENCES=""
30
+
31
+ while [ $# -gt 0 ]; do
32
+ case "$1" in
33
+ --title|-t) TITLE="$2"; shift 2 ;;
34
+ --context|-c) CONTEXT="$2"; shift 2 ;;
35
+ --decision|-d) DECISION="$2"; shift 2 ;;
36
+ --alternatives|-a) ALTERNATIVES="$2"; shift 2 ;;
37
+ --consequences|-q) CONSEQUENCES="$2"; shift 2 ;;
38
+ *) TITLE="$1"; shift ;;
39
+ esac
40
+ done
41
+
42
+ # --- Interactive mode if no title provided ---
43
+ if [ -z "$TITLE" ]; then
44
+ echo -e "${CYAN}[opencode-kit ADR]${NC} Interactive mode"
45
+ echo ""
46
+ read -r -p "Title: " TITLE
47
+ [ -z "$TITLE" ] && { echo -e "${RED}Title required${NC}"; exit 1; }
48
+ read -r -p "Context (why this decision?): " CONTEXT
49
+ read -r -p "Decision (what we decided): " DECISION
50
+ read -r -p "Alternatives considered: " ALTERNATIVES
51
+ read -r -p "Consequences (positive + negative): " CONSEQUENCES
52
+ fi
53
+
54
+ # --- Validate required fields ---
55
+ if [ -z "$TITLE" ]; then
56
+ echo -e "${RED}❌ Title is required. Use: bash src/adr.sh \"Your Decision Title\"${NC}"
57
+ exit 1
58
+ fi
59
+
60
+ # --- Compute next ADR ID ---
61
+ NEXT_ID=$($PYTHON_CMD -c "
62
+ import json
63
+ with open('$CONTRACT_FILE') as f: d = json.load(f)
64
+ log = d.get('decisions', {}).get('adr_log', [])
65
+ if not log:
66
+ print('ADR-001')
67
+ else:
68
+ last = max(int(entry.get('id','ADR-000').replace('ADR-','')) for entry in log)
69
+ print(f'ADR-{last+1:03d}')
70
+ ")
71
+
72
+ # --- Check for duplicate title ---
73
+ DUP=$($PYTHON_CMD -c "
74
+ import json
75
+ with open('$CONTRACT_FILE') as f: d = json.load(f)
76
+ log = d.get('decisions', {}).get('adr_log', [])
77
+ for entry in log:
78
+ if entry.get('title','').lower().strip() == '$TITLE'.lower().strip():
79
+ print(entry.get('id',''))
80
+ break
81
+ " 2>/dev/null)
82
+ if [ -n "$DUP" ]; then
83
+ echo -e "${YELLOW}⚠️ Duplicate title found: $DUP — '$TITLE'${NC}"
84
+ echo " Skipping. Update existing ADR instead."
85
+ exit 0
86
+ fi
87
+
88
+ # --- Build ADR entry ---
89
+ # --- Build ADR entry via heredoc to avoid nested quote issues ---
90
+ $PYTHON_CMD -c "
91
+ import json, sys, os
92
+
93
+ title = '$TITLE'
94
+ date_val = '$(date +%Y-%m-%d)'
95
+ next_id = '$NEXT_ID'
96
+
97
+ entry = {
98
+ 'id': next_id,
99
+ 'date': date_val,
100
+ 'title': title,
101
+ 'context': '''$(echo "$CONTEXT" | sed "s/'/\\\\'/g")''',
102
+ 'decision': '''$(echo "$DECISION" | sed "s/'/\\\\'/g")''',
103
+ 'alternatives': '''$(echo "$ALTERNATIVES" | sed "s/'/\\\\'/g")''',
104
+ 'consequences': '''$(echo "$CONSEQUENCES" | sed "s/'/\\\\'/g")'''
105
+ }
106
+
107
+ with open('/tmp/opencode-adr-entry.json', 'w') as f:
108
+ json.dump(entry, f, indent=2)
109
+ print('Entry written')
110
+ "
111
+
112
+ # --- Inject into contract.json ---
113
+ $PYTHON_CMD -c "
114
+ import json
115
+
116
+ with open('$CONTRACT_FILE') as f:
117
+ contract = json.load(f)
118
+
119
+ with open('/tmp/opencode-adr-entry.json') as f:
120
+ entry = json.load(f)
121
+
122
+ if 'decisions' not in contract:
123
+ contract['decisions'] = {}
124
+ if 'adr_log' not in contract['decisions']:
125
+ contract['decisions']['adr_log'] = []
126
+
127
+ contract['decisions']['adr_log'].append(entry)
128
+
129
+ with open('$CONTRACT_FILE', 'w') as f:
130
+ json.dump(contract, f, indent=2)
131
+
132
+ print(json.dumps(entry, indent=2))
133
+ "
134
+
135
+ echo ""
136
+ echo -e "${GREEN}[opencode-kit] ✅ ADR recorded: $NEXT_ID${NC}"
137
+ echo " Title: $TITLE"
138
+ echo " File: $CONTRACT_FILE"
139
+ echo " Next: Persist via: lean-ctx ctx_knowledge remember key orchestration-contract value \"\$(cat $CONTRACT_FILE)\""
package/src/cli.js ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * opencode-kit CLI — version and help
4
+ * Usage: npx opencode-kit --version
5
+ * npx opencode-kit --help
6
+ */
7
+ import { fileURLToPath } from 'url';
8
+ import path from 'path';
9
+ import fs from 'fs';
10
+
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
+ const pkgPath = path.resolve(__dirname, '../package.json');
13
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
14
+
15
+ const args = process.argv.slice(2);
16
+
17
+ if (args.includes('--version') || args.includes('-v')) {
18
+ console.log(pkg.version);
19
+ process.exit(0);
20
+ }
21
+
22
+ if (args.includes('--help') || args.includes('-h') || args.length === 0) {
23
+ console.log(`
24
+ opencode-kit v${pkg.version}
25
+
26
+ Usage:
27
+ npx opencode-kit [command]
28
+
29
+ Commands:
30
+ init [--force] Scaffold orchestration framework into project
31
+ update [--dry-run] Pull latest templates from GitHub
32
+ --version, -v Print version
33
+ --help, -h Print this help
34
+
35
+ Plugin mode:
36
+ Add "opencode-kit" to opencode.json plugin array (FIRST position).
37
+ Skills are auto-registered. Contract auto-initialized on first run.
38
+
39
+ Config resolution:
40
+ 1. .opencode/ (project override)
41
+ 2. ~/.config/opencode-kit/ (global defaults)
42
+ 3. plugin templates/ (shipped defaults)
43
+
44
+ Docs: ${pkg.homepage}
45
+ `);
46
+ process.exit(0);
47
+ }
@@ -0,0 +1,81 @@
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
+ . "$SCRIPT_DIR/platform.sh"
8
+
9
+ PLUGIN_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
10
+ GLOBAL_CONFIG_DIR="$HOME/.config/opencode-kit"
11
+
12
+ # Resolve a config file from the lookup chain:
13
+ # 1. .opencode/<path> (project override)
14
+ # 2. ~/.config/opencode-kit/<path> (global defaults)
15
+ # 3. <plugin>/<path> (plugin defaults)
16
+ # Returns: path to the first file found, or empty string if none found
17
+ resolve_config() {
18
+ local rel_path="$1"
19
+
20
+ # 1. Project override
21
+ if [ -f ".opencode/$rel_path" ]; then
22
+ echo ".opencode/$rel_path"
23
+ return 0
24
+ fi
25
+
26
+ # 2. Global defaults
27
+ if [ -f "$GLOBAL_CONFIG_DIR/$rel_path" ]; then
28
+ echo "$GLOBAL_CONFIG_DIR/$rel_path"
29
+ return 0
30
+ fi
31
+
32
+ # 3. Plugin defaults (templates/)
33
+ if [ -f "$PLUGIN_ROOT/templates/$rel_path" ]; then
34
+ echo "$PLUGIN_ROOT/templates/$rel_path"
35
+ return 0
36
+ fi
37
+
38
+ # 4. Plugin defaults (root/)
39
+ if [ -f "$PLUGIN_ROOT/$rel_path" ]; then
40
+ echo "$PLUGIN_ROOT/$rel_path"
41
+ return 0
42
+ fi
43
+
44
+ echo ""
45
+ return 1
46
+ }
47
+
48
+ # Initialize global config from plugin defaults
49
+ # Copies templates/* and rules/* to ~/.config/opencode-kit/
50
+ init_global_config() {
51
+ echo "[opencode-kit] Initializing global config at $GLOBAL_CONFIG_DIR"
52
+
53
+ mkdir -p "$GLOBAL_CONFIG_DIR/orchestration" "$GLOBAL_CONFIG_DIR/rules"
54
+
55
+ # Copy contract template
56
+ if [ ! -f "$GLOBAL_CONFIG_DIR/orchestration/contract.json" ]; then
57
+ cp "$PLUGIN_ROOT/templates/contract.json" "$GLOBAL_CONFIG_DIR/orchestration/contract.json"
58
+ echo " ✅ contract.json → global config"
59
+ fi
60
+
61
+ # Copy rules
62
+ if [ ! -f "$GLOBAL_CONFIG_DIR/rules/rules.json" ]; then
63
+ cp "$PLUGIN_ROOT/rules/rules.json" "$GLOBAL_CONFIG_DIR/rules/rules.json"
64
+ echo " ✅ rules.json → global config"
65
+ fi
66
+
67
+ echo "[opencode-kit] ✅ Global config initialized"
68
+ }
69
+
70
+ # Detect if the opencode-kit plugin is active
71
+ is_plugin_active() {
72
+ # Check if .opencode/plugins/opencode-kit.js exists (sign of plugin mode)
73
+ [ -f ".opencode/plugins/opencode-kit.js" ] && return 0
74
+ # Check if ~/.config/opencode-kit exists (sign of global config)
75
+ [ -d "$GLOBAL_CONFIG_DIR" ] && return 0
76
+ return 1
77
+ }
78
+
79
+ # Export functions
80
+ export -f resolve_config init_global_config is_plugin_active
81
+ export GLOBAL_CONFIG_DIR
package/src/init.sh ADDED
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env bash
2
+ # opencode-kit init — scaffold orchestration framework into target project
3
+ # Usage: npx opencode-kit init [--force]
4
+ set -euo pipefail
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
7
+ . "$SCRIPT_DIR/platform.sh"
8
+ . "$SCRIPT_DIR/global-config.sh"
9
+ KIT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
10
+
11
+ RED='\033[0;31m'
12
+ GREEN='\033[0;32m'
13
+ YELLOW='\033[1;33m'
14
+ CYAN='\033[0;36m'
15
+ NC='\033[0m'
16
+
17
+ FORCE="${1:-}"
18
+ TARGET_DIR="${PWD}"
19
+ TIMESTAMP=$(date +%Y%m%d%H%M%S)
20
+
21
+ echo "[opencode-kit] 🚀 Initializing orchestration framework in $TARGET_DIR"
22
+
23
+ # --- Dependency check ---
24
+ echo ""
25
+ echo "[opencode-kit] Checking dependencies..."
26
+
27
+ deps_ok=0
28
+ for cmd in git node; do
29
+ if command -v "$cmd" &>/dev/null; then
30
+ echo " ✅ $cmd: $(command -v $cmd)"
31
+ else
32
+ echo " ❌ $cmd: NOT FOUND — install $cmd first"
33
+ deps_ok=1
34
+ fi
35
+ done
36
+
37
+ # Check lean-ctx via MCP key (soft check — warn, don't block)
38
+ if command -v lean-ctx &>/dev/null; then
39
+ echo " ✅ lean-ctx available"
40
+ else
41
+ echo " ⚠️ lean-ctx not detected — ensure it's configured in MCP"
42
+ fi
43
+
44
+ if [ "$deps_ok" -eq 1 ]; then
45
+ echo -e "${RED}❌ Missing dependencies. Install them and retry.${NC}"
46
+ exit 1
47
+ fi
48
+
49
+ # --- Git check ---
50
+ if ! git rev-parse --git-dir &>/dev/null; then
51
+ echo ""
52
+ echo "[opencode-kit] Not a git repository. Initializing..."
53
+ git init
54
+ echo " ✅ git initialized"
55
+ fi
56
+
57
+ # --- Detect plugin mode ---
58
+ PLUGIN_MODE=false
59
+ if is_plugin_active; then
60
+ PLUGIN_MODE=true
61
+ echo ""
62
+ echo -e "${CYAN}[opencode-kit] Plugin detected — scaffolding project data only${NC}"
63
+ fi
64
+
65
+ # --- Handle existing .opencode/ ---
66
+ if [ -d ".opencode" ]; then
67
+ if [ "$FORCE" = "--force" ]; then
68
+ BACKUP=".opencode.bak.$TIMESTAMP"
69
+ echo ""
70
+ echo -e "${YELLOW}⚠️ --force: Backing up existing .opencode/ to $BACKUP${NC}"
71
+ cp -r ".opencode" "$BACKUP"
72
+ rm -rf ".opencode"
73
+ echo " ✅ Backed up to $BACKUP"
74
+ else
75
+ echo ""
76
+ echo -e "${YELLOW}⚠️ .opencode/ already exists. Use --force to overwrite (backup + clean scaffold).${NC}"
77
+ echo " Missing files will be added. Existing files will NOT be overwritten."
78
+ fi
79
+ fi
80
+
81
+ # --- Scaffold directories ---
82
+ mkdir -p .opencode/orchestration .opencode/rules .opencode/agents .opencode/src
83
+
84
+ # --- Copy templates ---
85
+ echo ""
86
+ echo "[opencode-kit] Scaffolding files..."
87
+
88
+ cp "$KIT_DIR/templates/contract.json" .opencode/orchestration/contract.json
89
+ echo " ✅ contract.json"
90
+
91
+ cp "$KIT_DIR/templates/superpowers-contract.json" .opencode/templates/superpowers-contract.json
92
+ echo " ✅ superpowers-contract.json"
93
+
94
+ cp "$KIT_DIR/rules/rules.json" .opencode/rules/rules.json
95
+ echo " ✅ rules.json"
96
+
97
+ cp "$KIT_DIR/src/verify.sh" .opencode/src/verify.sh
98
+ chmod +x .opencode/src/verify.sh
99
+ echo " ✅ verify.sh (executable)"
100
+
101
+ cp "$KIT_DIR/src/platform.sh" .opencode/src/platform.sh
102
+ chmod +x .opencode/src/platform.sh
103
+ echo " ✅ platform.sh (executable)"
104
+
105
+ # Plugin-specific: scripts that exist locally for CLI access
106
+ if [ "$PLUGIN_MODE" = false ]; then
107
+ # Non-plugin mode: copy all shell scripts
108
+ cp "$KIT_DIR/rules/validation.sh" .opencode/rules/validation.sh
109
+ chmod +x .opencode/rules/validation.sh
110
+ echo " ✅ rules/validation.sh"
111
+
112
+ cp "$KIT_DIR/src/preflight.sh" .opencode/src/preflight.sh
113
+ chmod +x .opencode/src/preflight.sh
114
+ echo " ✅ preflight.sh (executable)"
115
+
116
+ cp "$KIT_DIR/src/postflight.sh" .opencode/src/postflight.sh
117
+ chmod +x .opencode/src/postflight.sh
118
+ echo " ✅ postflight.sh (executable)"
119
+
120
+ cp "$KIT_DIR/src/update.sh" .opencode/src/update.sh
121
+ chmod +x .opencode/src/update.sh
122
+ echo " ✅ update.sh (executable)"
123
+
124
+ cp "$KIT_DIR/src/adr.sh" .opencode/src/adr.sh
125
+ chmod +x .opencode/src/adr.sh
126
+ echo " ✅ adr.sh (executable)"
127
+
128
+ cp "$KIT_DIR/src/telemetry.sh" .opencode/src/telemetry.sh
129
+ chmod +x .opencode/src/telemetry.sh
130
+ echo " ✅ telemetry.sh (executable)"
131
+
132
+ # --- Copy agent templates (pre-flight gates) ---
133
+ for agent in orchestrator planner task-manager code-reviewer learner fixer; do
134
+ if [ -f "$KIT_DIR/templates/agents/$agent.md" ]; then
135
+ cp "$KIT_DIR/templates/agents/$agent.md" ".opencode/agents/$agent.md"
136
+ echo " ✅ agents/$agent.md"
137
+ fi
138
+ done
139
+ fi
140
+
141
+ # --- Git ignore .opencode/src (scripts are project-specific) ---
142
+ if [ -f ".gitignore" ]; then
143
+ if ! grep -q ".opencode/src" .gitignore 2>/dev/null; then
144
+ echo ".opencode/src/" >> .gitignore
145
+ fi
146
+ fi
147
+
148
+ # --- Verify ---
149
+ echo ""
150
+ echo "[opencode-kit] Running verification..."
151
+ if "$KIT_DIR/src/verify.sh"; then
152
+ echo ""
153
+ echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
154
+ echo -e "${GREEN} ✅ opencode-kit v0.5.0 initialized${NC}"
155
+ echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
156
+ echo ""
157
+ echo " Next steps:"
158
+ echo " 1. Set GOAL & SCOPE in .opencode/orchestration/contract.json"
159
+ echo " 2. Set your project rules in .opencode/rules/rules.json"
160
+ echo " 3. Read AGENTS.md for writing conventions"
161
+ echo " 4. Start with: Load contract → Plan → Execute → Review"
162
+ else
163
+ echo -e "${RED}❌ Verification failed. Check errors above.${NC}"
164
+ exit 1
165
+ fi
@@ -0,0 +1,34 @@
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
@@ -0,0 +1,132 @@
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
+ . "$SCRIPT_DIR/platform.sh"
8
+
9
+ CONTRACT_KEY="orchestration-contract"
10
+ CONTRACT_FILE=".opencode/orchestration/contract.json"
11
+ STATE_FILE="STATE.md"
12
+ TELEMETRY_DIR=".opencode/telemetry"
13
+ START_TIME_FILE=".opencode/telemetry/.phase_start"
14
+
15
+ echo "[opencode-kit] Post-flight: persisting state..."
16
+
17
+ # --- Telemetry: record phase completion ---
18
+ mkdir -p "$TELEMETRY_DIR"
19
+ PHASE_START=$(cat "$START_TIME_FILE" 2>/dev/null || echo "")
20
+ if [ -n "$PHASE_START" ]; then
21
+ PHASE_ELAPSED=$(( $(date +%s) - PHASE_START ))
22
+ # Read current state from contract
23
+ if [ -f "$CONTRACT_FILE" ]; then
24
+ CURRENT_STATE=$($PYTHON_CMD -c "
25
+ import json
26
+ with open('$CONTRACT_FILE') as f: d=json.load(f)
27
+ print(d.get('state','UNKNOWN'))
28
+ " 2>/dev/null || echo "UNKNOWN")
29
+ PREV_STATE=""
30
+ [ -f "$TELEMETRY_DIR/phases.jsonl" ] && PREV_STATE=$(tail -1 "$TELEMETRY_DIR/phases.jsonl" 2>/dev/null | $PYTHON_CMD -c "import sys,json; print(json.load(sys.stdin).get('to','INIT'))" 2>/dev/null || echo "INIT")
31
+ echo "{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"from\":\"$PREV_STATE\",\"to\":\"$CURRENT_STATE\",\"elapsed_ms\":$((PHASE_ELAPSED * 1000))}" >> "$TELEMETRY_DIR/phases.jsonl"
32
+ echo " 📊 Telemetry: $PREV_STATE → $CURRENT_STATE (${PHASE_ELAPSED}s)"
33
+ fi
34
+ rm -f "$START_TIME_FILE"
35
+ fi
36
+
37
+ # --- Step 1: Re-read contract from lean-ctx (the source of truth) ---
38
+ CURRENT_CONTRACT=$(lean-ctx ctx_knowledge recall --query "$CONTRACT_KEY" 2>/dev/null || cat "$CONTRACT_FILE")
39
+
40
+ # --- Step 2: Write back to lean-ctx ---
41
+ lean-ctx ctx_knowledge remember \
42
+ category architecture \
43
+ key "$CONTRACT_KEY" \
44
+ value "$CURRENT_CONTRACT" 2>/dev/null && \
45
+ echo " ✅ Contract persisted to lean-ctx" || \
46
+ echo " ⚠️ lean-ctx persist failed"
47
+
48
+ # --- Step 3: Sync STATE.md ---
49
+ mkdir -p "$(dirname "$STATE_FILE")"
50
+ if [ -f "$CONTRACT_FILE" ]; then
51
+ STATE=$(echo "$CURRENT_CONTRACT" | $PYTHON_CMD -c "import sys,json; d=json.load(sys.stdin); print(d.get('state','UNKNOWN'))" 2>/dev/null || echo "UNKNOWN")
52
+ PHASE=$(echo "$CURRENT_CONTRACT" | $PYTHON_CMD -c "import sys,json; d=json.load(sys.stdin); r=d.get('retry',{}); print(r.get('current_phase','none'))" 2>/dev/null || echo "none")
53
+ SCORE=$(echo "$CURRENT_CONTRACT" | $PYTHON_CMD -c "import sys,json; d=json.load(sys.stdin); s=d.get('score',{}); print(s.get('combined','?'))" 2>/dev/null || echo "?")
54
+ echo " 📝 Contract state: $STATE (phase: $PHASE, score: $SCORE)"
55
+
56
+ # Create or update STATE.md with current focus
57
+ cat > "$STATE_FILE" << STATEMD
58
+ # Project State
59
+
60
+ ## Current Focus
61
+ Agent orchestration — $STATE (phase: ${PHASE:-none}). Score: $SCORE.
62
+
63
+ ## Known Blockers
64
+ $(echo "$CURRENT_CONTRACT" | $PYTHON_CMD -c "
65
+ import sys,json
66
+ d=json.load(sys.stdin)
67
+ r=d.get('retry',{})
68
+ issues=r.get('issues',[])
69
+ if issues:
70
+ for i in issues: print(f'- {i}')
71
+ else:
72
+ print('None')
73
+ " 2>/dev/null || echo "None")
74
+
75
+ ## Active Decisions
76
+ $(echo "$CURRENT_CONTRACT" | $PYTHON_CMD -c "
77
+ import sys,json
78
+ d=json.load(sys.stdin)
79
+ log=d.get('decisions',{}).get('adr_log',[])
80
+ if log:
81
+ for entry in log[-3:]:
82
+ print(f'- {entry.get(\"id\",\"?\")}: {entry.get(\"title\",\"\")}')
83
+ else:
84
+ print('No ADRs recorded')
85
+ " 2>/dev/null || echo "None")
86
+
87
+ ## Recent Changes
88
+ - Last state transition: $(echo "$CURRENT_CONTRACT" | $PYTHON_CMD -c "
89
+ import sys,json
90
+ d=json.load(sys.stdin)
91
+ phases=d.get('metrics',{}).get('phases_completed',[])
92
+ print(phases[-1] if phases else 'INIT')
93
+ " 2>/dev/null || echo "INIT")
94
+ STATEMD
95
+ echo " ✅ STATE.md synced"
96
+ fi
97
+
98
+ # --- Step 4: Save ctx_session ---
99
+ lean-ctx ctx_session save 2>/dev/null && \
100
+ echo " ✅ Session saved" || \
101
+ echo " ⚠️ ctx_session save skipped (not available)"
102
+
103
+ # --- Step 5: Update telemetry summary ---
104
+ if [ -f "$TELEMETRY_DIR/phases.jsonl" ] && [ -n "$PYTHON_CMD" ]; then
105
+ $PYTHON_CMD -c "
106
+ import json
107
+ total_ms = 0
108
+ agents = set()
109
+ phases = []
110
+ with open('$TELEMETRY_DIR/phases.jsonl') as f:
111
+ for line in f:
112
+ line=line.strip()
113
+ if not line: continue
114
+ try:
115
+ entry=json.loads(line)
116
+ total_ms+=entry.get('elapsed_ms',0)
117
+ phases.append(entry.get('to',''))
118
+ except: pass
119
+
120
+ summary = {
121
+ 'phases_completed': phases,
122
+ 'total_elapsed_ms': total_ms,
123
+ 'total_elapsed_s': round(total_ms/1000, 1),
124
+ 'updated_at': '$(date -u +%Y-%m-%dT%H:%M:%SZ)'
125
+ }
126
+ with open('$TELEMETRY_DIR/summary.json', 'w') as f:
127
+ json.dump(summary, f, indent=2)
128
+ print(f' 📈 Telemetry summary: {len(phases)} phases, {total_ms/1000:.0f}s total')
129
+ " 2>/dev/null || true
130
+ fi
131
+
132
+ echo "[opencode-kit] ✅ Post-flight complete."