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
46 changes: 22 additions & 24 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
# Modula — Dashboard repo (Rust)
# Modula: dashboard repo (Rust)

One-binary desktop app for the Modula AI software factory. Structured state lives
in a single global SQLite DB at `~/.modula/db.sqlite`; markdown artifacts (spec
folders, logs, wiki) live under `~/.modula/<workspace>/`.
One-binary desktop app for the Modula AI software factory.

The engine serves gRPC over a local IPC socket — no TCP port. The desktop app and
the `modula` CLI are both clients of it.
- Structured state lives in one global SQLite DB at `~/.modula/db.sqlite`.
- Markdown artifacts (spec folders, logs, wiki) live under `~/.modula/<workspace>/`.
- The engine serves gRPC over a local IPC socket, not a TCP port. The desktop app and the `modula` CLI are both its clients.

## Read before editing

- [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) — crate layout, where things
live, and the rules that govern edits. **Start here.**
- [`docs/PLUGINS.md`](docs/PLUGINS.md) — the plugin seam and the
open-source/proprietary split (`plugins/remote`, `../modula-plugins`).
- [`docs/CLI.md`](docs/CLI.md) — the `modula` command reference.
- [`docs/MODULA.md`](docs/MODULA.md) — requirements, packaging, configuration.
| Doc | Covers |
| --- | --- |
| [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) | Crate layout, where things live, and the rules for edits. **Start here.** |
| [`docs/PLUGINS.md`](docs/PLUGINS.md) | The plugin seam and the open-source/proprietary split (`plugins/remote`, `../modula-plugins`). |
| [`docs/CLI.md`](docs/CLI.md) | The `modula` command reference. |
| [`docs/MODULA.md`](docs/MODULA.md) | Requirements, packaging, configuration. |

## Run / dev
## Run and develop

```bash
bash scripts/dev.sh # engine + Vite + Tauri shell
Expand All @@ -28,25 +27,24 @@ cargo test --workspace -- --test-threads=1 # single-threaded: each E2E test bo
cargo tauri dev --manifest-path apps/desktop/src-tauri/Cargo.toml # shell only
```

## Before committing
## Pass CI before committing

Run what CI gates on, or the PR will fail:
Run what CI gates on, or the PR fails.

- Rust: `cargo fmt --all`, then `cargo clippy --workspace --all-targets -- -D warnings`
- Frontend (`apps/desktop`): `pnpm exec biome check --write .`, then `pnpm build`
- Rust: `cargo fmt --all`, then `cargo clippy --workspace --all-targets -- -D warnings`.
- Frontend (`apps/desktop`): `pnpm exec biome check --write .`, then `pnpm build`.

### Commit messages
### Write the commit message

- Conventional Commits: `<type>[optional scope]: <description>` (`feat`, `fix`,
`docs`, `refactor`, `chore`, `test`, `perf`, `ci`).
- One line; add a short body only when the *why* isn't obvious from the diff.
- Use Conventional Commits: `<type>[optional scope]: <description>`. Types: `feat`, `fix`, `docs`, `refactor`, `chore`, `test`, `perf`, `ci`.
- Write one line. Add a short body only when the diff doesn't show the *why*.
- No `Co-Authored-By` trailer.

```
```text
feat(conversations): derive chat title from first user message
fix(spawn): validate provider config_dir before launching child
```

### Pull requests
### Write the pull request

Concise: what changed and why, not a play-by-play. No co-author line.
State what changed and why, not a play-by-play. No co-author line.
49 changes: 39 additions & 10 deletions crates/modula_services/templates/skills/ai-wiki.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,41 @@
## Skill: AI Wiki

The workspace `wiki/` is an agent-maintained knowledge base of durable codebase
facts — architecture, conventions, quirks — **never** task-specific commentary.

- **Before working**, read `wiki/index.md` and any pages relevant to the
projects/components you'll touch. Cite pages as `[[link]]` in specs.
- **When your work surfaces a durable fact** that contradicts, fills a gap in,
or extends the wiki: **read `wiki/SCHEMA.md` first**, then update the relevant
page(s), keep `wiki/index.md` in sync, and append an entry to `wiki/log.md`.
- Keep it durable: if a fact only matters to this task, it belongs in the spec
or a thread comment, not the wiki.
The workspace `wiki/` is an agent-maintained knowledge base of durable codebase facts: architecture, conventions, quirks. It **never** holds task-specific commentary.

- **Before you work**, read `wiki/index.md` and the pages for the projects and components you'll touch. Cite pages as `[[link]]` in specs.
- **When you find a durable fact** that contradicts, fills a gap in, or extends the wiki, **read `wiki/SCHEMA.md` first**. Then update the relevant pages, keep `wiki/index.md` in sync, and append an entry to `wiki/log.md`.
- **Keep it durable.** A fact that matters only to this task belongs in the spec or a thread comment, not the wiki.

### Writing style

Agents read wiki pages to act. Write terse, factual, instructional pages.

**Page shape**

- H1 title in sentence case, then a 1–2 sentence intro stating what the page covers and when it's relevant.
- Order sections: what it is → reference → how to → when it breaks. Skip any that don't apply.
- Name sections by task: "Run the migrations", not "Migrations". Don't restate the heading in the first sentence. Add the next fact.
- Keep one topic per page. Split a page when it covers two things, and link the halves.

**Sentences**

- Lead with the fact. Cut the lead-in ("In order to understand…").
- Imperative and second person: "Run `make db`", not "We run `make db`" or "`make db` should be run".
- One idea per sentence. Split a rule from its exception.
- Cut filler: `simply`, `just`, `easily`, `powerful`, `robust`, `note that`, `it's important to`.
- No em dashes. Use a full stop, colon, or comma.

