docs(novel): switch the pack to the container-vault layout
Some checks are pending
Create Release / Check if Version Bump PR Merged (push) Waiting to run
Create Release / Create Git Tag and GitHub Release (push) Blocked by required conditions
Post-Merge Version Bump / Check if Version Bump Needed (push) Waiting to run
Post-Merge Version Bump / Create Version Bump PR (push) Blocked by required conditions

One Obsidian vault holds every book; each book is a subfolder and is the opencode
workspace. The pack is shared at the vault root and inherited by walking up, so a
book no longer gets its own .opencode copy:

- SCHEMA §1 rewritten: container vault -> book folders, shared .opencode + docs
- init.md: scaffolds only the book (AGENTS.md, DATA/, 01/), checks that a shared
  pack is reachable, and no longer copies agent/command files into the project
- PACK-NOTES: layout diagram + a headless section documenting the deny-based
  permission model
This commit is contained in:
Claus Lohmar 2026-10-16 23:36:04 +01:00
parent 84d259edda
commit 4b9a535d6c
3 changed files with 87 additions and 51 deletions

View file

@ -6,15 +6,18 @@ description: "Initialize a new novel project — interactive scaffolding wizard"
Initializes a new novel/series project with the OpenNovelWriter structure. Initializes a new novel/series project with the OpenNovelWriter structure.
## Concept: workspace = book ## Concept: one vault, one folder per book
One book (or series) = one **workspace** = one vault folder. The user first All books live as folders inside one container vault (e.g. "Novels"). A book folder
creates and names a vault (e.g. "Die Kessler Zwillinge") in Obsidian, then opens **is** the workspace: the author creates the folder, opens it as the opencode
that folder as a workspace in opencode, and runs `/init` THERE. workspace, and runs `/init` THERE.
`/init` scaffolds **into the current directory** — it does NOT create a nested `/init` scaffolds **into the current directory** — it does NOT create a nested
subfolder. The vault's own name is the project name. The "series title" question subfolder. The folder's name is the project name; the "series title" question below
below is for metadata (and may differ from the folder name), not for creating a folder. is metadata (and may differ from the folder name).
The agent pack (`.opencode/`) is **shared and inherited** from the vault root.
Never create a copy inside a book folder.
## Workflow ## Workflow
@ -27,6 +30,11 @@ The current directory IS the book's workspace. Confirm the folder name with the
author (it is the project/series name). Do NOT create a subfolder — scaffold author (it is the project/series name). Do NOT create a subfolder — scaffold
directly into the current directory. directly into the current directory.
Then confirm the **shared pack is reachable**: an `.opencode/` directory must exist
in an ancestor directory (the container vault root). opencode finds it by walking
up. If there is none, STOP and tell the author to set up the shared pack — never
seed a local `.opencode/` in the book folder.
### Step 1: Ask the writing language (always first) ### Step 1: Ask the writing language (always first)
> "Which language will you write in? (e.g. Deutsch, English, Português)" > "Which language will you write in? (e.g. Deutsch, English, Português)"
@ -61,11 +69,8 @@ Scaffold **into the current directory** (the workspace/vault the author already
created and named): created and named):
``` ```
. (the workspace/vault, named by the author) . (the book folder inside the container vault)
├── .opencode/ ├── AGENTS.md (this book's metadata; from the template)
│ ├── agent/ (OpenNovelAgent + subagents, copied from the tool)
│ ├── command/ (init, chapter, translate)
│ └── opencode.json
├── 01/ (one per book, numbered; title optional, renamed later) ├── 01/ (one per book, numbered; title optional, renamed later)
│ ├── DE/ (one folder per publication language, ISO code) │ ├── DE/ (one folder per publication language, ISO code)
│ ├── EN/ │ ├── EN/
@ -78,10 +83,11 @@ created and named):
│ ├── timeline.yaml.md │ ├── timeline.yaml.md
│ ├── glossary.yaml.md │ ├── glossary.yaml.md
│ └── style.md │ └── style.md
├── AGENTS.md ├── README.md
└── README.md
``` ```
`.opencode/` is deliberately **absent** here — the pack is shared at the vault root.
- **Book folders:** numbers only (`01`, `02`). Titles are added later by renaming. - **Book folders:** numbers only (`01`, `02`). Titles are added later by renaming.
- **Language folders:** ISO codes only (`DE`, `EN`, `PT`). - **Language folders:** ISO codes only (`DE`, `EN`, `PT`).
- **Images:** each book gets `images/prompts/` and `images/generated/`. - **Images:** each book gets `images/prompts/` and `images/generated/`.
@ -165,5 +171,6 @@ as chapters are written.
- Book/language folders ALWAYS as number resp. ISO code — never force a title. - Book/language folders ALWAYS as number resp. ISO code — never force a title.
- YAML keys English, values in the canonical language (see SCHEMA §4). - YAML keys English, values in the canonical language (see SCHEMA §4).
- No chapter files during init — they are created when writing. - No chapter files during init — they are created when writing.
- Copy the agent/command files from the tool repo (`.opencode/`) so the new - NEVER copy the agent/command files into the book folder. The pack is shared and
project runs standalone. inherited from the vault root; a local `.opencode/` forks the agents and drifts
out of date.

