Doubtfully Useful LLM Ledger — a local web UI for monitoring and managing project planning files, paired with a set of Claude Code skills and hooks that enforce a structured idea-to-task workflow.
dull watches your project's planning documents (docs/, CLAUDE.md, AGENTS.md, task YAML files) and surfaces them in a browser-based UI in real time. Run it alongside a Claude Code session so both you and the agent share the same live view of project state.
Beyond the UI, the repo ships:
- Skills — reusable Claude Code behaviors you invoke with
/skill-name - Hooks — Node scripts that fire automatically on agent actions to enforce data integrity
npm install
npm start # serves on http://0.0.0.0:6768
# pick a port
DULL_PORT=4000 npm start
# point at a different project root
node bin/dull.js --root /path/to/my-projectCLI flags:
| Flag | Short | Default | Description |
|---|---|---|---|
--port |
-p |
6768 |
Port to listen on |
--root |
-r |
cwd |
Project root to watch |
--host |
0.0.0.0 |
Host to bind |
.claude/
settings.json # Claude Code permissions and hook config
skills/ # Skills loaded by Claude Code
decision-close/
decision-record/
idea-approve/
idea-capture/
idea-close/
idea-elaborate/
idea-review/
spec-approve/
spec-close/
spec-create/
spec-review/
status/
task-done/
task-load/
task-plan/
task-start/
task-tdd/
task-update/
hooks/ # Hook scripts (must be wired into settings.json)
post_write_idea_guard.js
post_write_task_validator.js
stop_task_guard.js
docs/
IDEAS.yaml # Idea registry
SPECS.yaml # Spec registry
TASKS.yaml # Task registry
DECISIONS.yaml # Decision registry
ideas/ # Per-idea detail files (IDEA-NNN.md)
specs/ # Per-spec detail files (SPEC-NNN.md)
tasks/ # Per-task detail files (DOMAIN-NNN.yaml)
decisions/ # Per-decision detail files (DEC-NNN.md)
Work flows through four stages, each with its own registry file and optional detail document.
IDEA → SPEC → TASK → DECISION
Raw captures of anything worth exploring. Status flow:
captured → elaborating → review → approved (archived)
→ rejected (archived)
→ parked (stays live)
captured— written down, no analysis yetelaborating— being fleshed out by human or agentreview— waiting for a human decisionparked— not now, maybe later
Detail files follow the template at docs/ideas/.template.md. Required sections: ## The Idea, ## Elaboration, ## Review Notes, ## Decision.
Approved ideas become specs — precise contracts for what will be built. Status flow:
draft → review → accepted (stays live, spawns tasks)
→ rejected (archived)
→ draft (sent back for revision)
→ parked (stays live)
Accepted specs remain in the live registry as a reference for the tasks they spawned.
Concrete units of work derived from specs (or created directly). Status values:
planning → ready → in-progress → done
→ blocked
→ abandoned
ID format: <DOMAIN>-<NNN> — e.g. AUTH-001, UI-003. Detail files carry full context: objective, acceptance criteria, work log, and handoff context written on completion.
Standing rules and choices that govern the project. Never deleted — only superseded or retired.
proposed → active → superseded
→ retired
Each decision has a single rule: field — one sentence, plain English, actionable.
Skills are Markdown files in skills/ that Claude Code loads when you invoke them. Invoke any skill with /skill-name in a Claude Code session.
| Skill | Description |
|---|---|
/idea-capture |
Record a new idea. Generates an ID, creates the detail file, and asks who owns the elaboration step. |
/idea-elaborate |
Begin or continue elaboration on a captured idea. Respects soft locks if another owner is active. |
/idea-review |
Present an elaborated idea to a human for a decision. Outcomes: approve, park, reject, abandon, or send back. |
/idea-approve |
Approve an idea and promote it to tasks. Runs /task-plan for each task, archives the idea from the live index. |
/idea-close |
Close an idea as parked, rejected, or abandoned. Parked stays live; rejected/abandoned move to archive. |
| Skill | Description |
|---|---|
/spec-create |
Create a new spec from a parent idea or a description. Reads the spec template and adds an entry to docs/specs.yaml. |
/spec-review |
Present a spec to a human for a decision. Outcomes: accept, send back to draft, park, or reject. |
/spec-approve |
Accept a spec and spawn or update its tasks. Spec stays in the live index as a reference. |
/spec-close |
Close a spec as parked, rejected, or abandoned. |
| Skill | Description |
|---|---|
/task-plan |
Plan a new task. Reads the task template, creates a registry entry and detail file. Run before any implementation. |
/task-start |
Start a task. Validates dependencies, loads handoff context, transitions status to in-progress. Run before touching code. |
/task-load |
Load and summarize a task's full context without changing its status. |
/task-update |
Record a progress note, mark blocked, or add references during an active task session. |
/task-tdd |
TDD workflow: loads and starts a task, runs red-green-refactor loop with progress updates, stops at review for human sign-off. |
/task-done |
Close a task. Validates acceptance criteria, writes handoff context, updates status to done. |
| Skill | Description |
|---|---|
/decision-record |
Record a new architectural or process decision. Adds an entry to docs/decisions.yaml and creates a detail file. |
/decision-close |
Supersede or retire an existing decision. Neither is deleted — decisions are permanent records. |
| Skill | Description |
|---|---|
/status |
Summarize current task state: counts by status, active and blocked tasks, anything needing attention. |
Hooks are Node scripts that Claude Code executes automatically on specific events. They must be registered in .claude/settings.json — they do not activate just by being present in hooks/.
Add this block to .claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "node hooks/post_write_idea_guard.js",
"timeout": 10
},
{
"type": "command",
"command": "node hooks/post_write_task_validator.js",
"timeout": 10
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "node hooks/stop_task_guard.js",
"timeout": 10
}
]
}
]
}
}Fires after any Write/Edit to docs/ideas/*.md. Two non-blocking checks:
- Soft lock — warns if the file's
owner_iddoesn't match the writing agent. Prevents one agent from clobbering another's in-progress elaboration. - Structure — warns if any required section (
## The Idea,## Elaboration,## Review Notes,## Decision) is missing.
Both checks emit warnings only — they never block the write.
Fires after any Write/Edit to docs/tasks/*.yaml. Checks:
- All required fields present:
id,title,status,created,updated,objective,acceptance_criteria statusis a valid value (planning,ready,in-progress,blocked,done,abandoned)acceptance_criteriais not empty when status isin-progressordonehandoff_context.summaryis filled when status isdone
Warnings only — non-blocking. Fixes are expected before ending the session.
Fires when the agent tries to end a session. Scans docs/tasks.yaml for any task with status: in-progress. If found, blocks the session from ending with a message naming the open tasks.
To end the session cleanly: mark open tasks done (with handoff context filled) or change them to blocked with a reason in the work log.
This is the only blocking hook — it prevents incomplete work from going silent at session end.
npm install
# dev mode — hot-reloads server and UI
npm run dev
# build the UI
npm run buildThe server is Fastify serving a Preact + Tailwind frontend. File watching uses chokidar. Watched paths are configured in src/config.js.