**Formatting**

- Numbered lists for ordered steps, bullets for everything else.
- Tables for anything that enumerates: flags, env vars, fields, states, exit codes.
- Code, paths, commands, and identifiers in backticks. Placeholders in ALL-CAPS: `modula task get TASK_ID`.
- Fenced code blocks carry a language tag.
- Link text describes the target. Link once, where the topic comes up.
- No callouts or alert boxes unless ignoring the point breaks something.

**Accuracy**

- Write only what you verified in code or by running it. If something is unknown, leave it out or mark it `unverified`.
- Show real commands and real output. Invented output goes stale unnoticed.
- Document the failure: error text, cause, fix. A happy-path-only page is half a page.
59 changes: 32 additions & 27 deletions crates/modula_services/templates/skills/engine-api.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,34 @@
## Skill: Engine CLI

All workspace state lives in the engine and is reached through the `modula` CLI.
The engine URL and the current workspace are detected automatically — you never
pass them.

Reads print formatted text (read it directly; do not parse as JSON):

modula task list # all tasks
modula task get <task-id> # one task with its variants
modula config get # pipeline keys, projects, providers, agents
modula comment list <task-id> # a task's thread

Writes take a single JSON body argument:

modula task create '{ … }'
modula task patch <task-id> '{"status":"<pipeline-key>"}'
modula variant create <task-id> '{"count":2}'
modula variant patch <variant-id> '{"status":"in_progress"}'
modula variant patch <variant-id> '{"action":"accept"}'
modula comment create <task-id> '{"author":"<you>","kind":"comment","content":"…"}'

Conventions:
- Fill `<task-id>` / `<variant-id>` from the **Inputs for this run** block in your
prompt. Ids are UUIDs minted by the engine — never invent them.
- Read valid pipeline keys from `modula config get`; never hardcode status keys.
- A non-zero exit means the call failed — stop and surface the error.
- Every write emits an event the dispatcher routes to the next agent; you never
spawn other agents yourself.
All workspace state lives in the engine. Reach it through the `modula` CLI. The CLI detects the engine URL and the current workspace, so never pass them.

### Read state

Reads print formatted text. Read it directly; don't parse it as JSON.

```sh
modula task list # all tasks
modula task get <task-id> # one task with its variants
modula config get # pipeline keys, projects, providers, agents
modula comment list <task-id> # a task's thread
```

### Write state

Writes take one JSON body argument.

```sh
modula task create '{ … }'
modula task patch <task-id> '{"status":"<pipeline-key>"}'
modula variant create <task-id> '{"count":2}'
modula variant patch <variant-id> '{"status":"in_progress"}'
modula variant patch <variant-id> '{"action":"accept"}'
modula comment create <task-id> '{"author":"<you>","kind":"comment","content":"…"}'
```

### Rules

- Take `<task-id>` and `<variant-id>` from the **Inputs for this run** block in your prompt. The engine mints ids as UUIDs. Never invent one.
- Read valid pipeline keys from `modula config get`. Never hardcode status keys.
- A non-zero exit means the call failed. Stop and report the error.
- Every write emits an event, and the dispatcher routes it to the next agent. Never spawn agents yourself.
55 changes: 25 additions & 30 deletions crates/modula_services/templates/skills/specs.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,29 @@
## Skill: Specs

