Not in the top 1,000 by GitHub stars
OthmanAdi/plandeck
The visual Kanban for long-running AI agents. Watch your agent's plan organize itself: dependencies unlock into Ready, the critical path lights up, the one next move is obvious. Nobody wants to read a markdown plan.
History
Review weekly changes to SKILL.md before updating. Dates mark the weeks in which we observed each version; checks apply to the recorded commits.
View weekly changes
SKILL.md
Week of Nov 2, 2026 · +0 / −0 lines
Before Network or broad shell → After Network or broad shell
7d3f52dcc5bc → 7d3f52dcc5bc
Week of Oct 26, 2026 · Line counts unavailable
Before Not checked yet → After Network or broad shell
First observation → 7d3f52dcc5bc
No earlier version was observed for comparison.
Heat
0.3- Star growth, last 7 daysNot available
- Star growth, last 24 hoursNot available
- Rank movement, last 7 daysNot available
- Commits, last 30 daysNot available
- Accounts asking about it through MCP, last 7 daysNot available
- Hot votes this weekNot available
Heat never changes the order by stars, and sponsorship never touches it. How heat works
Stars over time
30-day change not yet available: not enough history.
Individual skills in this repository
- plandeck
/
How it works
plandeck
When your agent uses it
A live visual Kanban board and continuity layer for long-running agentic tasks. Nobody wants to read a markdown plan or stare at raw HTML while an agent grinds for an hour, so Plandeck turns the plan into a board that organizes itself. Use it when a task is multi-step, long-running, or needs visual planning. Break the work into cards, declare which card depends on which, and Plandeck computes the critical path, auto-promotes cards to Ready as their dependencies clear, rolls up story-point estimates into an honest percent complete, and names the one next action. Plans, observed transitions, and last-known-good snapshots stay as plain files on disk, so they survive /clear and context resets. After a reset, run plandeck next to recover the single next move and its recent history. If the plan stops parsing, run plandeck doctor before using git. Renders a live board in the browser over SSE. Triggers on plan a project, break down a task, kanban board, task board, visual plan, long-running agent, agentic task, track dependencies, critical path, story-point estimates, what is next, mission journal, recover plan, plandeck.
What it loads into context
Frontmatter
253 tokensAlways available to the agent
SKILL.md
4,090 tokensWhen the skill is activated
Reference documents
10,075 tokensLoaded on request
Token counts are estimates. Scripts run separately; they are not counted as reference context.
The playbook
- When to use it
- When not to use it
- Files in a plan directory
- Card schema
- Commands
- Continuity contract for agents
- The deterministic brain (no AI)
- The working loop
- After a /clear
- The board
- Where it runs
- Lineage
Scripts executed separately
package.jsonscripts/board.mjsscripts/check-plan.mjsscripts/cli.mjsscripts/doctor.mjsscripts/lib/continuity.mjsscripts/lib/deck.mjsscripts/lib/intelligence.mjsscripts/lib/journal.mjsscripts/lib/render.mjsscripts/lib/snapshot.mjsscripts/screenshot.mjstest/all.test.mjstest/deck.test.mjstest/doctor.test.mjstest/journal.test.mjs
Files
.gitattributesasset101 bytes.github/ISSUE_TEMPLATE/agent_compatibility.mddoc445 bytes.github/ISSUE_TEMPLATE/bug_report.mddoc384 bytes.github/PULL_REQUEST_TEMPLATE.mddoc385 bytes.github/workflows/test.ymlasset503 bytes.gitignoreasset401 bytes.npmignoreasset118 bytesCHANGELOG.mddoc5,261 bytesCONTRIBUTING.mddoc2,153 bytesLICENSEasset1,079 bytesNOTICEasset697 bytesREADME.mddoc18,470 bytesSKILL.mdcore16,595 bytesSKILL.mdcore16,595 bytesagents/judge.mddoc2,234 bytesagents/pm.mddoc2,611 bytesagents/scout.mddoc2,178 bytesagents/worker.mddoc2,500 bytesexamples/README.mddoc1,118 bytesexamples/ship-onboarding-flow/cards/C001-onboarding-map.mddoc870 bytesexamples/ship-onboarding-flow/plan.mddoc1,061 bytesexamples/ship-onboarding-flow/plan.yamlasset2,970 bytesmedia/banner.pngasset1,299,391 bytesmedia/board-preview.htmlasset36,602 bytesmedia/board.pngasset137,036 bytesmedia/plandeck-mark.svgasset449 bytespackage.jsonruns1,478 bytesscripts/board.mjsruns30,470 bytesscripts/check-plan.mjsruns3,032 bytesscripts/cli.mjsruns9,578 bytesscripts/doctor.mjsruns5,205 bytesscripts/lib/continuity.mjsruns617 bytesscripts/lib/deck.mjsruns32,582 bytesscripts/lib/intelligence.mjsruns10,160 bytesscripts/lib/journal.mjsruns9,225 bytesscripts/lib/render.mjsruns26,431 bytesscripts/lib/snapshot.mjsruns3,864 bytesscripts/screenshot.mjsruns3,011 bytestemplates/card.mddoc323 bytestemplates/plan.mddoc934 bytestemplates/plan.yamlasset2,004 bytestest/all.test.mjsruns173 bytestest/deck.test.mjsruns43,229 bytestest/doctor.test.mjsruns9,620 bytestest/journal.test.mjsruns8,872 bytes
Read SKILL.md
# Plandeck
Plandeck keeps a plan on disk as `plan.yaml` and renders it as a live Kanban board that reorganizes itself as you edit. You author cards (id, estimate, dependencies, next action). Plandeck computes the rest: which cards are Ready, the critical path, progress rollups, and the single next move. It grows out of planning-with-files (crash-proof file plans plus a completion gate). What is new in Plandeck is the live visual board plus a deterministic intelligence layer over the yaml. It contains no AI and no LLM, only pure, tested functions.
## When to use it
Reach for Plandeck when the work is multi-step and worth tracking:
- A task that needs 5+ steps, spans a long session, or will outlive a `/clear`.
- Work with real dependencies, where order matters and one card unblocks others.
- Anything you want to see: a board with lanes, estimates, and a critical path.
## When not to use it
View the complete source on GitHubSkills included
- plandeck
Repository facts
- License
- MIT
- Created
- Jul 12, 2026
- Last pushed
- Aug 3, 2026
- Forks
- 1
- Open issues
- 0
- Project activity
- Slowing
Before you install
Network or broad shellAutomated signals, not a guarantee
Checked Nov 4, 2026
Not fully analyzed: some files were too long, unreadable or missing. This never lowers the level.
Show 2 notes
SKILL.md:1Analysis incomplete: Referenced executable target unavailable; not analyzedmedia/board-preview.html:224Analysis incomplete: physical or joined line exceeds character limit
- No broad tool permissions
- No dangerous shell commands
- 83 network referencesin 12 files: test/deck.test.mjs:362 scripts/board.mjs:174 README.md:8 and 9 more
- No hidden or obfuscated text
- No instruction overrides
- 16 executable scriptsin 16 files: package.json:1 scripts/board.mjs:1 scripts/check-plan.mjs:1 and 13 more
- No access to secrets
- No requests to vote in Hot
Show all 99 signals by file
CONTRIBUTING.md
- Network accessline 19
git clone https://github.com/OthmanAdi/plandeck
NOTICE
- Network accessline 4
Lineage ------------------------------------------------------------------------------- planning-with-files — https://github.com/OthmanAdi/planning-with-files - Network accessline 5
------------------------------------------------------------------------------- planning-with-files — https://github.com/OthmanAdi/planning-with-files Copyright (c) 2026 Ahmad Othman Ammar Adi - Network accessline 6
planning-with-files — https://github.com/OthmanAdi/planning-with-files
README.md
- Network accessline 8
npx skills add https://github.com/othmanadi/plandeck --skill plandeck - Network accessline 20
   , MIT), which gave crash-proof markdown plans that survive `/clear` plus a deterministic completion gate. Plande - Network accessline 44
