@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.
@@ -1,67 +0,0 @@
1
- # Contract Protocol
2
-
3
- The orchestrator contract (`contract.json`) is the single source of truth for every agent session.
4
-
5
- ## What it does
6
-
7
- - Tracks **state** — what phase the workflow is in (INIT → PLAN → ... → COMPLETE)
8
- - Stores **requirements** — the goal, acceptance criteria, and constraints
9
- - Logs **decisions** — Architecture Decision Records (ADRs)
10
- - Records **scores** — quality metrics from the scoring pipeline
11
- - Captures **telemetry** — elapsed time, phases completed, agents used
12
-
13
- ## State Machine
14
-
15
- ```
16
- INIT → PLAN → PLAN_SCORED → EXECUTE → EXECUTE_SCORED → REVIEW → REVIEW_SCORED → COMPLETE
17
-
18
- BLOCKED (any phase) → user intervention → retry
19
- ```
20
-
21
- **Transition rules:**
22
- - Each phase transition requires score ≥ 70
23
- - Score < 50 → BLOCKED
24
- - Max 3 retry attempts before escalation
25
-
26
- ## Key Fields
27
-
28
- ```json
29
- {
30
- "state": "INIT",
31
- "contract_version": "0.5.8",
32
- "requirements": {
33
- "goal": "What we're building",
34
- "acceptance_criteria": ["testable condition 1"],
35
- "constraints": ["must not..."]
36
- },
37
- "governance": {
38
- "active_agent": "orchestrator",
39
- "current_guidance": "Instructions for this session",
40
- "extension_skills": ["java-conventions"],
41
- "permissions": { "do": [], "dont": ["push to main"] }
42
- },
43
- "decisions": {
44
- "adr_log": [
45
- { "id": "ADR-001", "date": "2026-06-11", "title": "Use contract protocol", ... }
46
- ]
47
- },
48
- "score": {
49
- "combined": 85,
50
- "verdict": "PASS"
51
- },
52
- "metrics": {
53
- "phases_completed": ["INIT", "PLAN", "PLAN_SCORED"]
54
- }
55
- }
56
- ```
57
-
58
- ## Resolution Order
59
-
60
- 1. `.opencode/orchestration/contract.json` — project override
61
- 2. `~/.config/opencode-kit/contract.json` — global defaults
62
- 3. Plugin `templates/contract.json` — shipped defaults
63
-
64
- ## CLI
65
-
66
- View contract state: `bash .opencode/src/status.sh`
67
- Compare across branches: `bash .opencode/src/diff.sh [branch1] [branch2]`
@@ -1,60 +0,0 @@
1
- # Scoring Pipeline
2
-
3
- Every subagent output is scored before the next phase begins.
4
-
5
- ## Tier 1 — Rule Checks
6
-
7
- Automatic checks run by the orchestrator. Start at 100, deduct per violation:
8
-
9
- | Check | Deduction |
10
- |-------|:---------:|
11
- | Schema valid (required fields present) | -15 |
12
- | Permissions violated | -40 |
13
- | Blast radius HIGH/CRITICAL | -40 |
14
- | Writing order wrong | -15 |
15
- | Fields missing | -15 |
16
-
17
- If subtotal < 70 → skip Tier 2, use subtotal as combined score.
18
-
19
- ## Tier 2 — LLM Judge
20
-
21
- If Tier 1 score ≥ 70, the orchestrator runs a judge via `subtask()`.
22
-
23
- ```json
24
- {
25
- "score": 85,
26
- "rationale": "All requirements met. Writing order correct.",
27
- "missing_items": ["Test for null boundary case"]
28
- }
29
- ```
30
-
31
- **Dimensions:**
32
- | Dimension | Max | What it evaluates |
33
- |-----------|:---:|-------------------|
34
- | Requirements | 40 | Does output satisfy goal + acceptance criteria? |
35
- | Governance | 30 | Follows rules.json + writing order? |
36
- | Completeness | 20 | All files created? Edge cases documented? |
37
- | Edge cases | 10 | Nulls, errors, boundaries covered? |
38
-
39
- ## Tier 3 — Verdict
40
-
41
- | Combined Score | Verdict | Action |
42
- |:--------------:|:-------:|--------|
43
- | ≥ 70 | **PASS** | Advance to next phase |
44
- | 50–69 | **RETRY** | Increment retry count, re-delegate |
45
- | < 50 | **BLOCKED** | Escalate to user |
46
-
47
- ## Configuration
48
-
49
- Thresholds and deductions are in `rules.json` → `scoring`:
50
-
51
- ```json
52
- {
53
- "scoring": {
54
- "tier1": { "schema_valid_deduction": 15, ... },
55
- "thresholds": { "pass": 70, "retry": 50, "max_attempts": 3 }
56
- }
57
- }
58
- ```
59
-
60
- Projects can override rule severity via `contract.json` → `validation.rule_overrides`.
@@ -1,78 +0,0 @@
1
- # Troubleshooting Guide
2
-
3
- ## Common Issues
4
-
5
- ### Plugin doesn't load
6
-
7
- **Symptom**: Skills not available, contract not injected.
8
-
9
- **Checks:**
10
- 1. Is `@ikieaneh/opencode-kit` in `opencode.json` plugin array? Must be **first**.
11
- 2. Is it installed? `ls node_modules/@ikieaneh/opencode-kit`
12
- 3. Is the plugin array syntax correct? `"plugin": ["@ikieaneh/opencode-kit"]`
13
-
14
- ### Agent jumps straight to implementation
15
-
16
- **Symptom**: Agent starts working without loading contract.
17
-
18
- **Fix:** The plugin's `messages.transform` hook injects the bootstrap. Make sure:
19
- 1. Plugin is first in the array
20
- 2. No other plugin overrides the same hook
21
- 3. Run `bash .opencode/src/doctor.sh` to verify
22
-
23
- ### Contract not found
24
-
25
- **Symptom**: "Contract not found" error from preflight.
26
-
27
- **Solution:**
28
- ```sh
29
- bash .opencode/src/doctor.sh
30
- # Or manually:
31
- mkdir -p .opencode/orchestration
32
- cp node_modules/@ikieaneh/opencode-kit/templates/contract.json .opencode/orchestration/contract.json
33
- ```
34
-
35
- ### Score below threshold
36
-
37
- **Symptom**: Workflow keeps retrying or gets blocked.
38
-
39
- **Check:**
40
- - Tier 1 violations (blast radius, permissions)
41
- - Tier 2 judge feedback — read `judge.missing_items`
42
- - Retry count — max 3 attempts before BLOCKED
43
-
44
- ### ShellCheck fails in CI
45
-
46
- **Symptom**: GitHub Actions ShellCheck job fails.
47
-
48
- **Fix:** Run locally:
49
- ```sh
50
- shellcheck src/*.sh rules/*.sh
51
- ```
52
- Look for SC1091 (source path) or SC2001 (sed style) — most are info-level.
53
-
54
- ### Scaffold test fails in CI
55
-
56
- **Symptom**: init.sh exits with error.
57
-
58
- **Check:**
59
- - Are all `mkdir -p` paths created before `cp`?
60
- - Run locally: `cd /tmp && git init && bash /path/to/init.sh`
61
-
62
- ### Custom skill not loading
63
-
64
- **Symptom**: Skill referenced in opencode.json not available.
65
-
66
- **Solution:**
67
- 1. Place skill at `.opencode/skills/<name>/SKILL.md`
68
- 2. Verify frontmatter has `description:` field
69
- 3. Verify SKILL.md starts with `---` (YAML frontmatter)
70
- 4. Run `bash .opencode/src/doctor.sh` to verify
71
-
72
- ## Diagnostics
73
-
74
- ```sh
75
- bash .opencode/src/doctor.sh # Full health check
76
- bash .opencode/src/status.sh # Dashboard view
77
- bash .opencode/src/analytics.sh # Telemetry analysis
78
- ```
@@ -1,9 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" fill="none">
2
- <rect width="80" height="80" rx="16" fill="#1a1a2e"/>
3
- <path d="M20 25h40v30H20z" stroke="#e94560" stroke-width="2" fill="none"/>
4
- <path d="M20 40h40" stroke="#e94560" stroke-width="2"/>
5
- <circle cx="30" cy="35" r="3" fill="#0f3460"/>
6
- <circle cx="50" cy="35" r="3" fill="#0f3460"/>
7
- <path d="M25 48l10-10M45 48l10-10" stroke="#16213e" stroke-width="2"/>
8
- <text x="40" y="62" text-anchor="middle" font-size="8" fill="#e94560" font-family="monospace">KIT</text>
9
- </svg>
@@ -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,152 +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=$($PYTHON_CMD -c "
63
- import json
64
- with open('$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 = max(int(entry.get('id','ADR-000').replace('ADR-','')) for entry in log)
70
- print(f'ADR-{last+1:03d}')
71
- ")
72
-
73
- # --- Check for duplicate title ---
74
- DUP=$($PYTHON_CMD -c "
75
- import json
76
- with open('$CONTRACT_FILE') as f: d = json.load(f)
77
- log = d.get('decisions', {}).get('adr_log', [])
78
- for entry in log:
79
- if entry.get('title','').lower().strip() == '$TITLE'.lower().strip():
80
- print(entry.get('id',''))
81
- break
82
- " 2>/dev/null)
83
- if [ -n "$DUP" ]; then
84
- echo -e "${YELLOW}⚠️ Duplicate title found: $DUP — '$TITLE'${NC}"
85
- echo " Skipping. Update existing ADR instead."
86
- exit 0
87
- fi
88
-
89
- # --- Build ADR entry via temp JSON file (avoids shell injection) ---
90
- # Write ADR fields to temp file to avoid shell interpolation into Python
91
- ADR_DATA=$(mktemp /tmp/opencode-adr-data-XXXXX.json)
92
- cat > "$ADR_DATA" << ADRDATA
93
- {
94
- "title": "$TITLE",
95
- "context": "$CONTEXT",
96
- "decision": "$DECISION",
97
- "alternatives": "$ALTERNATIVES",
98
- "consequences": "$CONSEQUENCES"
99
- }
100
- ADRDATA
101
-
102
- $PYTHON_CMD -c "
103
- import json
104
-
105
- with open('$ADR_DATA') as f:
106
- data = json.load(f)
107
-
108
- entry = {
109
- 'id': '$NEXT_ID',
110
- 'date': '$(date +%Y-%m-%d)',
111
- 'title': data['title'],
112
- 'context': data['context'],
113
- 'decision': data['decision'],
114
- 'alternatives': data['alternatives'],
115
- 'consequences': data['consequences']
116
- }
117
-
118
- with open('/tmp/opencode-adr-entry.json', 'w') as f:
119
- json.dump(entry, f, indent=2)
120
- print('Entry written')
121
- "
122
-
123
- rm -f "$ADR_DATA"
124
-
125
- # --- Inject into contract.json ---
126
- $PYTHON_CMD -c "
127
- import json
128
-
129
- with open('$CONTRACT_FILE') as f:
130
- contract = json.load(f)
131
-
132
- with open('/tmp/opencode-adr-entry.json') as f:
133
- entry = json.load(f)
134
-
135
- if 'decisions' not in contract:
136
- contract['decisions'] = {}
137
- if 'adr_log' not in contract['decisions']:
138
- contract['decisions']['adr_log'] = []
139
-
140
- contract['decisions']['adr_log'].append(entry)
141
-
142
- with open('$CONTRACT_FILE', 'w') as f:
143
- json.dump(contract, f, indent=2)
144
-
145
- print(json.dumps(entry, indent=2))
146
- "
147
-
148
- echo ""
149
- echo -e "${GREEN}[opencode-kit] ✅ ADR recorded: $NEXT_ID${NC}"
150
- echo " Title: $TITLE"
151
- echo " File: $CONTRACT_FILE"
152
- 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,95 +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
- if [ -n "$PYTHON_CMD" ]; then
46
- $PYTHON_CMD -c "
47
- import json, sys
48
-
49
- def get_state(c):
50
- try:
51
- d = json.loads(c)
52
- return {
53
- 'state': d.get('state', '?'),
54
- 'goal': (d.get('requirements', {}) or {}).get('goal', '?'),
55
- 'score': (d.get('score', {}) or {}).get('combined', '?'),
56
- 'phases': (d.get('metrics', {}) or {}).get('phases_completed', []),
57
- 'adrs': len((d.get('decisions', {}) or {}).get('adr_log', [])),
58
- 'version': d.get('contract_version', '?')
59
- }
60
- except:
61
- return None
62
-
63
- a = get_state('''$CONTRACT_A''') if '''$CONTRACT_A''' else None
64
- b = get_state('''$CONTRACT_B''') if '''$CONTRACT_B''' else None
65
-
66
- if a and b:
67
- print(f' Field $BRANCH_A $BRANCH_B')
68
- print(f' {"-"*50}')
69
- for field in ['state', 'goal', 'score', 'version']:
70
- va = str(a.get(field, '?'))[:20]
71
- vb = str(b.get(field, '?'))[:20]
72
- marker = ' ←→' if va != vb else ' '
73
- print(f' {field:20s} {va:20s} {marker} {vb:20s}')
74
- print(f' phases {len(a.get(\"phases\",[])):3d} completed {len(b.get(\"phases\",[])):3d} completed')
75
- print(f' ADRs {a.get(\"adrs\",0):3d} recorded {b.get(\"adrs\",0):3d} recorded')
76
- elif a and not b:
77
- print(f' Contract exists in $BRANCH_A but NOT in $BRANCH_B')
78
- print(f' State: {a.get(\"state\",\"?\")}')
79
- elif b and not a:
80
- print(f' Contract exists in $BRANCH_B but NOT in $BRANCH_A')
81
- print(f' State: {b.get(\"state\",\"?\")}')
82
- " 2>/dev/null || echo -e "${YELLOW}Could not parse contract JSON${NC}"
83
- fi
84
-
85
- # Raw git diff
86
- echo ""
87
- echo -e "${CYAN}Raw diff:${NC}"
88
- if git diff "$BRANCH_A" "$BRANCH_B" -- "$CONTRACT_FILE" 2>/dev/null | head -40; then
89
- if ! git diff --exit-code "$BRANCH_A" "$BRANCH_B" -- "$CONTRACT_FILE" &>/dev/null; then
90
- :
91
- fi
92
- fi
93
- echo ""
94
- echo -e "Run: ${GREEN}bash .opencode/src/diff.sh${NC} (default: main vs HEAD)"
95
- echo -e " ${GREEN}bash .opencode/src/diff.sh staging feature-x${NC} (custom branches)"