View file

@ -56,6 +56,36 @@ The three vaults are not redundant copies of one thing — they have distinct ro
- `AGENTS.md` in the Scruffy vault is a *filled-in instance* (real series title/author/genre), not the template — the placeholder version is committed as `templates/AGENTS.md`; instances stay in their vault. - `AGENTS.md` in the Scruffy vault is a *filled-in instance* (real series title/author/genre), not the template — the placeholder version is committed as `templates/AGENTS.md`; instances stay in their vault.
- `opencode.json` is committed pristine (`$schema` + `default_agent`). The permission block added to the Kessler vault (external_directory allow for `/Users/Shared/Novels/**`) is machine-local and deliberately **not** in the pack. - `opencode.json` is committed pristine (`$schema` + `default_agent`). The permission block added to the Kessler vault (external_directory allow for `/Users/Shared/Novels/**`) is machine-local and deliberately **not** in the pack.
## Layout this pack is built for
One container vault, one folder per book, **one** `.opencode` shared at the vault root:
```
/Users/Shared/Vaults/Novels/ # the vault (human interface: Obsidian)
├── .opencode -> /Users/Shared/NovelPack/.opencode # this pack, symlinked
├── docs -> /Users/Shared/NovelPack/docs # this SCHEMA
├── AGENTS.md # house rules
├── Scruffy/ # a book = a workspace
└── Kessler/ # another book
```
opencode walks up from the book folder to find `.opencode`, so books carry no copies.
(`/Users/Shared/NovelPack` exists because opencode writes `node_modules/` and a
`package.json` next to the agents it loads — pointing that at a plain pack directory
keeps both the vault and this repo clean.)
## Headless operation
The pack runs headless: `bash` is allowed by default in `OpenNovelAgent` and
`Importer` (it was `"ask"`, which opencode cannot prompt for in `opencode run`, so
every tool call was auto-rejected). Safety now comes from **explicit denies** of
irreversible commands — `rm -rf *`, `rm -rf /*`, `sudo *`, `mkfs*`, `diskutil *`,
`shutdown *`, `reboot*` — plus the existing `.env`/`.key`/`.secret`/`.git` edit denies.
`--dangerously-skip-permissions` is therefore no longer needed.
The other eight agents keep `bash: "deny"` on purpose: they are note-editing
subagents that use opencode's file tools and never need a shell.
## Sync direction ## Sync direction
The repo is the source of truth **for the pack**. Vaults pull pack-managed files from here: The repo is the source of truth **for the pack**. Vaults pull pack-managed files from here:

View file

