issues

    SQLite-backed DAG issue tracker with a MoonBit CLI and Rabbit Tea dashboard.

    issues
    sqlite
    dag
    cli
    dashboard
    Download zip
    Author
    Version
    0.1.8
    License
    Apache-2.0
    Last updated
    8 hours ago
    Downloads
    66

    #bobzhang/issues

    SQLite-backed DAG issue tracker with a MoonBit CLI for agents and a Rabbit Tea dashboard for humans.

    #CLI

    Run the CLI through Moon while developing:

    moon run --target native cmd/main -- --db issues.db init moon run --target native cmd/main -- --db issues.db add -p 5 --body "Track the project plan." Build tracker moon run --target native cmd/main -- --db issues.db add Implement CLI moon run --target native cmd/main -- --db issues.db link -t contains iss_21845b479070a3c5 iss_0e62aff88401672c moon run --target native cmd/main -- --db issues.db focus iss_21845b479070a3c5 moon run --target native cmd/main -- --db issues.db next moon run --target native cmd/main -- --db issues.db todos moon run --target native cmd/main -- --db issues.db todos --ready moon run --target native cmd/main -- --db issues.db show iss_21845b479070a3c5 moon run --target native cmd/main -- --db issues.db outline moon run --target native cmd/main -- --db issues.db import-markdown plan.md moon run --target native cmd/main -- --db issues.db serve

    Issue IDs are deterministic: add trims the title, hashes it with SHA-256, and uses the first 16 hex characters with an iss_ prefix. Titles are immutable; to rename an issue, create a new issue with the new title and relink it. Re-adding the same title returns the same ID. If the generated SHA ID already belongs to a different title, the CLI exits with ISSUE_TITLE_RENAME_REQUIRED so an agent can choose a more specific title.

    Edges are typed:

    • contains for outline/decomposition.
    • depends_on for blocking relationships.
    • relates_to for loose references.

    next returns the highest-priority unfinished, unblocked leaf under the current focus item. todos renders the current focus as a recursive Markdown task list for terminal reading. Use todos --ready, todos --blocked, or todos --all for flat filtered lists.

    import-markdown batch-creates todos from a CommonMark task list using cmark.mbt:

    - [ ] Build tracker - [x] Implement CLI - [ ] Implement dashboard

    Nested task bullets should be indented to the parent item content column; after - [ ] Parent, that means six spaces before the child -. Use --parent <issue-id> to attach imported top-level tasks under an existing issue.

    Larger-agent workflows have mutable details, notes, claims, done evidence, search, and an append-only event log:

    moon run --target native cmd/main -- --db issues.db body iss_21845b479070a3c5 "Mutable details and acceptance criteria." moon run --target native cmd/main -- --db issues.db note iss_21845b479070a3c5 "Found the schema edge case." moon run --target native cmd/main -- --db issues.db claim --agent codex --ttl-minutes 60 iss_21845b479070a3c5 moon run --target native cmd/main -- --db issues.db done iss_0e62aff88401672c "Validated with moon test --target native." moon run --target native cmd/main -- --db issues.db events iss_21845b479070a3c5 moon run --target native cmd/main -- --db issues.db search schema moon run --target native cmd/main -- --db issues.db release --agent codex iss_21845b479070a3c5

    The SQLite schema records migrations in schema_migrations. Current hashed-ID databases migrate additively, and old numeric-ID databases are migrated into deterministic title-hash IDs with items_legacy_numeric backup tables.

    #Dashboard

    Generate a static dashboard bundle from the SQLite database:

    moon run --target native cmd/main -- --db issues.db dashboard --out dashboard-dist

    Open dashboard-dist/index.html in a browser. The command writes data.js, builds the MoonBit/Rabbit Tea JS app, and copies it to app.js.

    For a live local viewer backed by SQLite, run:

    moon run --target native cmd/main -- --db issues.db serve --port 8080

    Then open http://127.0.0.1:8080. The service exposes:

    • / and /index.html for the dashboard.
    • /data.js for the dashboard bootstrap data.
    • /api/graph for graph JSON.
    • /api/next for the next actionable todo as JSON.
    • /api/outline for the text outline.
    • POST /api/body to update mutable issue body text.
    • POST /api/status to update mutable status; done requires evidence.
    • POST /api/note to append an issue note.
    • /healthz for a simple health check.

    The live dashboard can edit body text, status, and append-only notes. Issue titles remain immutable: the UI only displays them, and the service has no title mutation endpoint.

    #Validation

    moon test --target native moon test --target js moon check --target native moon check --target js

    #Executable CLI Docs

    The tests/scrut/issue_tracker.md document is a Scrut suite: it describes the agent workflow in Markdown and executes each scrut block against the compiled CLI with a fresh SQLite database.

    Install Scrut, then run:

    scripts/test-scrut.sh

    EdgeKind

    pub(all) enum EdgeKind {
    Contains
    DependsOn
    RelatesTo
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    EdgeKind::equal

    fn EdgeKind::equal(EdgeKind, EdgeKind) -> Bool

    EdgeKind::from_storage

    fn EdgeKind::from_storage(value : String) -> EdgeKind raise

    EdgeKind::not_equal

    fn EdgeKind::not_equal(x : EdgeKind, y : EdgeKind) -> Bool

    EdgeKind::to_json

    fn EdgeKind::to_json(EdgeKind) -> Json

    EdgeKind::to_repr

    EdgeKind::to_storage

    fn EdgeKind::to_storage(self : EdgeKind) -> String

    IssueEdge

    pub(all) struct IssueEdge {
    from_id : String
    to_id : String
    kind : EdgeKind
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    IssueEdge::equal

    fn IssueEdge::equal(IssueEdge, IssueEdge) -> Bool

    IssueEdge::not_equal

    fn IssueEdge::not_equal(x : IssueEdge, y : IssueEdge) -> Bool

    IssueEdge::to_json

    fn IssueEdge::to_json(IssueEdge) -> Json

    IssueGraph

    pub(all) struct IssueGraph {
    items : Array[IssueItem]
    edges : Array[IssueEdge]
    current_id : String?
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    IssueGraph::all_todos_markdown

    fn IssueGraph::all_todos_markdown(self : IssueGraph, focus : String?) -> String

    IssueGraph::blocked_count

    fn IssueGraph::blocked_count(self : IssueGraph) -> Int

    IssueGraph::blocked_todos

    fn IssueGraph::blocked_todos(self : IssueGraph, focus : String?) -> Array[IssueItem]

    IssueGraph::blocked_todos_markdown

    fn IssueGraph::blocked_todos_markdown(self : IssueGraph, focus : String?) -> String

    IssueGraph::contains_children

    fn IssueGraph::contains_children(self : IssueGraph, id : String) -> Array[IssueItem]

    IssueGraph::dependencies

    fn IssueGraph::dependencies(self : IssueGraph, id : String) -> Array[IssueItem]

    IssueGraph::edge_count

    fn IssueGraph::edge_count(self : IssueGraph, kind : EdgeKind) -> Int

    IssueGraph::empty

    fn IssueGraph::empty() -> IssueGraph

    IssueGraph::equal

    fn IssueGraph::equal(IssueGraph, IssueGraph) -> Bool

    IssueGraph::find_item

    fn IssueGraph::find_item(self : IssueGraph, id : String) -> IssueItem?

    IssueGraph::has_open_child

    fn IssueGraph::has_open_child(self : IssueGraph, id : String) -> Bool

    IssueGraph::is_blocked

    fn IssueGraph::is_blocked(self : IssueGraph, id : String) -> Bool

    IssueGraph::next_todo

    fn IssueGraph::next_todo(self : IssueGraph, focus : String?) -> IssueItem?

    IssueGraph::not_equal

    fn IssueGraph::not_equal(x : IssueGraph, y : IssueGraph) -> Bool

    IssueGraph::open_count

    fn IssueGraph::open_count(self : IssueGraph) -> Int

    IssueGraph::outline

    fn IssueGraph::outline(self : IssueGraph, focus : String?) -> String

    IssueGraph::ready_todos

    fn IssueGraph::ready_todos(self : IssueGraph, focus : String?) -> Array[IssueItem]

    IssueGraph::ready_todos_markdown

    fn IssueGraph::ready_todos_markdown(self : IssueGraph, focus : String?) -> String

    IssueGraph::scoped_items

    fn IssueGraph::scoped_items(self : IssueGraph, focus : String?) -> Array[IssueItem]

    IssueGraph::to_json

    fn IssueGraph::to_json(IssueGraph) -> Json

    IssueGraph::todos_markdown

    fn IssueGraph::todos_markdown(self : IssueGraph, focus : String?) -> String

    IssueItem

    pub(all) struct IssueItem {
    id : String
    title : String
    body : String
    status : Status
    priority : Int
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    IssueItem::equal

    fn IssueItem::equal(IssueItem, IssueItem) -> Bool

    IssueItem::not_equal

    fn IssueItem::not_equal(x : IssueItem, y : IssueItem) -> Bool

    IssueItem::to_json

    fn IssueItem::to_json(IssueItem) -> Json

    Status

    pub(all) enum Status {
    Todo
    InProgress
    Done
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    Status::equal

    fn Status::equal(Status, Status) -> Bool

    Status::from_storage

    fn Status::from_storage(value : String) -> Status raise

    Status::is_open

    fn Status::is_open(self : Status) -> Bool

    Status::not_equal

    fn Status::not_equal(x : Status, y : Status) -> Bool

    Status::to_json

    fn Status::to_json(Status) -> Json

    Status::to_repr

    Status::to_storage

    fn Status::to_storage(self : Status) -> String

    Source Files

    Powered by MoonBit

    Site sourceReport issuePackagesBuild queueSkillsStatistics

    © 2026 mooncakes.io