Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions .github/releases/v1.0.34.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
## opencode {VERSION}

Stable release from `main` branch. The MCP stack moves to SDK v2 with per-server protocol era negotiation (`auto`/`legacy`/`modern`), the negotiated era becomes visible in `mcp list` and the HTTP API, dual-era interop is locked in by real-server regression tests, and local macOS builds sign their darwin binaries so `bun run build` self-validates again.

---

### 🎯 Features

- **MCP SDK v2 + 2026-07-28 protocol era negotiation, #447/#448 (PR #449)**: the client migrates from `@modelcontextprotocol/sdk@1.29.0` to `@modelcontextprotocol/client@2.0.0`, adding `protocol: auto | legacy | modern` per MCP server in config. `auto` probes `server/discover` with conservative fallback to the 2025 initialize handshake; `legacy` skips the probe; `modern` pins 2026-07-28 with no fallback. Known boundaries: SSE-only remote servers are no longer connectable, and transparent 404 session recovery is gone per spec.
- **Negotiated era visible in status, #450 (PR #451)**: connected MCP statuses now carry optional `era` ("modern"/"legacy") and `protocolVersion` fields, populated from the v2 client's negotiated version (omitted when unreported). `opencode mcp list` renders `connected: modern (2026-07-28)`, the HTTP API schema composes the same fields, and the JS SDK is regenerated to match.

---

### 🐛 Bug Fixes

- **macOS local builds died in their own smoke test, #454 (PR #455)**: freshly compiled unsigned darwin binaries were killed by the kernel (SIGKILL 137) before `--version` could run, failing `bun run build`; darwin targets are now ad-hoc signed (`codesign --force --sign -`) before the smoke step, triple-gated on host platform, target platform, and codesign availability. Linux hosts and cross-builds are unchanged.

---

### 🏗️ Architecture / Refactor