@ -8,48 +8,47 @@
## 1. Project Structure ## 1. Project Structure
**Concept: workspace = book.** One book (or series) = one folder = one workspace **Concept: one container vault, one folder per book.** A single Obsidian vault
= one Obsidian vault. The author creates and names the vault first (e.g. (e.g. "Novels") holds every book. Each book is a subfolder and *is* the workspace
"Die Kessler Zwillinge"), then runs `/init` *inside* it. The vault's own name is you open opencode in. The agent pack lives **once**, at the vault root, and is
the project name; `/init` scaffolds into it directly (no nested subfolder). inherited by every book — a book never carries its own `.opencode/`. The canonical
`docs/SCHEMA.md` likewise exists once, at the vault root.
The project directory is organized like a Git repository:
``` ```
[Series Title]/ # the workspace/vault (named by the author) Novels/ # the container vault (Obsidian)
├── .opencode/ # agent configuration (local, via /init) ├── .opencode -> /Users/Shared/NovelPack/.opencode # ONE shared agent pack
│ ├── agent/ ├── docs/ -> /Users/Shared/NovelPack/docs # canonical SCHEMA.md
│ ├── command/ ├── AGENTS.md # house rules that apply to every book
│ └── opencode.json ├── Scruffy/ # a BOOK = a workspace
├── 01/ # Book 1 (folder = number, title optional) │ ├── AGENTS.md # this book's metadata and rules
│ ├── DE/ # language folder (ISO-639-1 code) │ ├── DATA/ # this book's world
│ │ ├── Kapitel 001.md │ │ ├── wiki.yaml.md # world wiki (canonical)
│ │ ├── Kapitel 002.md │ │ ├── timeline.yaml.md # timeline (lore history + plot)
│ │ └── ... │ │ ├── glossary.yaml.md # translation glossary
│ ├── EN/ │ │ └── style.md # the author's writing style
│ │ ├── Chapter 001.md │ ├── 01/ # Book 1 (folder = number, title optional)
│ │ └── ... │ │ ├── DE/ # language folder (ISO-639-1 code)
│ └── images/ # illustrations for this book │ │ │ ├── Kapitel 001.md
│ ├── prompts/ # image prompts (Markdown, one per image) │ │ │ └── ...
│ └── generated/ # generated image files │ │ ├── EN/
├── 02/ # Book 2 │ │ │ ├── Chapter 001.md
├── DATA/ │ │ │ └── ...
│ ├── wiki.yaml.md # world wiki (canonical) │ │ └── images/ # illustrations for this book
│ ├── timeline.yaml.md # timeline (lore history + plot) │ │ ├── prompts/ # image prompts (Markdown, one per image)
│ ├── glossary.yaml.md # translation glossary │ │ └── generated/ # generated image files
│ └── style.md # the author's writing style │ └── 02/ # Book 2 of the same series
├── AGENTS.md # project rules (read by the agent) └── Kessler/ # another book — same shape, own AGENTS.md + DATA/
└── README.md # human overview
``` ```
### Multiple books in parallel ### Multiple books in parallel
Each book is a **separate workspace** (separate folder, separate opencode Each book is a **separate folder inside the one vault**. opencode is always started
workspace). The `OpenNovelAgent` is the same everywhere — what makes a workspace *inside* the book folder; it finds the shared pack by walking up to the vault root,
"the Scruffy book" vs. "the Kessler book" is its own `AGENTS.md` + `DATA/`. so books never duplicate the agents. The `OpenNovelAgent` is identical everywhere —
Switch workspaces to switch books; do not try to fit multiple books in one folder. what makes a workspace "the Scruffy book" vs. "the Kessler book" is its own
`AGENTS.md` + `DATA/`. Switch books by switching folders; never put two books in one
--- folder. (If several books live in one vault and a book needs to be verifiable on its
own, make the *book folder* a git repository — not the vault.)
## 2. Naming Conventions ## 2. Naming Conventions