@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/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
@@ -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."