Spec folders are the durable record of a task's design and progress. They live
under the workspace root, named by human-readable slug (never UUIDs):
A spec folder is the durable record of one variant's design and progress. It lives under the workspace root and is named by slug, never by UUID.

```
```text
specs/<task-slug>/v<position>/
phases.md # variant overview + phase checklist (source of truth)
phase-1-plan.md # scope / approach / notes for phase 1
phases.md # variant overview and phase checklist (source of truth)
phase-1-plan.md # scope, approach, and notes for phase 1
phase-1-task.md # actionable checklist for phase 1
phase-2-plan.md
phase-2-task.md
...
```

`<task-slug>` is the task's external id + title, slugified (e.g.
`mod-0001-some-new-adjustment`); `<position>` is the variant's 1-based position.
Your run's prompt names the exact folder ("This run's spec folder is …") — use
that path; don't construct it yourself. `phases.md` lists phases as `- [ ]`
(pending) / `- [x]` (done) and names every project under "Projects touched".

### Templates
- `<task-slug>` is the task's external id and title, slugified: `mod-0001-some-new-adjustment`.
- `<position>` is the variant's 1-based position.
- Your prompt names the exact folder ("This run's spec folder is …"). Use that path. Don't construct it yourself.
- `phases.md` marks phases `- [ ]` (pending) or `- [x]` (done) and lists every project under "Projects touched".

`phases.md`:
### Write `phases.md`

```markdown
# <task title>

## Problem
What is broken / missing / requested. Quote the task.
What is broken, missing, or requested. Quote the task.

## Approach
The high-level approach this variant takes. One paragraph.
Expand All @@ -37,11 +33,11 @@ Names must match `projects[].name` in `/config`. If none, say so and stop.

- project: <name>
changes:
- path/to/file — what changes
- path/to/file: what changes

## Phases
- [ ] phase-1 — <one-line summary>
- [ ] phase-2 — <one-line summary>
- [ ] phase-1: <one-line summary>
- [ ] phase-2: <one-line summary>

## Risks & tradeoffs
- vs other variants: …
Expand All @@ -52,13 +48,13 @@ Names must match `projects[].name` in `/config`. If none, say so and stop.
- integration / manual: …
```

`phase-N-plan.md`:
### Write `phase-N-plan.md`

```markdown
# <TASK> — <variant> — phase-N — <short title>
# <TASK> / <variant> / phase-N: <short title>

## Scope
What this phase delivers; what it intentionally defers.
What this phase delivers and what it defers.

## Approach
One paragraph.
Expand All @@ -67,22 +63,21 @@ One paragraph.
Implementation hints, edge cases, references.
```

`phase-N-task.md`:
### Write `phase-N-task.md`

```markdown
# <TASK> — <variant> — phase-N — Tasks
# <TASK> / <variant> / phase-N: Tasks

- [ ] <step 1>
- [ ] <step 2>
- [ ] Lint / typecheck / tests pass
- [ ] Lint, typecheck, and tests pass
- [ ] Self-review diff
```

### Ownership
### Who edits what

- `phases.md` — the worker checks phases off and appends "Done" / "Rework round
N" sections; never rewrite the researcher's design.
- `phase-N-plan.md` — append-only for the worker (add a `## Deviations` section
if needed).
- `phase-N-task.md` — the worker mutates freely: tick items, add discovered
sub-tasks, strike abandoned ones with a one-line reason.
| File | Worker may |
| --- | --- |
| `phases.md` | Tick phases and append "Done" or "Rework round N" sections. Never rewrite the researcher's design. |
| `phase-N-plan.md` | Append only. Add a `## Deviations` section if needed. |
| `phase-N-task.md` | Edit freely: tick items, add discovered sub-tasks, strike abandoned ones with a one-line reason. |
87 changes: 56 additions & 31 deletions crates/modula_services/templates/skills/tasks.md
Original file line number Diff line number Diff line change
@@ -1,46 +1,71 @@
## Skill: Tasks, Variants & Comments

### Reading tasks and variants
### Read tasks and variants

modula task get <task-id> # one task with its variants
modula variant get <variant-id> # one variant (status, position) + owning task
modula comment list <task-id> # the task thread, then each variant's thread
```sh
modula task get <task-id> # one task with its variants
modula variant get <variant-id> # one variant (status, position) and its owning task
modula comment list <task-id> # the task thread, then each variant's thread
```

