A self-contained opencode project that helps you turn recent work — spread across several other projects — into bilingual (English + Italian) blog articles for a Tailwind/Next.js MDX blog. It only observes the projects it writes about; it never modifies them.
Two manually-invoked primary agents live here:
- topic-extractor — a read-only analyst that surfaces candidate topics from your recent git history and opencode session transcripts.
- blog-writer — drafts a bilingual MDX article from a chosen topic, iterating with you, and publishes it on explicit command.
- git on your
PATH. - sqlite3 on your
PATH. - The opencode session database at
~/.local/share/opencode/opencode.db.
Verify read-only access to the database before first use:
sqlite3 -readonly ~/.local/share/opencode/opencode.db ".tables"If that lists tables (including session), you're ready. If the file does
not exist, open opencode at least once so the database is created, or adjust
your setup so the DB lives at that path.
| Path | Purpose |
|---|---|
inputs/ |
Candidate-topic files (topics-YYYY-MM-DD.md) + the published-topics.md ledger |
drafts/ |
Article drafts (<slug>.mdx, draft: true) |
published/ |
Published articles (moved here on your command) |
CONVENTIONS.md |
Your writing style/voice (authored in step 03) |
tracked-projects.txt |
Optional allowlist of project directories |
.opencode/ |
Agents and the permission config (opencode.json) |
Invoke each agent manually from inside this project (there is no automation or scheduling).
Ask it to extract topics, e.g. "extract topics from last week". It:
- Determines which projects to consider (see the allowlist below).
- Reads git commit history and opencode session transcripts
(
sqlite3 -readonly, filtered on thetime_updatedcolumn), correlates them by timestamp, and redacts obvious secrets. - Skips any topic you have already published an article about (matched by
topic_keyvia theinputs/published-topics.mdledger and published articles' frontmatter), so re-running it does not resurface finished work. Drafts do not count — only published articles. - Scores each candidate across five dimensions (Reader appeal, Technical depth,
Storytelling, Uniqueness, Publishability), orders them by the resulting
Overall score, and writes a DRAFT topics file to
inputs/topics-YYYY-MM-DD.md(named by the run date — so same-week re-runs don't collide — with the ISO week, window, and aGenerated:timestamp recorded inside). On your approval it flipsStatus: DRAFTtoStatus: FINAL.
It never writes an article itself.
Ask it to write about a topic, e.g. "write about topic 2" or a free-text
subject. It reads CONVENTIONS.md, drafts the English body first and
iterates with you, then translates to Italian and assembles a single
bilingual drafts/<slug>.mdx with frontmatter draft: true and a stable
topic_key. On your explicit "publish <slug>" command it flips
draft: false, moves the file to published/<slug>.mdx, and records the
topic_key in inputs/published-topics.md so the topic-extractor won't
resurface it. It never edits source projects and never commits or pushes.
Editing tracked-projects.txt is optional:
- Present and non-empty (has at least one path line): it is an allowlist — only those project directories are considered.
- Absent or comment-only: the topic-extractor auto-discovers candidate projects from the opencode DB and confirms the list with you before extracting.
Use the allowlist to exclude sensitive projects.
A single, repeatable end-to-end acceptance procedure lives in
docs/acceptance.md. It maps every acceptance criterion
to a check, distinguishing automated mechanical checks from the manual
agent-conversation script.
Run the whole mechanical suite from the project root:
bash tests/run_all.shOr run individual verifiers:
bash tests/scaffold_check.sh # step 01 scaffold + permissions structure
bash tests/extract_topics_check.sh # step 02 topic-extractor + extract-topics
bash tests/conventions_check.sh # step 03 conventions template + interview
bash tests/blog_writer_check.sh # step 04 blog-writer + write-blog-article
bash tests/acceptance_check.sh # step 05 system-wide static + coverage
bash tests/permission_check.sh # step 05 permission/destructive-command denial
bash tests/redaction_check.sh # step 05 deterministic redaction/flaggingThe permission-boundary and destructive-command scenarios can additionally be proven against the live runtime (opt-in, consumes model calls):
ACCEPTANCE_RUNTIME=1 bash tests/permission_check.shThe permission boundary for both agents lives in
.opencode/opencode.json:
- Both agents may read anywhere (to inspect source projects and the DB).
topic-extractormay edit only withininputs/**.blog-writermay edit only withindrafts/**,inputs/**, andpublished/**.- Both may run
git *andsqlite3 * -readonly *;git push,git commit --amend, andrm -rfare denied. external_directoryallows reading under your home directory and the opencode DB directory, while denying well-known secret stores (~/.ssh,~/.aws,~/.gnupg,~/.config, and any.env).
opencode's configuration is validated strictly and is not hot-reloaded.
After editing .opencode/opencode.json (or the agent files), quit and
restart opencode so the changes take effect.
If opencode reports a ConfigInvalidError and you need to edit the config
from inside opencode, start it with the project config disabled:
OPENCODE_DISABLE_PROJECT_CONFIG=1 opencodeFix .opencode/opencode.json, then restart normally.
The permission boundary depends on how opencode resolves multiple matching
patterns within a single {pattern: action} object. Per the official
opencode documentation, rules are evaluated by pattern match, with the
last matching rule winning. The recommended layout — which
.opencode/opencode.json follows — puts the catch-all "*" (and "**")
rule first and more specific rules after it. Additional facts that
shape this config:
- Wildcards are simple:
*matches zero or more of any character (including path separators) and?matches exactly one; everything else is literal. ~/$HOMEat the start of a pattern expands to your home directory.- Agent permissions are merged with the global config, and agent rules take precedence.
external_directorycovers any tool touching paths outside the project. Entries there inherit workspace defaults (soreadstays allowed unless denied); the explicit denies above block the sensitive stores.
Because ordering is significant, keep the catch-all deny/ask rules first and
the narrow allow/deny rules last when editing .opencode/opencode.json.