Skip to content

Repository files navigation

opencode-todo-sync

Persistent, anti-hallucination Plan & Todo state for OpenCode - modeled on how Antigravity and GitHub Copilot handle planning.

One strict, versioned JSON file per project (.opencode/todo.json) is the single source of truth. It is re-read from disk before every AI reply, injected into the context verbatim before session compaction, and - most importantly - the AI can only change it through deterministic custom tools that validate the schema in code.

Context window = RAM (volatile). Filesystem = disk (persistent). This plugin moves your plan and task state out of the context window and onto disk.

License TypeScript


Why

  • Strict JSON > free-form Markdown. The AI cannot quietly rewrite your plan: every write is schema-validated by the plugin, not by the model's mood.
  • Zero hallucination on reset / compact / new session. State is never reconstructed from memory. A fresh session is built exclusively from what is really on disk.
  • Works in every project, no setup. Install once globally; the plugin creates the file, runs a deterministic repo audit, and gets out of the way.
  • File stays local. By default the todo file is appended to .git/info/exclude - no merge conflicts, no repo pollution.

How it works

OpenCode hook What the plugin does
Plugin init Creates .opencode/todo.json if missing (strict schema). On first run, runs todo_audit and seeds an initial plan from real repo facts.
experimental.chat.system.transform Re-reads the JSON from disk and injects it before every reply, together with operating rules. The AI is told the file is authoritative and must be changed only via the tools.
experimental.session.compacting Injects the real JSON verbatim into the compaction prompt, so the summary is generated from the file - never from the model's memory.
event (session.*, todo.updated) Re-validates / re-creates the file across session boundaries; best-effort sync of OpenCode's native todo tool into the JSON.
tool.execute.after When a test / build / lint / check bash command succeeds, auto-completes the in_progress task and records it in history.

Install

How local plugins are loaded