A task carries human-owned fields (`approved`, `max_variants`, `worktree`) and a
`variants[]` block. Each variant has a `status` that drives the pipeline. Read
the printed `status:`, `approved:`, and per-variant lines directly.
A task carries human-owned fields (`approved`, `max_variants`, `worktree`) and a `variants[]` block. Each variant's `status` drives the pipeline. Read the printed `status:`, `approved:`, and per-variant lines directly.

### Registering variants (researcher only)
### Register variants (researcher only)

modula variant create <task-id> '{"count":2}'
```sh
modula variant create <task-id> '{"count":2}'
```

The engine mints the UUIDs and prints the created variants (id + position). A
freshly registered variant has NO status and is NOT workable until promoted.
The engine mints the UUIDs and prints each created variant's id and position. A new variant has **no status** and is not workable until promoted.

### Variant status
### Set a variant's status

# Set a status directly.
modula variant patch <variant-id> '{"status":"in_progress"}'
```sh
# Set a status directly.
modula variant patch <variant-id> '{"status":"in_progress"}'

# Code-reviewer applies a verdict via an action (not a raw status).
modula variant patch <variant-id> '{"action":"accept"}'
# or '{"action":"rework"}'
# Code-reviewer applies a verdict through an action, not a raw status.
modula variant patch <variant-id> '{"action":"accept"}'
modula variant patch <variant-id> '{"action":"rework"}'
```

`variant patch` takes only the variant id — the CLI resolves the owning task.
`variant patch` takes only the variant id. The CLI resolves the owning task.

### Comments / thread entries
### Post a comment

Threads are **append-only**. Post entries with an `author`, a `kind`, and
`content`. Variant-scoped entries carry `variant` + `round`; task-scoped entries
omit `variant`.
Threads are **append-only**. Every entry has an `author`, a `kind`, and `content`. Variant-scoped entries add `variant` and `round`. Task-scoped entries omit `variant`.

modula comment create <task-id> \
'{"author":"<you>","kind":"comment","variant":"<variant-id>","round":N,"content":"…"}'
```sh
modula comment create <task-id> '{"author":"<you>","kind":"comment","variant":"<variant-id>","round":N,"content":"…"}'
```

`kind` values:
- `comment` — an observation or note.
- `question` — a question for the human (code-reviewer must post at least one).
- `verdict` — a decision; requires `verdict` set to the agent's allowed values
(e.g. `ACCEPT` / `REQUEST_CHANGES` for code-reviewer, `APPROVE` / `KICK_BACK`
for reviewer; `KICK_BACK` requires `affected_variants`).
- `rework` — the worker's summary of what a rework round fixed.
| `kind` | Use |
| --- | --- |
| `comment` | An observation or note. |
| `question` | A question for the human. Code-reviewer must post at least one. |
| `verdict` | A decision. Set `verdict` to a value your role allows: `ACCEPT` or `REQUEST_CHANGES` for code-reviewer, `APPROVE` or `KICK_BACK` for reviewer. `KICK_BACK` requires `affected_variants`. |
| `rework` | The worker's summary of what a rework round fixed. |

#### Writing style

Humans skim threads. Write each entry to be read in seconds.

- **Lead with the point.** The first line is the finding, question, or decision. No preamble ("I reviewed…", "After looking at…"), no closing recap.
- **One point per entry.** Two unrelated findings are two entries.
- **Bullets over paragraphs.** Numbered list only for ordered steps. Keep formatting flat: no headings, tables, or nested lists. Bold only the single fact the reader must not miss.
- **Short, direct sentences.** Imperative and active voice: "Rename `foo`", not "It might be worth considering renaming `foo`".
- **Cut filler.** Drop `simply`, `just`, `clearly`, `robust`, `note that`, `it's important to`, and hedges like `I think` or `it seems`.
- **Be concrete.** Cite `file:line` and quote only the lines that matter in a fenced block. Put code, paths, and commands in backticks. Give the fix, not only the problem.
- **State only what you verified.** Don't guess at causes or behavior. If you didn't check something, say so in one line.
- **Add new information only.** Don't restate the task, the diff, or earlier entries. Reference them instead.
- **Make questions answerable.** Ask one question and offer the options: "Keep the old endpoint? (a) yes, deprecate later (b) no, remove now".
- **No em dashes.** Use a full stop, colon, or comma.

Before:

> After carefully reviewing the changes in this variant, I noticed that there might potentially be an issue with how errors are handled in the sync path. It seems like the error is simply being swallowed, which could make debugging harder down the line, so it would probably be good to consider logging it.

After:

> `sync.rs:88` swallows the error from `fetch()`.
> - Log it with `tracing::warn!` or return it.
Loading
Loading