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
53 changes: 50 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
Expand All @@ -25,11 +26,57 @@ jobs:
- name: Typecheck
run: bun run typecheck

- name: Unit tests (engine, event log, config injection, guards)
run: bun test

- name: Build
run: bun run build

- name: Smoke test
- name: Engine end-to-end (real git worktrees, real evidence capture)
run: bun run e2e

- name: Plugin integration (built bundle, real hook shapes)
run: bun run integration

- name: Installer smoke test
run: bun run smoke

- name: Verify templates shipped
run: bun -e "import { existsSync } from 'node:fs'; const files = ['dist/cli/templates/orchestrator.md', 'dist/cli/templates/patterns/long-proof.md', 'dist/cli/templates/commands/teamwork.md', 'dist/cli/templates/prompts/orchestrator.txt']; for (const f of files) { if (!existsSync(f)) { console.error('Missing: ' + f); process.exit(1); } }"
- name: Verify templates and engine modules shipped
shell: bash
run: |
for f in \
dist/cli/templates/sentinel.md \
dist/cli/templates/patterns/long-proof.md \
dist/cli/templates/commands/teamwork.md \
dist/cli/templates/prompts/sentinel.txt \
dist/engine.js \
dist/events.js \
dist/policy.js ; do
if [ ! -f "$f" ]; then echo "Missing: $f"; exit 1; fi
done
echo "all expected build outputs present"

pack-install:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: oven-sh/setup-bun@v2
with:
bun-version: latest

- run: bun install --frozen-lockfile

- name: Build and pack
run: bun run build && npm pack

- name: Install the packed tarball into a clean project
shell: bash
run: |
mkdir -p /tmp/packtest && cd /tmp/packtest
npm init -y >/dev/null
npm install "$GITHUB_WORKSPACE"/opencode-teamwork-*.tgz >/dev/null
test -f node_modules/opencode-teamwork/dist/engine.js
test -f node_modules/opencode-teamwork/dist/cli/templates/patterns/long-proof.md
test -f node_modules/opencode-teamwork/SKILL.md
node -e "const p=require('./node_modules/opencode-teamwork/package.json');console.log('packed package ok:',p.version);if(!p.files.includes('SKILL.md'))process.exit(1)"
77 changes: 77 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,83 @@ All notable changes to opencode-teamwork are documented here. The format
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/).

## [0.3.0] — 2026-09-19

**The run loop moved into code.** Until now the "DAG engine", worktree
isolation, cost tracking and checkpoint/resume were instructions in a prompt:
`src/worktree.ts`, `src/cost.ts`, `src/state.ts` and `src/artifacts.ts` were
imported by nothing, so none of those guarantees were enforced by the runtime.

### Added
- **`src/engine.ts`** — a real scheduler: plan validation (cycles, dangling
dependencies, duplicate ids, unknown topologies), topological dispatch waves,
a concurrency cap, attempt accounting, per-round model escalation, dead-letter
after `maxRounds`, and budget enforcement that refuses dispatch at the cap.
- **`src/events.ts`** — append-only `events.jsonl` with a sha256 hash chain.
`state.json` is now a *derived* snapshot; `deriveSession()` replays a run and
`verifyChain()` refuses a tampered log.
- **Engine tools**: `teamwork_plan`, `teamwork_dispatch`, `teamwork_verify`,
`teamwork_status`, `teamwork_resume`.
- **`src/guard.ts`** — runtime role enforcement. Read-only roles
(verifier, falsifier, scout) cannot call write tools even if a host build
ignores part of the permission block.
- **`src/policy.ts`** — one source of truth for topology names, model ladders,
required checks and budget. A test asserts every topology resolves to a
pattern file.
- **Verification evidence**: checks now carry `cmd`, `exitCode`, `stdoutSha256`
and `durationMs`. A PASS with no executed check, or with a check that
contradicts its exit code, is rejected and does not count as a round.
- **Command flags parsed in code** — `--topology`, `--budget`, `--concurrency`,
`--session`, with an unknown topology reported instead of accepted.
- **Long runs survive compaction**: the `experimental.session.compacting` hook
re-injects the plan, task states and budget from the event log.
- **Tests**: 26 unit tests (`bun test`), a real end-to-end run against a git
repository (`bun run e2e`), and a built-bundle integration test that drives the
actual hook shapes OpenCode sends (`bun run integration`). CI runs all three on
Linux, macOS and Windows, plus a `npm pack` → clean-install check.
- `docs/self-improvement.md` — the design for improving the *policy* (not the
models) from the event log, with the guard metrics and failure modes.

### Fixed
- **Agent frontmatter was inert.** The plugin passed each agent file's whole
markdown — frontmatter included — as the `prompt` string, so `mode` defaulted
to `all`, `permission: edit: deny` on the verifier did nothing, `temperature`
was ignored, and `hidden` is not a config key at all. Frontmatter is now
parsed and injected as real `AgentConfig` keys.
- **The plugin clobbered per-role models.** `config.agent[name] = { prompt }`
replaced whatever the installer had written, discarding the model map. Config
is now spread-merged, and a test asserts the user's model survives.
- **Three incompatible topology vocabularies.** The pattern files said
`long-proof` / `distributed-coding` / `document-review` while the sentinel
prompt and `state.ts` said `proof` / `large-swarm` / `doc-review` — four of
five names in the routing table matched no file.
- **Pattern files never reached the model.** `getAllPatterns()` had no call
sites and no prompt referenced `patterns/`. Orchestrating agents now get a
generated index with absolute paths.
- **A template language OpenCode cannot expand.** `{{if eq .sessionId ""}}` in
the `/teamwork` command was passed to the model as literal text (only
`$ARGUMENTS`, `$1..$N`, `` !`cmd` `` and `@file` are supported), and
`$ARGUMENTS` appeared twice, duplicating every request.
- **Shell injection surface in the worktree manager.** Git was invoked by
building command strings from model-produced names. All git calls now use
argv arrays with `shell: false`, and agent/session names are validated.
- **`cleanupSession` never cleaned up.** It ran `git worktree remove` against
the worktrees' *parent* directory (never a worktree), swallowed the failure,
then deleted directories git still had registered. It now removes real
worktrees, deletes the session's branches, prunes, and verifies.
- **`dist/` layout did not match `package.json` exports.** `build:support`
emitted `dist/src/*.js` while `exports` pointed at `dist/*.js`.
- **`SKILL.md` now ships.** It was in the repo but not in `files`.
- Leaf agents get `task: deny`; only the sentinel and v1 orchestrator can spawn
subagents, so a worker can no longer fan out its own swarm.

### Changed
- `src/state.ts` is a compatibility shim over the event log;
`saveSessionState()` now throws with guidance rather than accepting a
model-authored snapshot.
- `bun run verify` is the single gate: typecheck → unit → build → smoke → e2e →
integration. `prepublishOnly` runs it.

## [0.2.1] — 2026-08-28

CLI UX fix.
Expand Down
Loading
Loading