# 2. Open the URL it prints, e.g. # http://plandeck.localhost:41747/ship-onboarding-flow/ - Network accessline 45
# http://plandeck.localhost:41747/ship-onboarding-flow/ - Network accessline 134
- Live board: http://plandeck.localhost:41747/ship-onboarding-flow/ - Network accessline 198
risk: med depends_on: [C002] verify: ["curl -f localhost:3000/api/signup -d @test/fixtures/user.json"] - Network accessline 199
depends_on: [C002] verify: ["curl -f localhost:3000/api/signup -d @test/fixtures/user.json"] next_action: "Wire the POST /signup handler to the users table, return 201 with the new id." - Network accessline 200
verify: ["curl -f localhost:3000/api/signup -d @test/fixtures/user.json"] - Network accessline 234
Plandeck grows out of **planning-with-files** by [OthmanAdi](https://github.com/OthmanAdi/planning-with-files) (MIT): durable, crash-proof plans on disk and a deterministic completion gate. Everything else, the board server, the YAML reader
SKILL.md
- Network accessline 171
- Live board: http://plandeck.localhost:41747/ship-onboarding-flow/
examples/ship-onboarding-flow/plan.yaml
- Network accessline 50
risk: med depends_on: [C002] verify: ["npm test -- test/auth/signup.test.ts", "curl -s localhost:3000/api/signup"] - Network accessline 51
depends_on: [C002] verify: ["npm test -- test/auth/signup.test.ts", "curl -s localhost:3000/api/signup"] next_action: "Finish POST /api/signup: hash password, insert user, emit onboarding.started." - Network accessline 52
verify: ["npm test -- test/auth/signup.test.ts", "curl -s localhost:3000/api/signup"]
media/board-preview.html
- Network accessline 332
async function boot(){ if(window.__PLANDECK_STATIC__){render(window.__PLANDECK_STATIC__);setLive(true);return;} try{const r=await fetch("./api/board",{cache:"no-store"});render(await r.json())}catch(e){} - Network accessline 333
if(window.__PLANDECK_STATIC__){render(window.__PLANDECK_STATIC__);setLive(true);return;} try{const r=await fetch("./api/board",{cache:"no-store"});render(await r.json())}catch(e){} connect(); - Network accessline 334
try{const r=await fetch("./api/board",{cache:"no-store"});render(await r.json())}catch(e){}
media/plandeck-mark.svg
- Network accessline 1
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Plandeck">
package.json
- Executable scriptline 1
Executable file - Network accessline 51
"repository": { "type": "git", "url": "git+https://github.com/OthmanAdi/plandeck.git" - Network accessline 52
"type": "git", "url": "git+https://github.com/OthmanAdi/plandeck.git" }, - Network accessline 53
"url": "git+https://github.com/OthmanAdi/plandeck.git" - Network accessline 54
}, "homepage": "https://github.com/OthmanAdi/plandeck#readme", "bugs": "https://github.com/OthmanAdi/plandeck/issues", - Network accessline 55
"homepage": "https://github.com/OthmanAdi/plandeck#readme", - Network accessline 56
"bugs": "https://github.com/OthmanAdi/plandeck/issues",
scripts/board.mjs
- Executable scriptline 1
Executable file - Network accessline 174
const timer = setTimeout(() => controller.abort(), 1000); try { const target = `http://${urlHost(connectHost(host))}:${port}/api/boards`; - Network accessline 175
try { const target = `http://${urlHost(connectHost(host))}:${port}/api/boards`; const handshake = await fetch(target, { signal: controller.signal }); - Network accessline 176
const target = `http://${urlHost(connectHost(host))}:${port}/api/boards`; const handshake = await fetch(target, { signal: controller.signal }); if (handshake.status !== 200) return null; - Network accessline 177
const handshake = await fetch(target, { signal: controller.signal }); - Network accessline 179
const state = await handshake.json(); if (!state || state.hub !== true) return null; const response = await fetch(target, { - Network accessline 180
if (!state || state.hub !== true) return null; const response = await fetch(target, { method: "POST", - Network accessline 181
const response = await fetch(target, { - Network accessline 202
try { const origin = new URL(summary.url).origin; await fetch(`${origin}/api/boards?path=${encodeURIComponent(summary.boardPath)}`, { method: "DELETE" }); - Network accessline 203
const origin = new URL(summary.url).origin; await fetch(`${origin}/api/boards?path=${encodeURIComponent(summary.boardPath)}`, { method: "DELETE" }); } catch { - Network accessline 204
await fetch(`${origin}/api/boards?path=${encodeURIComponent(summary.boardPath)}`, { method: "DELETE" }); - Network accessline 264
async function handleRequest(hub, req, res) { const requestUrl = new URL(req.url || "/", `http://${BIND_HOST}`); - Network accessline 265
const requestUrl = new URL(req.url || "/", `http://${BIND_HOST}`); - Network accessline 756
function hubOrigin(hub) { return `http://${urlHost(hub.publicHost)}:${hub.port}`; - Network accessline 757
return `http://${urlHost(hub.publicHost)}:${hub.port}`;
scripts/check-plan.mjs
- Executable scriptline 1
Executable file
scripts/cli.mjs
- Executable scriptline 1
#!/usr/bin/env node - Network accessline 104
atomicWriteFile(dest, nextMarkdown( payload, `http://plandeck.localhost:${PORT}/${payload.plan.slug}/`, - Network accessline 105
payload, `http://plandeck.localhost:${PORT}/${payload.plan.slug}/`, recentForNext(root, 5), - Network accessline 106
`http://plandeck.localhost:${PORT}/${payload.plan.slug}/`,
scripts/doctor.mjs
- Executable scriptline 1
Executable file
scripts/lib/continuity.mjs
- Executable scriptline 1
Executable file
scripts/lib/deck.mjs
- Executable scriptline 1
Executable file
scripts/lib/intelligence.mjs
- Executable scriptline 1
Executable file
scripts/lib/journal.mjs
- Executable scriptline 1
Executable file
scripts/lib/render.mjs
- Executable scriptline 1
Executable file - Network accessline 428
async function boot(){ if(window.__PLANDECK_STATIC__){render(window.__PLANDECK_STATIC__);setLive(true);return;} try{const r=await fetch("./api/board",{cache:"no-store"});render(await r.json())}catch(e){} - Network accessline 429
if(window.__PLANDECK_STATIC__){render(window.__PLANDECK_STATIC__);setLive(true);return;} try{const r=await fetch("./api/board",{cache:"no-store"});render(await r.json())}catch(e){} connect(); - Network accessline 430
try{const r=await fetch("./api/board",{cache:"no-store"});render(await r.json())}catch(e){}
scripts/lib/snapshot.mjs
- Executable scriptline 1
Executable file
scripts/screenshot.mjs
- Executable scriptline 1
#!/usr/bin/env node
test/all.test.mjs
- Executable scriptline 1
Executable file
test/deck.test.mjs
- Executable scriptline 1
Executable file - Network accessline 362
const base = "http://127.0.0.1:" + hub.port; const state = await fetch(base + "/api/boards").then((response) => response.json()); - Network accessline 363
const state = await fetch(base + "/api/boards").then((response) => response.json()); - Network accessline 369
const secondPayload = await fetch(base + "/x-2/api/board").then((response) => response.json()); - Network accessline 372
const index = await fetch(base + "/").then((response) => response.text()); - Network accessline 378
const wrongType = await fetch(base + "/api/boards", { - Network accessline 397
const removed = await fetch(base + "/api/boards?path=" + encodeURIComponent("/x-2/"), { - Network accessline 400
}); assert.equal(removed.status, 200); const afterDelete = await fetch(base + "/api/boards").then((response) => response.json()); - Network accessline 401
assert.equal(removed.status, 200); const afterDelete = await fetch(base + "/api/boards").then((response) => response.json()); assert.deepEqual(afterDelete.boards.map((board) => board.boardPath), ["/x/"]); - Network accessline 402
const afterDelete = await fetch(base + "/api/boards").then((response) => response.json()); - Network accessline 405
unlinkSync(join(firstDir, "plan.yaml")); await waitFor(async () => { const current = await fetch(base + "/api/boards").then((response) => response.json()); - Network accessline 406
await waitFor(async () => { const current = await fetch(base + "/api/boards").then((response) => response.json()); return current.boards.length === 0; - Network accessline 407
const current = await fetch(base + "/api/boards").then((response) => response.json()); - Network accessline 411
const unknown = await fetch(base + "/not-a-board"); - Network accessline 453
const state = await fetch(`http://127.0.0.1:${hub.port}/api/boards`).then((response) => response.json()); - Network accessline 474
}); hub = await quietly(() => runBoard({ planDir: root, port: 0, host: "127.0.0.1" })); const base = `http://127.0.0.1:${hub.port}`; - Network accessline 475
hub = await quietly(() => runBoard({ planDir: root, port: 0, host: "127.0.0.1" })); const base = `http://127.0.0.1:${hub.port}`; const response = await fetch(base + "/api/boards", { - Network accessline 476
const base = `http://127.0.0.1:${hub.port}`; const response = await fetch(base + "/api/boards", { method: "POST", - Network accessline 477
const response = await fetch(base + "/api/boards", { - Network accessline 486
const healthy = await fetch(base + "/api/boards"); - Network accessline 741
unlinkSync(join(root, "cards", "A.md")); const payload = await waitFor(async () => { const current = await fetch(boardServer.url + "api/board").then((response) => response.json()); - Network accessline 742
const payload = await waitFor(async () => { const current = await fetch(boardServer.url + "api/board").then((response) => response.json()); return current.warnings.some((warning) => warning.kind === "missing-note") ? current : n - Network accessline 743
const current = await fetch(boardServer.url + "api/board").then((response) => response.json()); - Network accessline 748
const state = await fetch(`http://127.0.0.1:${boardServer.port}/api/boards`).then((response) => response.json()); - Network accessline 766
boardServer = await quietly(() => runBoard({ planDir: root, port: 0, host: "127.0.0.1" })); const endpoint = boardServer.url + "api/board"; const good = await fetch(endpoint).then((response) => response.json()); - Network accessline 767
const endpoint = boardServer.url + "api/board"; const good = await fetch(endpoint).then((response) => response.json()); assert.ok(good.columns.length > 0); - Network accessline 768
const good = await fetch(endpoint).then((response) => response.json()); - Network accessline 771
writeFileSync(join(root, "plan.yaml"), "version: 1\n plan:\n"); const stale = await waitFor(async () => { const payload = await fetch(endpoint).then((response) => response.json()); - Network accessline 772
const stale = await waitFor(async () => { const payload = await fetch(endpoint).then((response) => response.json()); return payload.error ? payload : null; - Network accessline 773
const payload = await fetch(endpoint).then((response) => response.json()); - Network accessline 887
await new Promise((resolveWait) => setTimeout(resolveWait, 5)); const second = appendJournalEntry(root, { cardId: "A", field: "column", from: "ready", to: "doing", actor: "second" }); const response = await fetch(`${boardServer.url} - Network accessline 888
const second = appendJournalEntry(root, { cardId: "A", field: "column", from: "ready", to: "doing", actor: "second" }); const response = await fetch(`${boardServer.url}api/journal?since=${encodeURIComponent(second.ts)}&limit=5`); as - Network accessline 889
const response = await fetch(`${boardServer.url}api/journal?since=${encodeURIComponent(second.ts)}&limit=5`); - Network accessline 893
assert.equal(body.entries.length, 1); assert.deepEqual(body.entries[0], second); const post = await fetch(`${boardServer.url}api/journal`, { method: "POST" }); - Network accessline 894
assert.deepEqual(body.entries[0], second); const post = await fetch(`${boardServer.url}api/journal`, { method: "POST" }); assert.equal(post.status, 405); - Network accessline 895
const post = await fetch(`${boardServer.url}api/journal`, { method: "POST" });
test/doctor.test.mjs
- Executable scriptline 1
Executable file
test/journal.test.mjs
- Executable scriptline 1
Executable file
How the signals fit together
Facts from a static read of the files at the pinned commit. They describe what the code does, not why.
Some files could not be read; the facts below still hold.
Signal confidence: 89 facts
- Network access in code of documentationCONTRIBUTING.md:19Low confidence
- Network access in code of documentationREADME.md:8Low confidence
- Network access in prose of documentationREADME.md:20Low confidence
- Network access in prose of documentationREADME.md:34Low confidence
- Network access in code of documentationREADME.md:44Low confidence
- Network access in code of documentationREADME.md:45Low confidence
- Network access in code of documentationREADME.md:134Low confidence
- Network access in code of documentationREADME.md:198Low confidence
- Network access in code of documentationREADME.md:199Low confidence
- Network access in code of documentationREADME.md:200Low confidence
- Network access in prose of documentationREADME.md:234Low confidence
- Network access in code of the SKILL.mdSKILL.md:171High confidence
- Executable script in code of a file that runspackage.json:1High confidence
- Network access in code of a file that runspackage.json:51High confidence
- Network access in code of a file that runspackage.json:52High confidence
- Network access in code of a file that runspackage.json:53High confidence
- Network access in code of a file that runspackage.json:54High confidence
- Network access in code of a file that runspackage.json:55High confidence
- Network access in code of a file that runspackage.json:56High confidence
- Executable script in a comment of a file that runsscripts/board.mjs:1Low confidence
- Network access in code of a file that runsscripts/board.mjs:174High confidence
- Network access in code of a file that runsscripts/board.mjs:175High confidence
- Network access in code of a file that runsscripts/board.mjs:176High confidence
- Network access in code of a file that runsscripts/board.mjs:177High confidence
- Network access in code of a file that runsscripts/board.mjs:179High confidence
- Network access in code of a file that runsscripts/board.mjs:180High confidence
- Network access in code of a file that runsscripts/board.mjs:181High confidence
- Network access in code of a file that runsscripts/board.mjs:202High confidence
- Network access in code of a file that runsscripts/board.mjs:203High confidence
- Network access in code of a file that runsscripts/board.mjs:204High confidence
- Network access in code of a file that runsscripts/board.mjs:264High confidence
- Network access in code of a file that runsscripts/board.mjs:265High confidence
- Network access in code of a file that runsscripts/board.mjs:756High confidence
- Network access in code of a file that runsscripts/board.mjs:757High confidence
- Executable script in a comment of a file that runsscripts/check-plan.mjs:1Low confidence
- Executable script in code of a file that runsscripts/cli.mjs:1High confidence
- Network access in code of a file that runsscripts/cli.mjs:104High confidence
- Network access in code of a file that runsscripts/cli.mjs:105High confidence
- Network access in code of a file that runsscripts/cli.mjs:106High confidence
- Executable script in a comment of a file that runsscripts/doctor.mjs:1Low confidence
- Executable script in a comment of a file that runsscripts/lib/continuity.mjs:1Low confidence
- Executable script in a comment of a file that runsscripts/lib/deck.mjs:1Low confidence
- Executable script in a comment of a file that runsscripts/lib/intelligence.mjs:1Low confidence
- Executable script in a comment of a file that runsscripts/lib/journal.mjs:1Low confidence
- Executable script in a comment of a file that runsscripts/lib/render.mjs:1Low confidence
- Network access in code of a file that runsscripts/lib/render.mjs:428High confidence
- Network access in code of a file that runsscripts/lib/render.mjs:429High confidence
- Network access in code of a file that runsscripts/lib/render.mjs:430High confidence
- Executable script in a comment of a file that runsscripts/lib/snapshot.mjs:1Low confidence
- Executable script in code of a file that runsscripts/screenshot.mjs:1High confidence
and 39 more
Works with
| Agent | Status | Evidence | Install to |
|---|---|---|---|
| Claude Code | Compatible | Standard SKILL.md SKILL.md | .claude/skills/<skill-name>/ |
| Codex | Compatible | Standard SKILL.md SKILL.md | .agents/skills/<skill-name>/ |
| Cursor | Compatible | Standard SKILL.md SKILL.md | .cursor/skills/<skill-name>/ |
| Gemini CLI | Compatible | Standard SKILL.md SKILL.md | .gemini/skills/<skill-name>/ |
| GitHub Copilot | Compatible | Standard SKILL.md SKILL.md | .github/skills/<skill-name>/ |
| OpenCode | Compatible | Standard SKILL.md SKILL.md | .opencode/skills/<skill-name>/ |
- Native The repository ships a manifest, folder or frontmatter field for this agent.
- Compatible A standard SKILL.md that this agent can read.
- Unknown No evidence either way.
Derived automatically from paths, manifests and frontmatter. Not tested on each agent.