- **dev-lineage re-sync with main (PR #456)**: the three v2 follow-up deliveries entered main directly (SpecGit bootstrap defaulted the PR base), so a chore sync PR re-established that dev contains all of main, keeping dev the integration layer for subsequent work.

---

### 🧪 Test Summary

```
CI gates on main at each delivery merge (#451/#453/#455):
Typecheck: pass (3/3 runs)
Unit Tests (linux): pass (3/3 runs)
E2E Tests (linux): pass (3/3 runs)
E2E Tests (windows): pass (3/3 runs)
SpecGit Acceptance: pass (3/3 runs)
mcp suite: 95 pass (92 + interop 3)
lint ratchet: 4814-4825 <= 4850
dev sync (#456): all gates pass (1 prompt-loop flake, rerun green)
```

---

### 🔍 Verification

- Each delivery carried an accepted SpecGit verdict (`specgit finish` exit 0) on its final head after anchor-fresh CI, plus a DAG workflow double review (standards + intent) with 8/8 load-bearing claims independently verified and zero P0/P1 findings.
- New dual-era interop tests drive the real v2 client in fresh subprocesses against in-repo fixture servers: stdio auto negotiates 2026-07-28, stdio legacy pin negotiates 2025-11-25 against the same fixture, streamable HTTP auto negotiates 2026-07-28, each with a tool-call round trip.
- Live end-to-end check against dual-era fixtures: a v1 (legacy SDK) client connects via legacy fallback; a v2 binary negotiates modern and round-trips echo tools through the TUI.
- Deliberately deferred: darwin cross-builds from Linux CI remain unsigned (ad-hoc signing requires macOS codesign); SSE-only server support is intentionally dropped with v2.

---

**Full changelog:** [`{previous_tag}`...`{current_tag}`](https://github.com/LeXwDeX/OpenCode-GraphAgent/compare/{previous_tag}...{current_tag})
42 changes: 42 additions & 0 deletions .github/releases/v1.0.35.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
## opencode {VERSION}

{Prerelease/Stable} release from `{branch}` branch. Giant `summary.diffs` snapshots (37.8MB measured in the wild) can no longer starve the event loop into an unkillable state: message summaries are capped at 256KB at every write path, and oversized legacy rows are stripped on read - the session that triggered the original kill -9 now reopens cleanly.

---

### 🐛 Bug Fixes

- **ESC could not cancel a session fed a giant upstream diff, #458 (PR #459)**: `SessionSummary.summarize` wrote the full `snapshot.diffFull` output into the user message's `summary.diffs` with no size limit, so one upstream-sync turn injected a 37.8MB payload; every `message.updated` then re-serialized it into the event table (single events measured at 37.87MB, the shared DB grew to 10GB) and SSE broadcast, driving the in-process TUI into an allocation storm that starved the event loop - the abort HTTP request never got scheduled and only `kill -9` recovered it. Three guards now share `MAX_SUMMARY_DIFF_BYTES = 256KB`: source truncation in `summarize`, a write guard in `toRow`, and a read guard in `fromRow` that strips oversized legacy `summary_diffs` while keeping the additions/deletions/files stats (diffs are display-only metadata; the LLM context itself was only 695KB). Byte accounting matches the JSON serialization exactly, and round-trips are idempotent.

---

### ⚙️ CI / Engineering

- **specgit-accept re-aligned with the main-line harness (PR #459)**: a `specgit init --force` version refresh had reverted the workflow to the template's workspace-local CLI install (`npm install --no-save`), which dies on this bun workspace's `catalog:` protocol (EUNSUPPORTEDPROTOCOL, #434 redux), and to a `yaml`-importing wait step the global install cannot resolve. Restored the verified shape: `npm install -g specgit@^0.5.0`, regex-based policy parsing, main-only trigger, and the deviation is now recorded in AGENTS.md's local-specializations list so the next re-init re-applies it.

---

### 🧪 Test Summary

```
CI gates on main at the delivery merge (PR #459):
Typecheck: pass
Unit Tests (linux): pass
E2E Tests (linux): pass
E2E Tests (windows): pass
SpecGit Acceptance: pass
dag-core gate: 52 pass / 0 fail
new guard tests: 7/7 pass (summary-diff-guard.test.ts)
```

---

### 🔍 Verification

- Delivery carried an accepted SpecGit verdict (`specgit finish` exit 0) on the final head after anchor-fresh CI, via a DAG workflow (map -> coding -> verify -> review -> synthesize) with a PASS review verdict (0 blocking; 3 INFO suggestions all absorbed into the final commit: shared helper, byte-accounting comment, pinned boundary tests).
- `test/session/summary-diff-guard.test.ts` runs the real service graph (`Session.node` + real `Database.node`, only `Snapshot.diffFull` stubbed): source truncation keeps the leading files within budget, the read guard strips legacy oversized rows while preserving stats, in-budget round-trips are lossless, and empty/single-oversized/exact-capacity boundaries are pinned.
- Deliberately deferred (tracked in #458): abort-path resilience against a starved event loop, and an event-loop lag watchdog.

---

**Full changelog:** [`{previous_tag}`...`{current_tag}`](https://github.com/LeXwDeX/OpenCode-GraphAgent/compare/{previous_tag}...{current_tag})
7 changes: 4 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ tsconfig.tsbuildinfo
.opencode/dag-init.json
.opencode/workflow-drafts/
.opencode/workflow-reports/

# SpecGit delivery binding: local-only, never tracked
.specgit.yaml
# >>> specgit: local delivery assets (managed by specgit init) >>>
/.specgit.yaml
/spec_git/
# <<< specgit: local delivery assets (managed by specgit init) <<<
26 changes: 26 additions & 0 deletions .opencode/command/specgit-doctor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
description: Diagnose the SpecGit environment probes and drive the exit-3 repair loop
---

<!-- specgit-managed-entry-point -->

# /specgit-doctor

Thin trigger for the exit-3 diagnostic loop. The canonical behavior lives in
the AGENTS.md SpecGit block; this command only launches it.

## Steps

1. Run from the repo root:

```bash
specgit doctor --json
```

2. Read `probes[]`: every failing probe carries a `code` (git, repo,
origin, gh/glab presence and auth, policy).
3. Fix exactly what the failing probe names, then re-run
`specgit doctor --json` until exit 0.
4. Return to the verdict: `specgit finish --json`. Exit 3 is environment,
never delivery — do not edit the record or the policy to work around it.
5. `--json` is the only parse surface.
2 changes: 2 additions & 0 deletions .opencode/command/specgit-finish.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
description: Run the SpecGit evidence verdict and drive the fix loop to exit 0
---

<!-- specgit-managed-entry-point -->

# /specgit-finish

Thin trigger for the acceptance verdict. The canonical behavior lives in the
Expand Down
9 changes: 8 additions & 1 deletion .opencode/command/specgit-issue.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
description: Start a SpecGit delivery from a title or existing issue number
---

<!-- specgit-managed-entry-point -->

# /specgit-issue

Thin trigger for the delivery bootstrap. The canonical behavior lives in the
Expand All @@ -17,6 +19,11 @@ AGENTS.md SpecGit block; this command only launches it.
specgit issue "$ARGUMENTS" --json
```

3. On success report the brief: issue URL(s), PR URL (draft), branch name.
3. On success report the brief: issue URL(s), PR URL (draft), branch name —
then fill each issue body it created (Why / Scope / Approach /
Acceptance) from the discussion with `gh issue edit <n>`, then
implement. Fill in the draft PR's scaffold (Why / What changed /
Evidence) as you deliver; its placeholders are advisory, never gates,
and the closing references stay intact.
4. Switch to the delivery branch and begin the TDD loop.
5. On error, read `errors[].fix` and follow it — never bypass the record.
27 changes: 27 additions & 0 deletions .opencode/command/specgit-pr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
description: Repair the SpecGit PR binding — auto-discover by head branch or bind explicitly
---

<!-- specgit-managed-entry-point -->

# /specgit-pr

Thin trigger for PR-binding repair. The canonical behavior lives in the
AGENTS.md SpecGit block; this command only launches it.

## Steps

1. Run from the delivery branch:

```bash
specgit pr --json
```

2. Branch on the result:
- `exit 0` → the record's PR binding is repaired; resume the delivery.
- `pr_not_found` → push the branch (re-running `specgit issue`
resumes the bootstrap), then rerun this command.
- `pr_ambiguous` → several open PRs share the head branch; bind one
explicitly: `specgit pr <number>`.
3. `specgit pr` owns the PR binding; never hand-edit `.specgit.yaml`.
`--json` is the only parse surface.
28 changes: 28 additions & 0 deletions .opencode/command/specgit-status.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
description: Show local SpecGit evidence — record, delivery state, drift, origin
---

<!-- specgit-managed-entry-point -->

# /specgit-status

Thin trigger for local evidence. The canonical behavior lives in the
AGENTS.md SpecGit block; this command only launches it.

## Steps

1. Run from the repo root:

```bash
specgit status --json
```

2. Read `state` and `record` from the envelope: local evidence only —
record, drift, origin. Platform evidence (issues, PR, checks) belongs
to `specgit finish`.
3. No record is not an error: `state: "unbound"` with exit `0` is the
normal pre-binding state — bootstrap with `specgit issue` (the
`record_missing` warning carries the next step in `warnings[].fix`).
Exit `3` is different: `state: "unknown"`, a genuine evidence
failure — read `errors[].fix`.
4. Never hand-edit `.specgit.yaml`.
17 changes: 16 additions & 1 deletion .opencode/hooks/specgit-merge-guard.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/bin/sh
# SpecGit merge guard (managed by specgit init). Exit 2 = block with reason.
# SpecGit guard (managed by specgit init): start gate + merge guard. Exit 2 = block with reason.
GUARD_DIR=$(cd "$(dirname "$0")" && pwd)
export GUARD_DIR
# Hook payloads arrive as the first argument or on stdin; accept both.
Expand All @@ -8,6 +8,21 @@ if [ -n "$1" ]; then
else
payload=$(cat)
fi
tool=$(printf '%s' "$payload" | node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{try{const j=JSON.parse(s);process.stdout.write((j.tool_name)||'')}catch{process.stdout.write('')}})")
case "$tool" in
edit|write|Edit|Write)
# Start gate (#335): mutating files requires the delivery binding on
# THIS branch. The record's context.branch is written by specgit and
# matched as a fixed WHOLE line — no YAML parsing, no prefix collision
# (branch "feat/1-a" must never satisfy a record for "feat/1-a2").
branch=$(git branch --show-current 2>/dev/null)
if [ -z "$branch" ] || [ ! -f .specgit.yaml ] || ! grep -qFx " branch: $branch" .specgit.yaml; then
echo "specgit: start gate - this branch has no delivery binding. Start the delivery first: specgit issue \"<type>: <title>\", then fill each issue body from the discussion, then edit files." >&2
exit 2
fi
exit 0
;;
esac
command=$(printf '%s' "$payload" | node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{try{const j=JSON.parse(s);process.stdout.write((j.tool_input&&j.tool_input.command)||'')}catch{process.stdout.write('')}})")

case "$command" in
Expand Down
50 changes: 48 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ This repository uses a multi-context domain-document layout rooted at `CONTEXT-M
Kept OUTSIDE the managed block so `specgit init`/`--force` never rewrites them; re-apply each deviation after every re-init:

- `specgit-accept.yml` drops the template's `workflow_dispatch` trigger. Dispatch is the privileged context that fires CodeQL's cache-poisoning taint rule on the `head_ref` checkout (false positive: no cache use, read-only token, `persist-credentials: false`), and on dispatch events `head_ref` is empty so the verdict would evaluate the default branch — the wrong tree. Delivery here always goes through a PR. The head-ref checkout itself must NOT be replaced with a SHA: `specgit finish` requires HEAD on the delivery branch (detached_head otherwise).
- `specgit-accept.yml` installs the CLI globally (`npm install -g specgit@^0.5.0`), NOT the template's `npm install --no-save specgit@X`: a workspace-local install reads this bun workspace's package.json and dies on the `catalog:` protocol (EUNSUPPORTEDPROTOCOL, #434, again on #459). Re-apply after every `specgit init --force`.
- `spec_git/policy.yaml` `required_checks` uses the template's canonical check IDs (`unit-tests`, `e2e-tests`), not display names.

<!-- specgit:block:start -->
Expand All @@ -283,13 +284,30 @@ already exists); keep manual guidance outside them.
deterministic scaffold (the `Closes #n` line for every bound issue,
then Why / What changed / Evidence / Checklist sections), and writes
`.specgit.yaml`. Re-running resumes; it is idempotent.
- Fill in the scaffold sections as you deliver. Its placeholders are
advisory — the closing references are the only body gate. The PR body
- Issue bodies are filled at bootstrap, from the conversation: right after
`specgit issue` succeeds, edit each issue it created (`gh issue edit <n>`)
with the discussed Why / Scope / Approach / Acceptance, then implement.
The PR scaffold's placeholders are advisory — fill those sections in as
you deliver; the closing references are the only body gate. The PR body
is written once at creation; no SpecGit command edits an existing PR
body, and the repository's own pull-request template is never read.
- A draft pull request always fails the verdict (`pr_draft`): before
`specgit finish`, mark it ready for review — `gh pr ready <number>`
on GitHub, `glab mr update <number> --ready` on GitLab.
- Finish with `specgit finish`: the verdict, derived from real git, PR,
and CI evidence. Exit code 0 is the only "done".

### Issue tags

- Every bootstrap applies the title's `kind::<type>` member
automatically; pass `--tags <a,b>` to choose the full set explicitly.
- Selection is pool-first: existing on-spec labels win verbatim; anything
missing is seeded from the built-in `kind::` catalog or the policy's
`tags:` declarations. Unknown vocabulary exits 2 naming the universe.
- Choose with restraint: at most one label per axis, none when unsure —
off-spec pool labels are reported (`tag_pool_dirty` warnings are for
humans) and never renamed by SpecGit.

### Repair and diagnostics

- `specgit pr` repairs the pull-request binding: with no arguments it
Expand Down Expand Up @@ -333,6 +351,34 @@ verified on its own evidence, split it before binding.
- Never weaken `spec_git/policy.yaml` to make a verdict pass.
- `--json` is the only parse surface: stdout is exactly one JSON
document; never scrape human-readable output.

### Agent contract essentials

- **SpecGit is the default way of working here.** Any non-trivial
task — a feature, a fix, a refactor, a docs change — is a delivery:
work items live in this tracker as issues, never in private task
lists or conversational checklists. The trigger is the decision to
start: the moment the conversation settles and you begin turning
the plan into changes, the FIRST action is
`specgit issue <type>: <title>...` — before any file edit.
Working without a binding is a contract violation, not a style
choice. Immediately after bootstrap, fill each issue body
(Why / Scope / Approach / Acceptance) from the discussion with
`gh issue edit`, then implement. Mid-conversation inventories
("let me list everything to do") become issues, not chat
artifacts. Trivial replies and read-only questions need none of
this.
- The one rule: a delivery is done if and only if `specgit finish`
exits `0`. Never declare completion from task lists, file states, or
test runs you performed yourself.
- Branch on exit codes, not phrasing: `1` = evidence complete, fix what
the gates named; `3` = evidence missing, fix the environment first
(`specgit doctor`). Never present exit `3` as success.
- Keep the `Closes #n` references in the PR body intact; after changing
the PR body, head branch, or CI, re-run `specgit finish`. Never
bypass or reconfig a required check to make acceptance pass.
- Forge evidence flows through the user's authenticated CLI session only
(`gh` / `glab`): never read, log, or pass around tokens.
<!-- specgit:block:end -->

## Tool-call discipline (hard rules)
Expand Down
Loading
Loading