OpenCode auto-discovers plugin files (not folders) in ~/.config/opencode/plugins/*.ts / *.js. A cloned plugin folder is loaded through a path reference in the config: "plugin": ["C:/path/to/opencode-todo-sync"] (absolute path or file:// URL). The folder must contain a package.json with "main": "./src/index.ts" (already the case here), and the plugin needs @opencode-ai/plugin resolvable from ~/.config/opencode/node_modules.

Quick start (clone + command, copy-paste)

macOS / Linux (bash):

mkdir -p ~/.config/opencode/plugins ~/.config/opencode/command
git clone --depth 1 https://github.com/otontraore/opencode-todo-sync ~/.config/opencode/plugins/opencode-todo-sync
cp ~/.config/opencode/plugins/opencode-todo-sync/command/OTON.md ~/.config/opencode/command/OTON.md

# Register the folder + give it access to @opencode-ai/plugin
cat >> ~/.config/opencode/opencode.json <<'EOF'
{
  "$schema": "https://opencode.ai/config.json",
  "plugin": ["$HOME/.config/opencode/plugins/opencode-todo-sync"]
}
EOF
printf '{"name":"opencode-global","private":true,"dependencies":{"@opencode-ai/plugin":"^1.15.10"}}\n' > ~/.config/opencode/package.json
cd ~/.config/opencode && npm install

Windows PowerShell:

New-Item -ItemType Directory -Force "$env:USERPROFILE\.config\opencode\plugins","$env:USERPROFILE\.config\opencode\command" | Out-Null
git clone --depth 1 https://github.com/otontraore/opencode-todo-sync "$env:USERPROFILE\.config\opencode\plugins\opencode-todo-sync"
Copy-Item "$env:USERPROFILE\.config\opencode\plugins\opencode-todo-sync\command\OTON.md" "$env:USERPROFILE\.config\opencode\command\OTON.md"

# Register the folder + give it access to @opencode-ai/plugin
$pluginPath = "$env:USERPROFILE\.config\opencode\plugins\opencode-todo-sync"
@{ '$schema' = 'https://opencode.ai/config.json'; plugin = @($pluginPath) } | ConvertTo-Json | Set-Content "$env:USERPROFILE\.config\opencode\opencode.json" -Encoding utf8
'{ "name": "opencode-global", "private": true, "dependencies": { "@opencode-ai/plugin": "^1.15.10" } }' | Set-Content "$env:USERPROFILE\.config\opencode\package.json" -Encoding utf8
Push-Location "$env:USERPROFILE\.config\opencode"; npm install; Pop-Location

The PowerShell snippet overwrites opencode.json with the plugin entry - merge it into your existing config by hand if you already have one (just add the plugin array). Same for creating package.json if it already exists (add the dependency).

Prerequisites: git, Node.js 18+ (or Bun), OpenCode.

Then restart OpenCode.

Verify with the chat command:

/OTON

You should see Plugin opencode-todo-sync : integre et fonctionnel together with a project summary. /OTON stat adds the full report (last usage, last modified file, remaining todos, coherence verdict). The plugin creates and seeds .opencode/todo.json on first use in each project. You may rename the /OTON command by editing ~/.config/opencode/command/OTON.md (e.g. TODO.md) or adding your own command in the project's .opencode/command/ folder.

Per-project clone

Clone the plugin into the project's .opencode/plugin/opencode-todo-sync/ folder and register it in the project's opencode.json, then create .opencode/package.json for the dependency:

{
  "$schema": "https://opencode.ai/config.json",
  "plugin": [".opencode/plugin/opencode-todo-sync"]
}
{ "name": "project-opencode", "private": true, "dependencies": { "@opencode-ai/plugin": "^1.15.10" } }

Also copy command/OTON.md into .opencode/command/OTON.md. OpenCode runs dependency install at startup; npm install in .opencode/ also works.

From a Release zip (no git)

Download opencode-todo-sync.zip from the Releases page (tagged v*), unzip it into ~/.config/opencode/plugins/opencode-todo-sync/, then follow the config + package.json + install steps above. The zip contains src/ only - the plugin runs from source, no build step required.

From npm (after this project is published)

Add it to your global config so it applies to every project:

~/.config/opencode/opencode.json

{
  "$schema": "https://opencode.ai/config.json",
  "plugin": ["opencode-todo-sync"]
}

Or per-project in opencode.json:

{
  "$schema": "https://opencode.ai/config.json",
  "plugin": ["opencode-todo-sync"]
}

With options:

{
  "$schema": "https://opencode.ai/config.json",
  "plugin": [
    ["opencode-todo-sync", { "language": "fr", "autoAudit": true }]
  ]
}

Restart OpenCode after install. Config and plugins are loaded once at startup. Requires Node 18+ / Bun (OpenCode bundles its own).


First use in an existing project

  1. Open the project folder in OpenCode. Nothing else to do.
  2. The plugin creates .opencode/todo.json and - on the very first run - runs a deterministic audit (git ls-files, manifests, CI, Docker, structure) and seeds a proposed plan of pending tasks. todo_audit only ever reports real facts; it never invents anything.
  3. Review/confirm the plan (or run todo_audit again), then let the agent work through the tasks. Progress is written to disk every step of the way.

No manual setup. No configuration files in your repo.


Options

Option Type Default Description
filePath string .opencode/todo.json Location of the todo file (relative to the project root or absolute).
language "en" | "fr" "en" Language of the injected system rules and auto-generated audit tasks.
autoAudit boolean true Run the deterministic repo audit to seed an initial plan on first run.
autoComplete boolean true Auto-complete the in_progress task when a test/build/lint command succeeds.
commandPatterns string (regex alternation) "test|build|compile|lint|check|verify" Pattern matched against bash commands for autoComplete.
injectRules boolean true Inject the operating rules alongside the state every turn.
syncNativeTodo boolean true Best-effort sync of OpenCode's native todoread/todowrite into the JSON file.
ignoreMode "gitinfo" | "none" "gitinfo" gitinfo appends the file to .git/info/exclude (local only). none leaves the file visible to git.

Custom tools (the "lock")

The agent changes state only through these tools. Each validates input against the strict schema before writing.

Tool Purpose
todo_read Read the current plan, tasks (filterable by status / phase_id) and recent history.
todo_add Add one or more schema-validated tasks.
todo_update Update a task: status, priority, text, tags, labels, phase_id, estimates.
todo_done Mark a task completed, promote the next highest-priority pending task to in_progress, set current_focus, log history.
todo_audit Deterministic repo scan → proposed plan. apply: true replaces the plan/tasks with the proposal.
todo_stat Deterministic health report: last usage (mtime + last_updated), last modified file, remaining todos and a coherence / up-to-date verdict (schema, enums, unique ids, phase references, plan-sync) - used by /OTON stat, so the "everything is coherent" verdict is computed by code, never by the model's judgement.

Chat commands

Command What it does
/OTON Short report: calls todo_stat + todo_read, confirms the plugin is wired in, summarizes the project (stack, plan phase, current focus, task statuses).
/OTON stat Adds the full block: last usage (date/time), last modified file, all remaining todos (priority + phase), coherence verdict, up-to-date flag.

The command lives at command/OTON.md in this repo (and in ~/.config/opencode/command/OTON.md after the Quick start). Customize or rename it freely - add TODO.md variants, add your own arguments, or translate the prompt.

JSON schema

File: .opencode/todo.json. Versioned (schema_version: 1). Enums are fixed, so tooling can validate deterministically.

{
  "schema_version": 1,
  "metadata": {
    "project": "my-app",
    "branch": "main",
    "created_at": "2026-08-20T09:00:00.000Z",
    "last_updated": "2026-08-20T09:15:00.000Z",
    "current_focus": "Add user authentication",
    "stage": "implementing"              // planning | implementing | reviewing | done
  },
  "plan": [
    { "id": 1, "title": "Architecture & Audit", "status": "in_progress", "created_at": "...", "updated_at": "..." }
  ],
  "tasks": [
    {
      "id": 1,
      "text": "Explore the codebase structure and map the core modules",
      "status": "in_progress",            // pending | in_progress | completed | blocked
      "priority": "high",                  // low | medium | high
      "phase_id": 1,
      "tags": ["audit"],
      "labels": [],
      "created_at": "...",
      "updated_at": "...",
      "estimated_min": 30,
      "actual_min": null
    }
  ],
  "history": [
    { "ts": "2026-08-20T09:15:00.000Z", "event": "completed", "detail": "#1 Explore the codebase structure" }
  ]
}

The TypeScript types (TodoDocument, TodoTask, PlanPhase, …) are exported from opencode-todo-sync/schema so external tooling can validate or extend the file:

import { sanitizeDocument, isTodoDocument } from "opencode-todo-sync/schema"

Anti-hallucination guarantee

  1. The file is re-read from disk before every reply. Whatever the model "remembers" is irrelevant - it works from what is on disk.
  2. Compaction is fed the real file, verbatim, and is explicitly told not to rewrite it. The summary is a summary of facts.
  3. After a reset or compact, the fresh context is rebuilt only from the file. The plugin re-creates/repairs it on session.created / session.compacted if anything happened to it.
  4. todo_audit only emits real repo facts (files, manifests, CI, Docker, structure) as pending proposals. It never infers state from memory.
  5. Corrupted or deleted file → rebuilt from an empty, neutral schema. No hallucinated data is ever restored.
  6. Every write is validated in code. The AI cannot hand-write JSON; it must go through the todo_* tools.

Team usage

  • .opencode/todo.json is local-only by default (gitinfo mode) → no merge conflicts; every developer gets their own copy.
  • Want a shared board? Set ignoreMode: "none" and commit the file deliberately - every agent on the team then works from the same tracked plan.
  • Copy a plan across machines by copying the file itself.

Development

npm install        # or: bun install
npm run typecheck  # strict TypeScript check
npm run build      # compile to dist/
npm test           # smoke tests (node >= 18, uses tsx --test)

Releasing:

  • Push a v* tag (e.g. v0.1.0): the release workflow typechecks, tests, builds, assembles opencode-todo-sync.zip (src + dist + README + LICENSE) and creates a GitHub Release with the zip and npm tarball attached.
  • npm publishing uses the publish workflow and the NPM_TOKEN secret once the package is published at opencode-todo-sync.

Zero runtime dependencies.


License

MIT © 2026 OTON TRAORE

About

Persistent anti-gravity Plan & Todo sync for OpenCode. Strict JSON schema, deterministic custom tools (todo_read/add/update/done/audit), zero-hallucination session reset & compaction. Inspired by Antigravity and GitHub Copilot.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages