refactor: rename project surface to mergegate

This commit is contained in:
林 駿甫 (Shunsuke Hayashi) 2026-04-10 14:50:57 +09:00
parent 3f1453b23c
commit 29c72fee83
111 changed files with 1586 additions and 873 deletions

View file

@ -5,7 +5,7 @@ GITHUB_TOKEN=ghp_your_github_token_here
# Repository Information
# Format: owner/repo (e.g., YourUsername/my-project)
REPOSITORY=ShunsukeHayashi/miyabi-cli-standalone
REPOSITORY=ShunsukeHayashi/mergegate
# Anthropic API Key (optional for local development)
# Get key at: https://console.anthropic.com/

3
.gitignore vendored
View file

@ -176,3 +176,6 @@ yarn-error.log*
.ai/
.env
.gitnexus
project_memory/task-events.jsonl
project_memory/tasks.snapshot.json
project_memory/.tasks.lock

View file

@ -1,7 +1,7 @@
# Repository Guidelines
## Project Structure & Module Organization
- Rust workspace lives under `crates/`: `miyabi-cli` (CLI entry), `miyabi-tui` (UI), and `miyabi-core` (shared logic, config, sessions, Polaris/DTP: `gate`, `store`, `lock`, `protocol`). Shared workspace config in `Cargo.toml`.
- Rust workspace lives under `crates/`: `mergegate-cli` (CLI entry), `mergegate-tui` (UI), and `mergegate-core` (shared logic, config, sessions, MergeGate/DTP: `gate`, `store`, `lock`, `protocol`). Shared workspace config in `Cargo.toml`.
- ルートに `package.json` はない(過去テンプレート由来の記述は削除済み)。フロント系ツールが別ディレクトリにあれば、その README に従う。
- Supporting assets: `.claude/` agent configs, `docs/` for design notes, `project_memory/` for Polaris タスク台帳(`tasks.json`)、`.github/` for CI workflows。

View file

@ -181,7 +181,7 @@ miyabi gate manual-complete <task-id> --reason "理由" --operator "名前"
<!-- gitnexus:start -->
# GitNexus — Code Intelligence
This project is indexed by GitNexus as **miyabi-cli-standalone** (5691 symbols, 12441 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
This project is indexed by GitNexus as **miyabi-cli-standalone** (5866 symbols, 12893 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.

575
Cargo.lock generated
View file

@ -146,6 +146,21 @@ dependencies = [
"serde",
]
[[package]]
name = "bit-set"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
dependencies = [
"bit-vec",
]
[[package]]
name = "bit-vec"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]]
name = "bitflags"
version = "2.10.0"
@ -209,6 +224,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chrono"
version = "0.4.42"
@ -301,16 +322,6 @@ dependencies = [
"unicode-segmentation",
]
[[package]]
name = "core-foundation"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
@ -498,15 +509,6 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "encoding_rs"
version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
"cfg-if",
]
[[package]]
name = "equivalent"
version = "1.0.2"
@ -592,21 +594,6 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.2.2"
@ -741,8 +728,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi",
"wasm-bindgen",
]
[[package]]
@ -752,9 +741,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"r-efi",
"wasip2",
"wasm-bindgen",
]
[[package]]
@ -778,25 +769,6 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "h2"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386"
dependencies = [
"atomic-waker",
"bytes",
"fnv",
"futures-core",
"futures-sink",
"http",
"indexmap",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "half"
version = "2.7.1"
@ -881,7 +853,6 @@ dependencies = [
"bytes",
"futures-channel",
"futures-core",
"h2",
"http",
"http-body",
"httparse",
@ -907,22 +878,7 @@ dependencies = [
"tokio",
"tokio-rustls",
"tower-service",
]
[[package]]
name = "hyper-tls"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [
"bytes",
"http-body-util",
"hyper",
"hyper-util",
"native-tls",
"tokio",
"tokio-native-tls",
"tower-service",
"webpki-roots",
]
[[package]]
@ -944,11 +900,9 @@ dependencies = [
"percent-encoding",
"pin-project-lite",
"socket2",
"system-configuration",
"tokio",
"tower-service",
"tracing",
"windows-registry",
]
[[package]]
@ -1302,6 +1256,12 @@ dependencies = [
"hashbrown 0.15.5",
]
[[package]]
name = "lru-slab"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
[[package]]
name = "matchers"
version = "0.2.0"
@ -1318,10 +1278,72 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
name = "mergegate-cli"
version = "0.1.0"
dependencies = [
"anyhow",
"chrono",
"clap",
"crossterm 0.29.0",
"mergegate-core",
"mergegate-tui",
"ratatui",
"serde",
"serde_json",
"tokio",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "mergegate-core"
version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"chrono",
"dirs",
"fs2",
"futures",
"git2",
"glob",
"proptest",
"regex",
"reqwest",
"serde",
"serde_json",
"serde_yaml",
"tempfile",
"thiserror 2.0.17",
"tokio",
"toml",
"tracing",
"tracing-subscriber",
"uuid",
]
[[package]]
name = "mergegate-tui"
version = "0.1.0"
dependencies = [
"anyhow",
"arboard",
"chrono",
"crossterm 0.29.0",
"futures",
"mergegate-core",
"once_cell",
"pulldown-cmark",
"ratatui",
"serde",
"serde_json",
"syntect",
"textwrap",
"thiserror 2.0.17",
"tokio",
"unicode-width 0.2.0",
"uuid",
]
[[package]]
name = "miniz_oxide"
@ -1345,72 +1367,6 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "miyabi-cli"
version = "0.1.0"
dependencies = [
"anyhow",
"chrono",
"clap",
"crossterm 0.29.0",
"miyabi-core",
"miyabi-tui",
"ratatui",
"serde_json",
"tokio",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "miyabi-core"
version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"chrono",
"dirs",
"fs2",
"futures",
"git2",
"glob",
"regex",
"reqwest",
"serde",
"serde_json",
"serde_yaml",
"tempfile",
"thiserror 2.0.17",
"tokio",
"toml",
"tracing",
"tracing-subscriber",
"uuid",
]
[[package]]
name = "miyabi-tui"
version = "0.1.0"
dependencies = [
"anyhow",
"arboard",
"chrono",
"crossterm 0.29.0",
"futures",
"miyabi-core",
"once_cell",
"pulldown-cmark",
"ratatui",
"serde",
"serde_json",
"syntect",
"textwrap",
"thiserror 2.0.17",
"tokio",
"unicode-width 0.2.0",
"uuid",
]
[[package]]
name = "moxcms"
version = "0.7.9"
@ -1421,23 +1377,6 @@ dependencies = [
"pxfm",
]
[[package]]
name = "native-tls"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
dependencies = [
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "nu-ansi-term"
version = "0.50.3"
@ -1569,38 +1508,21 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "openssl"
version = "0.10.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328"
dependencies = [
"bitflags",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "openssl-probe"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
name = "openssl-src"
version = "300.5.4+3.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507b3792995dae9b0df8a1c1e3771e8418b7c2d9f0baeba32e6fe8b06c7cb72"
dependencies = [
"cc",
]
[[package]]
name = "openssl-sys"
version = "0.9.111"
@ -1609,6 +1531,7 @@ checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
dependencies = [
"cc",
"libc",
"openssl-src",
"pkg-config",
"vcpkg",
]
@ -1713,6 +1636,15 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro2"
version = "1.0.103"
@ -1722,6 +1654,25 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "proptest"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744"
dependencies = [
"bit-set",
"bit-vec",
"bitflags",
"num-traits",
"rand",
"rand_chacha",
"rand_xorshift",
"regex-syntax",
"rusty-fork",
"tempfile",
"unarray",
]
[[package]]
name = "pulldown-cmark"
version = "0.12.2"
@ -1750,6 +1701,12 @@ dependencies = [
"num-traits",
]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quick-error"
version = "2.0.1"
@ -1765,6 +1722,61 @@ dependencies = [
"memchr",
]
[[package]]
name = "quinn"
version = "0.11.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
dependencies = [
"bytes",
"cfg_aliases",
"pin-project-lite",
"quinn-proto",
"quinn-udp",
"rustc-hash",
"rustls",
"socket2",
"thiserror 2.0.17",
"tokio",
"tracing",
"web-time",
]
[[package]]
name = "quinn-proto"
version = "0.11.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31"
dependencies = [
"bytes",
"getrandom 0.3.4",
"lru-slab",
"rand",
"ring",
"rustc-hash",
"rustls",
"rustls-pki-types",
"slab",
"thiserror 2.0.17",
"tinyvec",
"tracing",
"web-time",
]
[[package]]
name = "quinn-udp"
version = "0.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
dependencies = [
"cfg_aliases",
"libc",
"once_cell",
"socket2",
"tracing",
"windows-sys 0.60.2",
]
[[package]]
name = "quote"
version = "1.0.42"
@ -1780,6 +1792,44 @@ version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
dependencies = [
"getrandom 0.3.4",
]
[[package]]
name = "rand_xorshift"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a"
dependencies = [
"rand_core",
]
[[package]]
name = "ratatui"
version = "0.29.0"
@ -1858,30 +1908,27 @@ checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f"
dependencies = [
"base64",
"bytes",
"encoding_rs",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-tls",
"hyper-util",
"js-sys",
"log",
"mime",
"native-tls",
"percent-encoding",
"pin-project-lite",
"quinn",
"rustls",
"rustls-pki-types",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tokio-native-tls",
"tokio-rustls",
"tokio-util",
"tower",
"tower-http",
@ -1891,6 +1938,7 @@ dependencies = [
"wasm-bindgen-futures",
"wasm-streams",
"web-sys",
"webpki-roots",
]
[[package]]
@ -1907,6 +1955,12 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustix"
version = "0.38.44"
@ -1940,6 +1994,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f"
dependencies = [
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki",
"subtle",
@ -1952,6 +2007,7 @@ version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a"
dependencies = [
"web-time",
"zeroize",
]
@ -1972,6 +2028,18 @@ version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "rusty-fork"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2"
dependencies = [
"fnv",
"quick-error 1.2.3",
"tempfile",
"wait-timeout",
]
[[package]]
name = "ryu"
version = "1.0.20"
@ -1987,44 +2055,12 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "schannel"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "security-framework"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
"bitflags",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "serde"
version = "1.0.228"
@ -2279,27 +2315,6 @@ dependencies = [
"yaml-rust",
]
[[package]]
name = "system-configuration"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
dependencies = [
"bitflags",
"core-foundation",
"system-configuration-sys",
]
[[package]]
name = "system-configuration-sys"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "tempfile"
version = "3.23.0"
@ -2382,7 +2397,7 @@ dependencies = [
"fax",
"flate2",
"half",
"quick-error",
"quick-error 2.0.1",
"weezl",
"zune-jpeg",
]
@ -2428,6 +2443,21 @@ dependencies = [
"zerovec",
]
[[package]]
name = "tinyvec"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.48.0"
@ -2455,16 +2485,6 @@ dependencies = [
"syn",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.26.4"
@ -2654,6 +2674,12 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "unarray"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
[[package]]
name = "unicase"
version = "2.8.1"
@ -2761,6 +2787,15 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "wait-timeout"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
dependencies = [
"libc",
]
[[package]]
name = "walkdir"
version = "2.5.0"
@ -2876,6 +2911,25 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "web-time"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "webpki-roots"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "weezl"
version = "0.1.12"
@ -2954,17 +3008,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-registry"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
dependencies = [
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-result"
version = "0.4.1"

View file

@ -1,9 +1,9 @@
[workspace]
resolver = "2"
members = [
"crates/miyabi-cli",
"crates/miyabi-tui",
"crates/miyabi-core",
"crates/mergegate-cli",
"crates/mergegate-tui",
"crates/mergegate-core",
]
[workspace.package]
@ -12,9 +12,9 @@ edition = "2021"
rust-version = "1.75"
authors = ["Miyabi Team"]
license = "BUSL-1.1"
repository = "https://github.com/ShunsukeHayashi/miyabi-cli-standalone"
homepage = "https://github.com/ShunsukeHayashi/miyabi-cli-standalone"
description = "Miyabi - Autonomous AI Development Framework"
repository = "https://github.com/ShunsukeHayashi/mergegate"
homepage = "https://github.com/ShunsukeHayashi/mergegate"
description = "MergeGate - Deterministic task execution and merge workflow for AI-assisted development"
keywords = ["ai", "cli", "tui", "autonomous", "development"]
categories = ["command-line-utilities", "development-tools"]

View file

@ -282,7 +282,7 @@ cargo clippy --all-targets -- -D warnings
cargo fmt --all --check
# 実行
cargo run -p miyabi-cli
cargo run -p mergegate-cli --bin mergegate
```
## ライセンス

View file

@ -1,2 +1,13 @@
# miyabi-cli-standalone
Autonomous development powered by Agentic OS
# MergeGate
Deterministic execution protocol for AI-assisted development.
Execution layer for GitNexus-style code intelligence:
- `GitNexus`: understand the codebase
- `MergeGate`: execute changes safely
Current CLI entrypoints: `miyabi` and `mergegate`
- `miyabi tui` / `mergegate tui`: terminal assistant
- `miyabi gate` / `mergegate gate`: deterministic task execution, file locks, and PR/merge workflow
Start with `cargo build --release`, then run `./target/release/mergegate --help` or `./target/release/mergegate gate guide`.

134
README.md
View file

@ -1,34 +1,62 @@
# Miyabi CLI
# MergeGate
A powerful terminal-based AI assistant with TUI (Terminal User Interface) built in Rust.
MergeGate is a deterministic execution protocol for AI-assisted development.
It is the execution layer for GitNexus-style code intelligence: GitNexus helps agents understand what will break, and MergeGate makes them execute that work in a safe, verifiable order.
In short:
- `GitNexus`: understand the codebase
- `MergeGate`: execute changes safely
The project currently supports both `miyabi` and `mergegate` as CLI entrypoints. `miyabi` remains the legacy/default command, and `mergegate` is the product-aligned alias.
This repository has two common entry points:
- `miyabi tui` / `mergegate tui`: interactive terminal assistant
- `miyabi gate` / `mergegate gate`: deterministic task execution and file-lock workflow for repo work
If you arrived here because of deterministic execution, task locks, PR gates, or agent-safe repo work, start with `mergegate gate` or `miyabi gate`, not the TUI.
## 60-Second Setup
```bash
# 1. Clone and build
git clone https://github.com/ShunsukeHayashi/miyabi-cli-standalone.git
cd miyabi-cli-standalone
git clone https://github.com/ShunsukeHayashi/mergegate.git
cd mergegate
cargo build --release
# 2. Set API key
export ANTHROPIC_API_KEY="sk-ant-..."
# 3. Run
./target/release/miyabi tui
./target/release/mergegate tui
```
That's it! Start chatting with Claude in your terminal.
That's it. Start with the TUI, or jump straight into `mergegate gate` for repo workflow control.
---
## Features
## Why MergeGate
- **Interactive TUI** - Beautiful terminal interface with markdown rendering
- **Chat Mode** - Conversational AI assistant
- **Agent Mode** - Autonomous task execution with tool approval
- **Session Management** - Persist and resume conversations
- **Execution layer for code intelligence** - Pair blast-radius understanding with deterministic execution, not chat-only automation
- **Deterministic task execution** - Register, analyze, lock, review, and merge in a verifiable order
- **Repo-safe agent workflow** - File locks and gate checks reduce accidental overlap and unsafe edits
- **Interactive TUI** - Terminal assistant for chat, prompts, and agent execution
- **Agent mode** - Autonomous execution with tool approval
- **Session management** - Persist and resume conversations
- **Configurable** - TOML-based configuration
- **Extended Thinking** - Support for Claude 4.5+ extended thinking
## Positioning
MergeGate was designed for teams that already believe code understanding is not enough.
Knowing the blast radius of a change is only half of the problem. Agents also need a protocol for when work starts, which files are locked, what must be recorded before editing, and what counts as done.
That is the role split:
- `GitNexus`: query, context, impact, detect-changes, rename, wiki, graph exploration
- `MergeGate`: register, impact record, assign, file lock, branch, PR, merge, completion discipline
If GitNexus answers "what is this change connected to?", MergeGate answers "how do we carry it through safely?"
## Installation
@ -40,16 +68,22 @@ That's it! Start chatting with Claude in your terminal.
### Build from Source
```bash
git clone https://github.com/ShunsukeHayashi/miyabi-cli-standalone.git
cd miyabi-cli-standalone
git clone https://github.com/ShunsukeHayashi/mergegate.git
cd mergegate
cargo build --release
```
Binary will be at `target/release/miyabi`.
Binary will be at `target/release/miyabi` and `target/release/mergegate`.
## Quick Start
### 1. Generate Config
Choose the path that matches what you want to do.
### A. Use MergeGate as a terminal assistant
Run the TUI after configuring your API key.
#### 1. Generate Config
```bash
./target/release/miyabi init
@ -57,7 +91,7 @@ Binary will be at `target/release/miyabi`.
This creates `~/.miyabi/config.toml`.
### 2. Set API Key
#### 2. Set API Key
Edit `~/.miyabi/config.toml`:
@ -72,7 +106,7 @@ Or use environment variable:
export ANTHROPIC_API_KEY="sk-ant-..."
```
### 3. Launch TUI
#### 3. Launch TUI
```bash
./target/release/miyabi tui
@ -80,8 +114,55 @@ export ANTHROPIC_API_KEY="sk-ant-..."
./target/release/miyabi
```
### B. Use MergeGate as a task execution gate
If you are working inside a repository and want task registration, impact tracking, file locks, and PR/merge recording, use `mergegate gate` or `miyabi gate`.
#### 1. Check whether the repo is already initialized
```bash
./target/release/miyabi gate status
```
If you see `tasks: 0`, that means the ledger exists but is currently empty.
#### 2. Initialize MergeGate for this repo if needed
```bash
./target/release/miyabi gate init
```
This creates `project_memory/tasks.json` and prepares the repo for gate-managed work.
#### 3. Read the built-in workflow guide
```bash
./target/release/miyabi gate guide
```
#### 4. Start a task
```bash
./target/release/miyabi gate register --issue 123 --title "Fix login redirect"
./target/release/miyabi gate impact issue-123 --risk medium --symbols 3
./target/release/miyabi gate assign issue-123 --agent codex --node macbook --files "src/auth.rs"
```
Typical flow:
1. `register`
2. `impact`
3. `assign`
4. implement
5. `branch`
6. `pr`
7. `merge` or `manual-complete`
## Usage
`MergeGate` is the product and documentation name.
Both `miyabi` and `mergegate` work today.
### CLI Commands
```bash
@ -94,6 +175,11 @@ miyabi sessions -d <id> # Delete session
miyabi sessions -e <id> # Export session to JSON
miyabi version # Show version info
miyabi agent <prompt> # Run autonomous agent
miyabi gate status # Show task ledger status
miyabi gate init # Initialize MergeGate in the current repo
miyabi gate guide # Show the full gate workflow guide
mergegate gate status # Same gate workflow with product-aligned command
mergegate tui # Same TUI with product-aligned command
```
### CLI Options
@ -187,7 +273,7 @@ Agent mode features:
## Core Library Features
The `miyabi-core` crate provides powerful utilities for building AI applications.
The `mergegate-core` crate provides powerful utilities for building AI applications.
### Git Utilities
@ -373,10 +459,10 @@ flags.load_from_map(config);
## Project Structure
```
miyabi-cli-standalone/
mergegate/
├── crates/
│ ├── miyabi-cli/ # CLI entry point
│ ├── miyabi-core/ # Core library
│ ├── mergegate-cli/ # CLI entry point
│ ├── mergegate-core/ # Core library
│ │ ├── agent.rs # Agent system
│ │ ├── anthropic.rs # Anthropic API client
│ │ ├── cache.rs # TTL cache system
@ -391,7 +477,7 @@ miyabi-cli-standalone/
│ │ ├── session.rs # Session management
│ │ ├── tools.rs # Built-in tools
│ │ └── ...
│ └── miyabi-tui/ # TUI implementation
│ └── mergegate-tui/ # TUI implementation
│ ├── app.rs # Main application
│ ├── views.rs # UI views
│ └── ...
@ -482,7 +568,7 @@ RUST_LOG=debug ./target/release/miyabi tui
- Check `miyabi --help` for CLI options
- Press `F1` in TUI for keybindings
- Open an issue: https://github.com/ShunsukeHayashi/miyabi-cli-standalone/issues
- Open an issue: https://github.com/ShunsukeHayashi/mergegate/issues
## License

View file

@ -4,7 +4,7 @@
## 1.1 verify_merge ラッパー (~50行)
- [ ] `crates/miyabi-core/src/protocol.rs``verify_merge()` を追加
- [x] `crates/miyabi-core/src/protocol.rs``verify_merge()` を追加
- 既存の `get_pull_request()` (github.rs) を呼ぶ
- `PullRequest.merge_commit_sha` を取得
- `pr.state == "merged"` を検証
@ -12,59 +12,53 @@
- tasks.json 更新: merge_commit + state → done
- ロック解放
- 後続タスク blocked → pending
- [ ] CLI に `miyabi gate verify-merge <task-id>` サブコマンド追加
- [ ] テスト: mock PullRequest で merged → done 遷移
- [x] CLI に `miyabi gate verify-merge <task-id>` サブコマンド追加
- [x] テスト: mock PullRequest で merged → done 遷移
## 1.2 escape hatch (~50行)
- [ ] `protocol.rs``force_unlock()` 追加
- [x] `protocol.rs``force_unlock()` 追加
```
fn force_unlock(&self, task_id: &str, reason: &str, operator: &str) -> Result<()>
```
- ロック即解放
- event log に reason + operator を記録
- state は変更しないimplementing のまま)
- [ ] `protocol.rs``manual_complete()` 追加
- [x] `protocol.rs``manual_complete()` 追加
```
fn manual_complete(&self, task_id: &str, reason: &str, operator: &str) -> Result<()>
```
- PR/merge なしで done に遷移
- event log に reason + operator + "manual" を記録
- CompletionMode::Manual として区別
- [ ] CLI に `miyabi gate force-unlock <task-id> --reason R --operator O` 追加
- [ ] CLI に `miyabi gate manual-complete <task-id> --reason R --operator O` 追加
- [ ] テスト: force_unlock → ロック解放確認
- [ ] テスト: manual_complete → done 遷移 + event 記録
- `CompletionMode::Manual` として区別2026-04-10 実装反映)
- [x] CLI に `miyabi gate force-unlock <task-id> --reason R --operator O` 追加
- [x] CLI に `miyabi gate manual-complete <task-id> --reason R --operator O` 追加
- [x] テスト: force_unlock → ロック解放確認
- [x] テスト: manual_complete → done 遷移 + event 記録
## 1.3 E2E テスト (~100行)
- [ ] `crates/miyabi-core/src/protocol.rs` の tests モジュールに E2E 追加
- [x] `crates/miyabi-core/src/protocol.rs` の tests モジュールに E2E 追加`full_lifecycle_register_to_merged_releases_lock` — マージ完了時は `TaskState::Merged`
```
#[test]
fn full_lifecycle_register_to_done() {
// 1. register(issue=1, title="test")
// 2. check_dependencies → ready
// 3. record_impact(risk=LOW)
// 4. assign_and_lock(agent="test", files=["src/test.rs"])
// 5. record_branch("feature/issue-1-test")
// 6. record_pr(42)
// 7. record_merge("a1b2c3d4...40hex")
// 8. assert: state == Done, lock == None
fn full_lifecycle_register_to_merged_releases_lock() {
// register → impact → assign → branch → pr → merge(40hex)
// assert: Merged, lock == None
}
```
- [ ] GATE 拒否テスト: issue=0 → GateError
- [ ] GATE 拒否テスト: impact なしで assign → GateError
- [ ] GATE 拒否テスト: HIGH risk + 承認なし → GateError
- [ ] GATE 拒否テスト: ロック競合 → LockError
- [ ] GATE 拒否テスト: 不正 SHA → GateError
- [ ] escape hatch テスト: force_unlock + manual_complete
- [x] GATE 拒否テスト: issue=0 → GateError既存テスト群でカバー
- [x] GATE 拒否テスト: impact なしで assign → GateError既存
- [x] GATE 拒否テスト: HIGH risk + 承認なし → GateError既存
- [x] GATE 拒否テスト: ロック競合 → LockError既存
- [x] GATE 拒否テスト: 不正 SHA → GateError既存
- [x] escape hatch テスト: force_unlock + manual_complete
## 1.4 最終確認
- [ ] `cargo test --all` → 全 GREEN
- [ ] `cargo clippy --all-targets --all-features -- -D warnings` → ゼロエラー
- [ ] `cargo build --release` → リリースビルド成功
- [ ] `npx gitnexus analyze --force` → 再インデックス
- [ ] `git tag v1.0-dtp-complete`
- [x] `cargo test --all` → 全 GREEN
- [x] `cargo clippy --all-targets --all-features -- -D warnings` → ゼロエラー
- [x] `cargo build --release` → リリースビルド成功
- [ ] `npx gitnexus analyze --force` → 再インデックス(必要時に手動)
- [ ] `git tag v1.0-dtp-complete`(リリース判断後)
- [ ] `git push origin v1.0-dtp-complete`
- [ ] `~/bin/announce "Polaris v1.0 完成。全 GATE 実装済み。テスト GREEN。"`

View file

@ -1,5 +1,5 @@
[package]
name = "miyabi-cli"
name = "mergegate-cli"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
@ -7,16 +7,20 @@ authors.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
description = "Miyabi CLI - Main command-line interface"
description = "MergeGate CLI - Main command-line interface for deterministic task execution and agent-assisted development"
[[bin]]
name = "miyabi"
path = "src/main.rs"
[[bin]]
name = "mergegate"
path = "src/bin/mergegate.rs"
[dependencies]
# Workspace crates
miyabi-core = { path = "../miyabi-core" }
miyabi-tui = { path = "../miyabi-tui" }
miyabi-core = { package = "mergegate-core", path = "../mergegate-core" }
miyabi-tui = { package = "mergegate-tui", path = "../mergegate-tui" }
# CLI
clap = { workspace = true }
@ -32,6 +36,7 @@ tokio = { workspace = true }
anyhow = { workspace = true }
# Serialization
serde = { workspace = true }
serde_json = { workspace = true }
# Utilities

View file

@ -0,0 +1 @@
include!("../main.rs");

View file

@ -1,8 +1,9 @@
//! Miyabi CLI - Main entry point
/// MergeGate CLI - Main entry point
use chrono::Duration as ChronoDuration;
use clap::{Parser, Subcommand, ValueEnum};
use clap::{CommandFactory, FromArgMatches, Parser, Subcommand, ValueEnum};
use miyabi_core::{FeatureFlagManager, RulesLoader};
use serde::Serialize;
use std::collections::HashMap;
use std::fs;
use std::io::{self, BufRead, BufReader, Write};
@ -13,6 +14,7 @@ use tracing_subscriber::EnvFilter;
/// Global feature flags manager
static FEATURE_FLAGS: std::sync::OnceLock<FeatureFlagManager> = std::sync::OnceLock::new();
static INVOKED_BINARY_NAME: std::sync::OnceLock<String> = std::sync::OnceLock::new();
/// Get the global feature flags manager
pub fn feature_flags() -> &'static FeatureFlagManager {
@ -27,9 +29,40 @@ pub fn feature_flags() -> &'static FeatureFlagManager {
})
}
fn detect_invoked_binary_name() -> String {
std::env::args()
.next()
.and_then(|path| {
std::path::Path::new(&path)
.file_name()
.map(|name| name.to_string_lossy().into_owned())
})
.filter(|name| !name.is_empty())
.unwrap_or_else(|| "miyabi".to_string())
}
fn current_binary_name() -> &'static str {
INVOKED_BINARY_NAME
.get_or_init(detect_invoked_binary_name)
.as_str()
}
fn gate_command(command: &str) -> String {
if command.is_empty() {
format!("{} gate", current_binary_name())
} else {
format!("{} gate {}", current_binary_name(), command)
}
}
fn agent_guide() -> String {
AGENT_GUIDE_TEMPLATE
.replace("{{GATE}}", &gate_command(""))
.replace("{{BINARY}}", current_binary_name())
}
#[derive(Parser)]
#[command(name = "miyabi")]
#[command(author, version, about = "Miyabi - Autonomous AI Development Framework", long_about = None)]
#[command(author, version, about = "MergeGate - Deterministic task execution and merge workflow for AI-assisted development", long_about = None)]
struct Cli {
/// Model to use (overrides config)
#[arg(short, long)]
@ -102,12 +135,15 @@ enum Commands {
},
#[command(
about = "Deterministic Task Protocol gate controls",
long_about = "Run miyabi gate init to set up a new project.\n\nDeterministic Task Protocol gate controls"
long_about = "Run the gate init command to set up a new project.\n\nDeterministic Task Protocol gate controls"
)]
Gate {
/// Output format
#[arg(long, value_enum, default_value_t = OutputFormat::Text)]
format: OutputFormat,
/// Emit a hook-friendly JSON event envelope to stdout
#[arg(long)]
emit_event: bool,
/// Path to the task ledger JSON file
#[arg(long, default_value = "project_memory/tasks.json")]
store_path: PathBuf,
@ -278,7 +314,7 @@ enum GateCommand {
},
}
#[derive(Debug)]
#[derive(Debug, Serialize)]
struct AssignPlanAttachment {
attachment_type: String,
source: String,
@ -286,7 +322,7 @@ struct AssignPlanAttachment {
content: String,
}
#[derive(Debug)]
#[derive(Debug, Serialize)]
struct AssignExecutionPlan {
task_title: String,
risk_level: Option<String>,
@ -296,7 +332,7 @@ struct AssignExecutionPlan {
next_steps: Vec<String>,
}
#[derive(Debug)]
#[derive(Debug, Serialize)]
struct InitStatus {
initialized: bool,
current_dir: String,
@ -409,7 +445,11 @@ async fn main() -> anyhow::Result<()> {
.with_env_filter(EnvFilter::from_default_env())
.init();
let cli = Cli::parse();
let invoked_binary_name = detect_invoked_binary_name();
let _ = INVOKED_BINARY_NAME.set(invoked_binary_name.clone());
let clap_binary_name: &'static str = Box::leak(invoked_binary_name.into_boxed_str());
let matches = Cli::command().name(clap_binary_name).get_matches();
let cli = Cli::from_arg_matches(&matches).unwrap_or_else(|error| error.exit());
match cli.command {
Some(Commands::Tui) | None => {
@ -874,10 +914,11 @@ async fn main() -> anyhow::Result<()> {
}
Some(Commands::Gate {
format,
emit_event,
store_path,
command,
}) => {
let code = handle_gate_command(&format, &store_path, command)?;
let code = handle_gate_command(&format, emit_event, &store_path, command)?;
std::process::exit(code);
}
Some(Commands::Openclaw { command }) => {
@ -1245,6 +1286,7 @@ async fn main() -> anyhow::Result<()> {
fn handle_gate_command(
format: &OutputFormat,
emit_event: bool,
store_path: &std::path::Path,
command: GateCommand,
) -> anyhow::Result<i32> {
@ -1263,7 +1305,10 @@ fn handle_gate_command(
let result = match command {
GateCommand::Init => {
initialize_gate_project(format, store_path)?;
let status = initialize_gate_project(format, emit_event, store_path)?;
if emit_event {
emit_gate_event("gate_initialized", None, &status);
}
Ok(())
}
GateCommand::Register {
@ -1297,7 +1342,9 @@ fn handle_gate_command(
&node,
)
.map(|task| {
if matches!(format, OutputFormat::Json) {
if emit_event {
emit_gate_event("task_registered", Some(&task.id), &task);
} else if matches!(format, OutputFormat::Json) {
println!("{}", serde_json::to_string_pretty(&task).unwrap());
} else {
println!("registered: {} ({})", task.id, task.title);
@ -1312,20 +1359,22 @@ fn handle_gate_command(
.status(task_id.as_deref())
.map(|status| match status {
StatusReport::Task(task) => {
if matches!(format, OutputFormat::Json) {
if emit_event {
emit_gate_event("task_status", Some(&task.id), &task);
} else if matches!(format, OutputFormat::Json) {
println!("{}", serde_json::to_string_pretty(&task).unwrap());
} else {
println!("{}: {:?} - {}", task.id, task.current_state, task.title);
print_gate_task_status(&task);
}
}
StatusReport::Snapshot(snapshot) => {
if matches!(format, OutputFormat::Json) {
if emit_event {
emit_gate_event("status_snapshot", None, &snapshot);
} else if matches!(format, OutputFormat::Json) {
println!("{}", serde_json::to_string_pretty(&snapshot).unwrap());
} else {
println!("tasks: {}", snapshot.tasks.len());
for task in snapshot.tasks {
println!(" {} [{:?}] {}", task.id, task.current_state, task.title);
}
let dispatchable = protocol.dispatchable().ok();
print_gate_snapshot_status(&snapshot, dispatchable.as_ref());
}
}
})
@ -1340,15 +1389,14 @@ fn handle_gate_command(
.and_then(|result| {
let attachments = protocol.attach_context(&task_id, actor, &node)?;
let plan = build_assign_execution_plan(&result.task, attachments);
if matches!(format, OutputFormat::Json) {
println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
"assignment": result,
"plan": assign_plan_to_json(&plan),
}))
.unwrap()
);
let output = serde_json::json!({
"assignment": result,
"plan": assign_plan_to_json(&plan),
});
if emit_event {
emit_gate_event("lock_acquired", Some(&task_id), &output);
} else if matches!(format, OutputFormat::Json) {
println!("{}", serde_json::to_string_pretty(&output).unwrap());
} else {
println!("assigned: {} -> {}@{}", result.task.id, agent, agent_node);
print_assign_execution_plan(&result, &plan);
@ -1383,7 +1431,9 @@ fn handle_gate_command(
&node,
)
.map(|task| {
if matches!(format, OutputFormat::Json) {
if emit_event {
emit_gate_event("impact_recorded", Some(&task.id), &task);
} else if matches!(format, OutputFormat::Json) {
println!("{}", serde_json::to_string_pretty(&task).unwrap());
} else {
println!("impact recorded: {}", task.id);
@ -1392,7 +1442,9 @@ fn handle_gate_command(
GateCommand::Branch { task_id, name } => protocol
.record_branch(&task_id, &name, actor, &node)
.map(|task| {
if matches!(format, OutputFormat::Json) {
if emit_event {
emit_gate_event("branch_created", Some(&task.id), &task);
} else if matches!(format, OutputFormat::Json) {
println!("{}", serde_json::to_string_pretty(&task).unwrap());
} else {
println!("branch recorded: {} -> {}", task.id, name);
@ -1402,7 +1454,9 @@ fn handle_gate_command(
protocol
.attach_context(&task_id, actor, &node)
.map(|attachments| {
if matches!(format, OutputFormat::Json) {
if emit_event {
emit_gate_event("context_attached", Some(&task_id), &attachments);
} else if matches!(format, OutputFormat::Json) {
println!("{}", serde_json::to_string_pretty(&attachments).unwrap());
} else if attachments.is_empty() {
println!("no context attachments: {}", task_id);
@ -1427,7 +1481,9 @@ fn handle_gate_command(
protocol
.refresh_context(&task_id, actor, &node)
.map(|attachments| {
if matches!(format, OutputFormat::Json) {
if emit_event {
emit_gate_event("context_refreshed", Some(&task_id), &attachments);
} else if matches!(format, OutputFormat::Json) {
println!("{}", serde_json::to_string_pretty(&attachments).unwrap());
} else if attachments.is_empty() {
println!("no context attachments: {}", task_id);
@ -1448,7 +1504,9 @@ fn handle_gate_command(
GateCommand::Pr { task_id, number } => protocol
.record_pr(&task_id, number, actor, &node)
.map(|task| {
if matches!(format, OutputFormat::Json) {
if emit_event {
emit_gate_event("pr_created", Some(&task.id), &task);
} else if matches!(format, OutputFormat::Json) {
println!("{}", serde_json::to_string_pretty(&task).unwrap());
} else {
println!("pr recorded: {} -> #{}", task.id, number);
@ -1457,7 +1515,9 @@ fn handle_gate_command(
GateCommand::Merge { task_id, sha } => protocol
.record_merge(&task_id, &sha, actor, &node)
.map(|task| {
if matches!(format, OutputFormat::Json) {
if emit_event {
emit_gate_event("task_completed", Some(&task.id), &task);
} else if matches!(format, OutputFormat::Json) {
println!("{}", serde_json::to_string_pretty(&task).unwrap());
} else {
println!("merge recorded: {} -> {}", task.id, sha);
@ -1466,7 +1526,9 @@ fn handle_gate_command(
GateCommand::VerifyMerge { task_id, repo } => protocol
.verify_merge(&task_id, &repo, actor, &node)
.map(|task| {
if matches!(format, OutputFormat::Json) {
if emit_event {
emit_gate_event("task_completed", Some(&task.id), &task);
} else if matches!(format, OutputFormat::Json) {
println!("{}", serde_json::to_string_pretty(&task).unwrap());
} else {
let sha = task
@ -1484,7 +1546,9 @@ fn handle_gate_command(
} => protocol
.force_unlock(&task_id, &reason, &operator)
.map(|task| {
if matches!(format, OutputFormat::Json) {
if emit_event {
emit_gate_event("lock_released", Some(&task.id), &task);
} else if matches!(format, OutputFormat::Json) {
println!("{}", serde_json::to_string_pretty(&task).unwrap());
} else {
println!("lock released: {} by {}", task.id, operator);
@ -1497,14 +1561,18 @@ fn handle_gate_command(
} => protocol
.manual_complete(&task_id, &reason, &operator)
.map(|task| {
if matches!(format, OutputFormat::Json) {
if emit_event {
emit_gate_event("task_completed", Some(&task.id), &task);
} else if matches!(format, OutputFormat::Json) {
println!("{}", serde_json::to_string_pretty(&task).unwrap());
} else {
println!("task completed manually: {} by {}", task.id, operator);
}
}),
GateCommand::Locks => protocol.locks().map(|locks| {
if matches!(format, OutputFormat::Json) {
if emit_event {
emit_gate_event("locks_reported", None, &locks);
} else if matches!(format, OutputFormat::Json) {
println!("{}", serde_json::to_string_pretty(&locks).unwrap());
} else if locks.is_empty() {
println!("no active locks");
@ -1515,7 +1583,9 @@ fn handle_gate_command(
}
}),
GateCommand::Dag => protocol.dag().map(|report| {
if matches!(format, OutputFormat::Json) {
if emit_event {
emit_gate_event("dag_reported", None, &report);
} else if matches!(format, OutputFormat::Json) {
println!("{}", serde_json::to_string_pretty(&report).unwrap());
} else {
for (index, level) in report.levels.iter().enumerate() {
@ -1524,10 +1594,16 @@ fn handle_gate_command(
}
}),
GateCommand::Dispatchable => protocol.dispatchable().map(|report| {
if matches!(format, OutputFormat::Json) {
if emit_event {
emit_gate_event("dispatchable_reported", None, &report);
} else if matches!(format, OutputFormat::Json) {
println!("{}", serde_json::to_string_pretty(&report).unwrap());
} else if report.tasks.is_empty() {
println!("no dispatchable tasks");
println!("next:");
println!(" {}", gate_command("status"));
println!(" {}", gate_command("dag"));
println!(" {}", gate_command("guide"));
} else {
for task in report.tasks {
println!("{} [{}] {}", task.id, task.priority, task.title);
@ -1536,6 +1612,13 @@ fn handle_gate_command(
}),
GateCommand::Serve { port } => {
serve_dashboard(store_path, port)?;
if emit_event {
emit_gate_event(
"dashboard_started",
None,
serde_json::json!({ "port": port }),
);
}
Ok(())
}
GateCommand::Dream {
@ -1553,7 +1636,9 @@ fn handle_gate_command(
protocol
.dream(since, auto, &repo_root, actor, &node)
.and_then(|report| {
if matches!(format, OutputFormat::Json) {
if emit_event {
emit_gate_event("dream_recorded", None, &report);
} else if matches!(format, OutputFormat::Json) {
println!("{}", serde_json::to_string_pretty(&report).unwrap());
} else {
print_dream_report(&report);
@ -1586,15 +1671,14 @@ fn handle_gate_command(
Err(ProtocolError::input("heartbeat currently requires --all"))
} else {
protocol.heartbeat_all().map(|renewed| {
if matches!(format, OutputFormat::Json) {
println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
"renewed": renewed,
"count": renewed.len(),
}))
.unwrap()
);
let output = serde_json::json!({
"renewed": renewed,
"count": renewed.len(),
});
if emit_event {
emit_gate_event("heartbeat_renewed", None, &output);
} else if matches!(format, OutputFormat::Json) {
println!("{}", serde_json::to_string_pretty(&output).unwrap());
} else {
println!("renewed leases: {}", renewed.len());
for task_id in renewed {
@ -1605,7 +1689,7 @@ fn handle_gate_command(
}
}
GateCommand::Guide => {
print!("{AGENT_GUIDE}");
print!("{}", agent_guide());
Ok(())
}
};
@ -1613,19 +1697,19 @@ fn handle_gate_command(
Ok(match result {
Ok(()) => 0,
Err(ProtocolError::GateRejected(message)) => {
emit_gate_error(format, "gate_rejected", &message);
emit_gate_error(format, emit_event, "gate_rejected", &message);
1
}
Err(ProtocolError::DependencyBlocked(message)) => {
emit_gate_error(format, "gate_rejected", &message);
emit_gate_error(format, emit_event, "gate_rejected", &message);
1
}
Err(ProtocolError::Input(message)) => {
emit_gate_error(format, "input_error", &message);
emit_gate_error(format, emit_event, "input_error", &message);
2
}
Err(ProtocolError::Internal(error)) => {
emit_gate_error(format, "internal_error", &error.to_string());
emit_gate_error(format, emit_event, "internal_error", &error.to_string());
1
}
})
@ -1678,17 +1762,23 @@ fn assign_next_steps(
miyabi_core::store::CompletionMode::GithubPr => vec![
"1. Create branch".to_string(),
"2. Make changes".to_string(),
format!("3. miyabi gate branch {task_id} ..."),
format!("4. miyabi gate pr {task_id} ..."),
format!("5. miyabi gate merge {task_id} ..."),
format!("3. {} {task_id} ...", gate_command("branch")),
format!("4. {} {task_id} ...", gate_command("pr")),
format!("5. {} {task_id} ...", gate_command("merge")),
],
miyabi_core::store::CompletionMode::Manual => vec![
"1. Complete the work".to_string(),
format!("2. miyabi gate manual-complete {task_id} --reason ... --operator ..."),
format!(
"2. {} {task_id} --reason ... --operator ...",
gate_command("manual-complete")
),
],
miyabi_core::store::CompletionMode::ExternalOp => vec![
"1. Complete external operation".to_string(),
format!("2. miyabi gate manual-complete {task_id} --reason ... --operator ..."),
format!(
"2. {} {task_id} --reason ... --operator ...",
gate_command("manual-complete")
),
],
}
}
@ -1737,10 +1827,145 @@ fn print_assign_execution_plan(
}
}
fn print_gate_task_status(task: &miyabi_core::store::ExecutionTask) {
println!("task: {}", task.id);
println!("title: {}", task.title);
println!("state: {:?}", task.current_state);
if task.issue_number > 0 {
println!("issue: #{}", task.issue_number);
}
println!("completion mode: {:?}", task.completion_mode);
if let Some(impact) = &task.impact {
println!(
"impact: {:?} (symbols: {})",
impact.risk_level, impact.affected_symbols
);
} else {
println!("impact: not recorded");
}
match task.current_state {
miyabi_core::store::TaskState::Pending | miyabi_core::store::TaskState::Draft => {
if task.impact.is_none() {
println!("next:");
println!(" {} {} --risk low --symbols 0", gate_command("impact"), task.id);
println!(
" {} {} --agent <name> --node <machine> --files \"path/to/file\"",
gate_command("assign"),
task.id
);
} else {
println!("next:");
println!(
" {} {} --agent <name> --node <machine> --files \"path/to/file\"",
gate_command("assign"),
task.id
);
}
}
miyabi_core::store::TaskState::Implementing => {
println!("next:");
println!(" {} {} <branch-name>", gate_command("branch"), task.id);
println!(" {} {}", gate_command("attach"), task.id);
}
miyabi_core::store::TaskState::Reviewing => {
println!("next:");
println!(" {} {} <number>", gate_command("pr"), task.id);
println!(" {} {} <40-char-sha>", gate_command("merge"), task.id);
}
miyabi_core::store::TaskState::Merged | miyabi_core::store::TaskState::Done => {
println!("next:");
println!(" {}", gate_command("dispatchable"));
}
_ => {}
}
}
fn print_gate_snapshot_status(
snapshot: &miyabi_core::store::TasksSnapshot,
dispatchable: Option<&miyabi_core::protocol::DispatchableReport>,
) {
if snapshot.tasks.is_empty() {
println!("tasks: 0");
println!("status: ready to initialize or register your first task");
println!("this is not an error. it means the ledger is empty.");
println!("next:");
println!(" {}", gate_command("init"));
println!(" {} --issue <N> --title \"Your task\"", gate_command("register"));
println!(" {}", gate_command("guide"));
return;
}
let total = snapshot.tasks.len();
let completed = snapshot
.tasks
.iter()
.filter(|task| {
matches!(
task.current_state,
miyabi_core::store::TaskState::Done | miyabi_core::store::TaskState::Merged
)
})
.count();
let active = snapshot
.tasks
.iter()
.filter(|task| {
matches!(
task.current_state,
miyabi_core::store::TaskState::Implementing
| miyabi_core::store::TaskState::Reviewing
| miyabi_core::store::TaskState::Analyzing
)
})
.count();
let waiting = total.saturating_sub(completed + active);
let dispatchable_count = dispatchable.map(|report| report.count).unwrap_or(0);
println!(
"tasks: {} (dispatchable: {}, locks: {})",
total,
dispatchable_count,
snapshot.file_locks.len()
);
println!(
"summary: {} completed, {} active, {} waiting",
completed, active, waiting
);
if let Some(report) = dispatchable {
if !report.tasks.is_empty() {
println!("next tasks:");
for task in report.tasks.iter().take(3) {
println!(" {} [{}] {}", task.id, task.priority, task.title);
}
println!("next:");
println!(" {} <task-id>", gate_command("status"));
println!(
" {} <task-id> --agent <name> --node <machine> --files \"path/to/file\"",
gate_command("assign")
);
} else {
println!("next:");
println!(" {}", gate_command("dag"));
println!(" {}", gate_command("locks"));
println!(" {}", gate_command("guide"));
}
}
println!("all tasks:");
for task in &snapshot.tasks {
println!(" {} [{:?}] {}", task.id, task.current_state, task.title);
}
}
fn initialize_gate_project(
format: &OutputFormat,
emit_event: bool,
store_path: &std::path::Path,
) -> anyhow::Result<()> {
) -> anyhow::Result<InitStatus> {
let current_dir = std::env::current_dir()?;
let created_path = store_path.display().to_string();
let initialized = if store_path.exists() {
@ -1773,6 +1998,10 @@ fn initialize_gate_project(
github_project_detected,
};
if emit_event {
return Ok(status);
}
if matches!(format, OutputFormat::Json) {
println!(
"{}",
@ -1785,15 +2014,15 @@ fn initialize_gate_project(
"gitignore_updated": status.gitignore_updated,
"github_project_detected": status.github_project_detected,
"next_steps": [
"miyabi gate register --issue <N> --title ...",
"miyabi gate status",
"miyabi gate --help"
gate_command("status"),
gate_command("guide"),
format!("{} --issue <N> --title ...", gate_command("register"))
],
}))?
);
} else {
if status.initialized {
println!("Polaris initialized in {}", status.current_dir);
println!("MergeGate initialized in {}", status.current_dir);
println!("Created: {}", status.created_path);
} else {
println!("Already initialized");
@ -1805,13 +2034,13 @@ fn initialize_gate_project(
println!("⚠️ No GitHub remote. Run: gh repo create <name> --private");
}
println!("Next steps:");
println!(" miyabi gate register --issue <N> --title ...");
println!(" miyabi gate status");
println!(" miyabi gate --help");
println!(" {}", gate_command("status"));
println!(" {}", gate_command("guide"));
println!(" {} --issue <N> --title ...", gate_command("register"));
print_init_checklist(&status);
}
Ok(())
Ok(status)
}
fn print_init_checklist(status: &InitStatus) {
@ -2011,8 +2240,24 @@ fn print_dream_report(report: &miyabi_core::DreamReport) {
}
}
fn emit_gate_error(format: &OutputFormat, kind: &str, message: &str) {
if matches!(format, OutputFormat::Json) {
fn emit_gate_event(event: &str, task_id: Option<&str>, payload: impl serde::Serialize) {
println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
"event": event,
"task_id": task_id,
"source": "miyabi-gate",
"ts": chrono::Utc::now(),
"payload": payload,
}))
.unwrap()
);
}
fn emit_gate_error(format: &OutputFormat, emit_event: bool, kind: &str, message: &str) {
if emit_event {
emit_gate_event(kind, None, serde_json::json!({ "message": message }));
} else if matches!(format, OutputFormat::Json) {
println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
@ -2026,12 +2271,12 @@ fn emit_gate_error(format: &OutputFormat, kind: &str, message: &str) {
}
}
const AGENT_GUIDE: &str = r#"
# Polaris (miyabi-gate) Agent Guide
const AGENT_GUIDE_TEMPLATE: &str = r#"
# MergeGate ({{GATE}}) Agent Guide
## What is this?
Polaris is a deterministic task execution protocol. It enforces a strict
MergeGate is a deterministic task execution protocol. It enforces a strict
workflow so that any agent on any machine produces the same verifiable result.
Tasks are tracked in project_memory/tasks.json, not in conversation memory.
@ -2047,53 +2292,53 @@ Tasks are tracked in project_memory/tasks.json, not in conversation memory.
```
Step 1: Register
miyabi-gate gate register --issue <N> --title "Task description"
{{GATE}} register --issue <N> --title "Task description"
Step 2: Impact analysis
miyabi-gate gate impact <task-id> --risk <low|medium|high|critical> --symbols <N>
{{GATE}} impact <task-id> --risk <low|medium|high|critical> --symbols <N>
# Add --approve if risk is high or critical
Step 3: Assign (acquires file locks)
miyabi-gate gate assign <task-id> --agent <your-name> --node <machine> --files "file1.rs,file2.rs"
{{GATE}} assign <task-id> --agent <your-name> --node <machine> --files "file1.rs,file2.rs"
# This prints an execution plan and context attachments. Read them.
Step 4: Work
# Edit ONLY the locked files. Pre-commit hook blocks unlocked files.
Step 5: Branch
miyabi-gate gate branch <task-id> feature/issue-<N>-<slug>
{{GATE}} branch <task-id> feature/issue-<N>-<slug>
Step 6: PR
miyabi-gate gate pr <task-id> <PR-number>
{{GATE}} pr <task-id> <PR-number>
Step 7: Merge
miyabi-gate gate merge <task-id> <merge-commit-sha>
{{GATE}} merge <task-id> <merge-commit-sha>
```
## For document-only tasks (no PR needed)
```
miyabi-gate gate register --issue <N> --title "Doc task" --completion-mode manual
miyabi-gate gate impact <task-id> --risk low --symbols 0
miyabi-gate gate assign <task-id> --agent <name> --node <machine> --files "docs/file.md"
{{GATE}} register --issue <N> --title "Doc task" --completion-mode manual
{{GATE}} impact <task-id> --risk low --symbols 0
{{GATE}} assign <task-id> --agent <name> --node <machine> --files "docs/file.md"
# ... do the work ...
miyabi-gate gate manual-complete <task-id> --reason "reason" --operator <name>
{{GATE}} manual-complete <task-id> --reason "reason" --operator <name>
```
## Checking state
```
miyabi-gate gate status # All tasks
miyabi-gate gate status <task-id> # One task
miyabi-gate gate locks # Active file locks
miyabi-gate gate dag # Dependency graph
miyabi-gate gate dispatchable # Tasks ready to work on
miyabi-gate gate attach <task-id> # View context attachments
{{GATE}} status # All tasks
{{GATE}} status <task-id> # One task
{{GATE}} locks # Active file locks
{{GATE}} dag # Dependency graph
{{GATE}} dispatchable # Tasks ready to work on
{{GATE}} attach <task-id> # View context attachments
```
## Context attachments (auto-injected on assign)
When you run `assign`, Polaris automatically attaches:
When you run `assign`, MergeGate automatically attaches:
- GitHub Issue body
- Impact analysis result
- Obsidian vault notes matching the task title (if OBSIDIAN_VAULT_PATH is set)
@ -2101,15 +2346,17 @@ When you run `assign`, Polaris automatically attaches:
- First 30 lines of each locked file
- First 30 lines of depth-1 impact files (direct callers)
Use `miyabi-gate gate attach <task-id> --format json` to get this as JSON
Use `{{GATE}} attach <task-id>` to inspect the attachments.
Use `{{GATE}} --format json status` or `{{GATE}} --format json locks`
when you need machine-readable output from supported commands.
for programmatic injection into prompts.
## Emergency commands
```
miyabi-gate gate force-unlock <task-id> --reason "why" --operator <name>
miyabi-gate gate manual-complete <task-id> --reason "why" --operator <name>
miyabi-gate gate heartbeat --all # Renew all lease heartbeats
{{GATE}} force-unlock <task-id> --reason "why" --operator <name>
{{GATE}} manual-complete <task-id> --reason "why" --operator <name>
{{GATE}} heartbeat --all # Renew all lease heartbeats
```
## Exit codes
@ -2128,23 +2375,23 @@ cargo clippy --all-targets --all-features -- -D warnings
## Self-improvement
```
miyabi-gate gate dream # Extract learnings from event log
miyabi-gate gate dream --auto # Also write High learnings to docs/ and update SKILL.md
miyabi-gate gate serve # Web dashboard at localhost:4848
{{GATE}} dream # Extract learnings from event log
{{GATE}} dream --auto # Also write High learnings to docs/ and update SKILL.md
{{GATE}} serve # Web dashboard at localhost:4848
```
## Command Reference
### init
Initialize project memory in the current repo.
miyabi-gate gate init
{{GATE}} init
### register
Register a new task. Creates an entry in tasks.json.
miyabi-gate gate register --issue <N> --title "Title"
miyabi-gate gate register --issue <N> --title "Title" --completion-mode manual
miyabi-gate gate register --issue <N> --title "Title" --dependencies dep-1,dep-2
miyabi-gate gate register --issue <N> --title "Title" --no-bus
{{GATE}} register --issue <N> --title "Title"
{{GATE}} register --issue <N> --title "Title" --completion-mode manual
{{GATE}} register --issue <N> --title "Title" --dependencies dep-1,dep-2
{{GATE}} register --issue <N> --title "Title" --no-bus
Options:
--issue <N> GitHub issue number (required, 0 = auto-create)
--title <TEXT> Task title (required)
@ -2157,9 +2404,9 @@ miyabi-gate gate serve # Web dashboard at localhost:4848
### impact
Record impact analysis for a task.
miyabi-gate gate impact <task-id> --risk low --symbols 3
miyabi-gate gate impact <task-id> --risk high --symbols 12 --approve
miyabi-gate gate impact <task-id> --risk medium --symbols 5 --depth1 "src/a.rs,src/b.rs"
{{GATE}} impact <task-id> --risk low --symbols 3
{{GATE}} impact <task-id> --risk high --symbols 12 --approve
{{GATE}} impact <task-id> --risk medium --symbols 5 --depth1 "src/a.rs,src/b.rs"
Options:
--risk <LEVEL> low | medium | high | critical
--symbols <N> Number of affected symbols
@ -2170,7 +2417,7 @@ miyabi-gate gate serve # Web dashboard at localhost:4848
### assign
Acquire file locks and start implementation.
miyabi-gate gate assign <task-id> --agent codex --node macbook --files "src/main.rs,src/lib.rs"
{{GATE}} assign <task-id> --agent codex --node macbook --files "src/main.rs,src/lib.rs"
Options:
--agent <NAME> Agent name (required)
--node <NAME> Machine name (required)
@ -2178,63 +2425,61 @@ miyabi-gate gate serve # Web dashboard at localhost:4848
### branch
Record branch creation.
miyabi-gate gate branch <task-id> feature/issue-45-auth
{{GATE}} branch <task-id> feature/issue-45-auth
### pr
Record PR creation.
miyabi-gate gate pr <task-id> 88
{{GATE}} pr <task-id> 88
### merge
Record merge verification. Releases locks and unblocks dependents.
miyabi-gate gate merge <task-id> <40-char-SHA>
{{GATE}} merge <task-id> <40-char-SHA>
### status
Show task status.
miyabi-gate gate status # All tasks
miyabi-gate gate status <task-id> # One task
miyabi-gate gate status --format json
{{GATE}} status # All tasks
{{GATE}} status <task-id> # One task
{{GATE}} --format json status
### locks
List active file locks.
miyabi-gate gate locks
miyabi-gate gate locks --format json
{{GATE}} locks
{{GATE}} --format json locks
### dag
Show DAG dependency levels.
miyabi-gate gate dag
{{GATE}} dag
### dispatchable
Show tasks ready to be worked on (dependencies resolved, no lock).
miyabi-gate gate dispatchable
miyabi-gate gate dispatchable --format json
{{GATE}} dispatchable
### attach
View context attachments for a task.
miyabi-gate gate attach <task-id>
miyabi-gate gate attach <task-id> --format json
{{GATE}} attach <task-id>
### refresh
Force-refresh context attachments (clears cache).
miyabi-gate gate refresh <task-id>
{{GATE}} refresh <task-id>
### verify-merge
Verify merge state via GitHub API.
miyabi-gate gate verify-merge <task-id> --repo owner/repo
{{GATE}} verify-merge <task-id> --repo owner/repo
### force-unlock
Emergency: release a lock without completing the task.
miyabi-gate gate force-unlock <task-id> --reason "why" --operator "name"
{{GATE}} force-unlock <task-id> --reason "why" --operator "name"
### manual-complete
Complete a task without merge verification (for doc/ops tasks).
miyabi-gate gate manual-complete <task-id> --reason "why" --operator "name"
{{GATE}} manual-complete <task-id> --reason "why" --operator "name"
### dream
Analyze event logs and extract learnings.
miyabi-gate gate dream
miyabi-gate gate dream --since 24h
miyabi-gate gate dream --auto
miyabi-gate gate dream --auto --vault-path /path/to/obsidian
{{GATE}} dream
{{GATE}} dream --since 24h
{{GATE}} dream --auto
{{GATE}} dream --auto --vault-path /path/to/obsidian
Options:
--since <DURATION> Filter events (e.g. 24h, 7d, 30m)
--auto Write High learnings to docs/ + update SKILL.md
@ -2242,18 +2487,18 @@ miyabi-gate gate serve # Web dashboard at localhost:4848
### heartbeat
Renew lock lease heartbeats.
miyabi-gate gate heartbeat --all
{{GATE}} heartbeat --all
### serve
Start web dashboard.
miyabi-gate gate serve
miyabi-gate gate serve --port 8080
{{GATE}} serve
{{GATE}} serve --port 8080
Options:
--port <N> Port number (default: 4848)
### guide
Print this guide.
miyabi-gate gate guide
{{GATE}} guide
### Global options (apply to all gate commands)
--format <text|json> Output format (default: text)
@ -2265,7 +2510,7 @@ const POLARIS_DASHBOARD_HTML: &str = r##"<!DOCTYPE html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Polaris Dashboard</title>
<title>MergeGate Dashboard</title>
<style>
:root {
color-scheme: light;
@ -2371,7 +2616,7 @@ const POLARIS_DASHBOARD_HTML: &str = r##"<!DOCTYPE html>
<body>
<div class="shell">
<header>
<h1>Polaris Dashboard</h1>
<h1>MergeGate Dashboard</h1>
<p class="subtitle">Deterministic Task Protocol live view</p>
<p class="meta" id="meta">Loading...</p>
</header>
@ -2574,7 +2819,7 @@ fn serve_dashboard(store_path: &std::path::Path, port: u16) -> anyhow::Result<()
store_path.to_path_buf(),
);
let listener = TcpListener::bind(("127.0.0.1", port))?;
println!("Polaris Dashboard listening on http://127.0.0.1:{port}");
println!("MergeGate Dashboard listening on http://127.0.0.1:{port}");
for stream in listener.incoming() {
match stream {
@ -2706,7 +2951,7 @@ fn bus_enqueue(task_id: &str, title: &str, store_path: &std::path::Path) {
let entry = serde_json::json!({
"ts": chrono::Utc::now().to_rfc3339(),
"agent": std::env::var("POLARIS_AGENT_ID").unwrap_or_else(|_| "system".into()),
"skill": "polaris-ops",
"skill": "mergegate-ops",
"task": format!("register: {title} ({task_id})"),
"result": "queued",
"score": 0.0,
@ -2718,7 +2963,11 @@ fn bus_enqueue(task_id: &str, title: &str, store_path: &std::path::Path) {
.open(&path)
{
use std::io::Write;
let _ = writeln!(file, "{}", serde_json::to_string(&entry).unwrap_or_default());
let _ = writeln!(
file,
"{}",
serde_json::to_string(&entry).unwrap_or_default()
);
}
}
}

View file

@ -1,5 +1,5 @@
[package]
name = "miyabi-core"
name = "mergegate-core"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
@ -7,7 +7,7 @@ authors.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
description = "Miyabi Core - Shared types and utilities"
description = "MergeGate Core - Shared types and utilities"
[dependencies]
tokio = { workspace = true }

View file

@ -154,7 +154,7 @@ where
/// Append a "Common Rejection Patterns" section to SKILL.md if gate rejections are detected.
fn update_skill_md_from_patterns(report: &DreamReport, repo_root: &Path) -> Result<()> {
let skill_path = repo_root.join("skills/polaris-ops/SKILL.md");
let skill_path = repo_root.join("skills/mergegate-ops/SKILL.md");
if !skill_path.exists() {
return Ok(());
}
@ -672,10 +672,10 @@ mod tests {
#[test]
fn update_skill_md_appends_when_no_marker() {
let tmp = TempDir::new().unwrap();
let skills_dir = tmp.path().join("skills/polaris-ops");
let skills_dir = tmp.path().join("skills/mergegate-ops");
fs::create_dir_all(&skills_dir).unwrap();
let skill_path = skills_dir.join("SKILL.md");
fs::write(&skill_path, "# Polaris Ops\n\nExisting content.\n").unwrap();
fs::write(&skill_path, "# MergeGate Ops\n\nExisting content.\n").unwrap();
let mut rejections = HashMap::new();
rejections.insert("GATE 3".to_string(), 2);
@ -690,7 +690,7 @@ mod tests {
update_skill_md_from_patterns(&report, tmp.path()).unwrap();
let content = fs::read_to_string(&skill_path).unwrap();
assert!(content.contains("# Polaris Ops"));
assert!(content.contains("# MergeGate Ops"));
assert!(content.contains("Existing content."));
assert!(content.contains("## よくある拒否パターン(自動生成)"));
assert!(content.contains("GATE 3"));
@ -699,12 +699,12 @@ mod tests {
#[test]
fn update_skill_md_replaces_marker_preserves_following_sections() {
let tmp = TempDir::new().unwrap();
let skills_dir = tmp.path().join("skills/polaris-ops");
let skills_dir = tmp.path().join("skills/mergegate-ops");
fs::create_dir_all(&skills_dir).unwrap();
let skill_path = skills_dir.join("SKILL.md");
fs::write(
&skill_path,
"# Polaris Ops\n\n\
"# MergeGate Ops\n\n\
## \n\n\
| GATE | | |\n|------|------|--------|\n| old | 1 | old |\n\n\
## \n\n- important link\n",

View file

@ -475,13 +475,13 @@ mod tests {
.unwrap();
for file in &files {
let conflict = manager.has_conflict(&[file.clone()]).unwrap();
let conflict = manager.has_conflict(std::slice::from_ref(file)).unwrap();
assert!(conflict.conflicting);
}
manager.release_lock("task-a").unwrap();
for file in &files {
let conflict = manager.has_conflict(&[file.clone()]).unwrap();
let conflict = manager.has_conflict(std::slice::from_ref(file)).unwrap();
assert!(!conflict.conflicting);
}
}
@ -550,11 +550,11 @@ mod proptest_tests {
) {
let (_tmp, ss, mgr) = setup();
seed(&ss, "task-a");
mgr.acquire_lock("task-a", "agent", "node", &[file.clone()]).unwrap();
let c = mgr.has_conflict(&[file.clone()]).unwrap();
mgr.acquire_lock("task-a", "agent", "node", std::slice::from_ref(&file)).unwrap();
let c = mgr.has_conflict(std::slice::from_ref(&file)).unwrap();
prop_assert!(c.conflicting);
mgr.release_lock("task-a").unwrap();
let c = mgr.has_conflict(&[file]).unwrap();
let c = mgr.has_conflict(std::slice::from_ref(&file)).unwrap();
prop_assert!(!c.conflicting);
}
@ -565,8 +565,8 @@ mod proptest_tests {
let (_tmp, ss, mgr) = setup();
seed(&ss, "task-a");
seed(&ss, "task-b");
mgr.acquire_lock("task-a", "agent", "node", &[file.clone()]).unwrap();
let result = mgr.acquire_lock("task-b", "agent", "node", &[file]);
mgr.acquire_lock("task-a", "agent", "node", std::slice::from_ref(&file)).unwrap();
let result = mgr.acquire_lock("task-b", "agent", "node", std::slice::from_ref(&file));
prop_assert!(result.is_err());
}

View file

@ -626,7 +626,7 @@ impl DeterministicExecutionProtocol {
pub fn dispatchable(&self) -> ProtocolResult<DispatchableReport> {
let snapshot = self.snapshot_store.load().map_err(ProtocolError::from)?;
let tasks = snapshot
let tasks: Vec<ExecutionTask> = snapshot
.tasks
.iter()
.filter(|task| matches!(task.current_state, TaskState::Pending | TaskState::Blocked))
@ -645,7 +645,12 @@ impl DeterministicExecutionProtocol {
})
.cloned()
.collect();
Ok(DispatchableReport { tasks })
let task_ids = tasks.iter().map(|task| task.id.clone()).collect();
Ok(DispatchableReport {
count: tasks.len(),
task_ids,
tasks,
})
}
fn attach_context_with_limit(
@ -1087,6 +1092,8 @@ pub struct DagReport {
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DispatchableReport {
pub count: usize,
pub task_ids: Vec<String>,
pub tasks: Vec<ExecutionTask>,
}
@ -1789,6 +1796,8 @@ mod tests {
}
let dispatchable = protocol.dispatchable().unwrap();
assert_eq!(dispatchable.count, 1);
assert_eq!(dispatchable.task_ids, vec!["phase-a".to_string()]);
assert_eq!(dispatchable.tasks.len(), 1);
assert_eq!(dispatchable.tasks[0].id, "phase-a");
}
@ -2777,7 +2786,7 @@ mod tests {
assert!(extract_wikilinks("no links here").is_empty());
assert!(extract_wikilinks("[[]]").is_empty()); // empty name
assert!(extract_wikilinks("[[ ]]").is_empty()); // whitespace only
// Unclosed link: should still extract the name
// Unclosed link: should still extract the name
assert_eq!(extract_wikilinks("[[unclosed"), vec!["unclosed"]);
}

View file

@ -1002,10 +1002,16 @@ mod tests {
assert_eq!(task.current_state, TaskState::Implementing);
assert!(task.lock.is_some());
assert!(task.impact.is_some());
assert_eq!(task.impact.as_ref().unwrap().risk_level, ImpactRiskLevel::High);
assert_eq!(
task.impact.as_ref().unwrap().risk_level,
ImpactRiskLevel::High
);
assert!(task.github_evidence.is_some());
assert_eq!(task.github_evidence.as_ref().unwrap().pr_number, 42);
assert_eq!(task.github_evidence.as_ref().unwrap().pr_state, GitHubPrState::Open);
assert_eq!(
task.github_evidence.as_ref().unwrap().pr_state,
GitHubPrState::Open
);
// file_locks should be populated from legacy lock
assert!(snapshot.file_locks.contains_key("src/main.rs"));
}
@ -1083,7 +1089,7 @@ mod proptest_tests {
proptest! {
#[test]
fn cas_rejects_stale_version(version in 1u64..100) {
fn cas_rejects_stale_version(_version in 1u64..100) {
let tmp = TempDir::new().unwrap();
let store = SnapshotStore::new(
tmp.path().join("snap.json"),
@ -1101,7 +1107,7 @@ mod proptest_tests {
#[test]
fn upsert_is_idempotent(n in 1usize..10) {
let tmp = TempDir::new().unwrap();
let store = SnapshotStore::new(
let _store = SnapshotStore::new(
tmp.path().join("snap.json"),
tmp.path().join(".lock"),
);

View file

@ -1,5 +1,5 @@
[package]
name = "miyabi-tui"
name = "mergegate-tui"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
@ -7,7 +7,7 @@ authors.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
description = "Miyabi TUI - Terminal User Interface following Codex patterns"
description = "MergeGate TUI - Terminal interface for MergeGate and agent-assisted development"
[[bin]]
name = "miyabi-tui"
@ -19,7 +19,7 @@ path = "src/lib.rs"
[dependencies]
# Workspace dependencies
miyabi-core = { path = "../miyabi-core" }
miyabi-core = { package = "mergegate-core", path = "../mergegate-core" }
arboard = { version = "3", optional = true }
# TUI Framework

View file

@ -101,7 +101,7 @@ impl App {
});
let welcome_message = if client.is_some() {
"Welcome to Miyabi CLI! Type your message and press Enter. Press Ctrl+P for commands, F1 for help."
"Welcome to MergeGate. Type your message and press Enter. Press Ctrl+P for commands, F1 for help."
} else {
"⚠ ANTHROPIC_API_KEY not set. Please set it in config or environment to use Claude API."
};

View file

@ -1,4 +1,4 @@
# Getting Started with miyabi-cli-standalone
# Getting Started with MergeGate
Miyabiプロジェクトへようこそこのガイドでは、ゼロからMiyabiを使い始めるまでの手順を詳しく解説します。
@ -66,18 +66,18 @@ echo $ANTHROPIC_API_KEY
#### 2.1 GitHub CLI使用推奨
```bash
cd miyabi-cli-standalone
gh repo create miyabi-cli-standalone --private --source=. --remote=origin
cd mergegate
gh repo create mergegate --private --source=. --remote=origin
```
#### 2.2 手動作成
1. https://github.com/new にアクセス
2. Repository nameに `miyabi-cli-standalone` を入力
2. Repository nameに `mergegate` を入力
3. "Private"を選択
4. "Create repository"をクリック
5. ローカルリポジトリと接続:
```bash
git remote add origin https://github.com/YOUR_USERNAME/miyabi-cli-standalone.git
git remote add origin https://github.com/YOUR_USERNAME/mergegate.git
git branch -M main
git add .
git commit -m "feat: initial commit 🚀"

View file

@ -1,6 +1,6 @@
# Operation Plan - miyabi-cli-standalone
# Operation Plan - MergeGate
**Project**: miyabi-cli-standalone
**Project**: mergegate
**Created**: 2025-11-22
**Execution Method**: Miyabi Autonomous Agents
@ -49,11 +49,11 @@ miyabi agent run coordinator --issue 24 # Tool Trait
miyabi status
# Issue一覧
gh issue list --repo ShunsukeHayashi/miyabi-cli-standalone \
gh issue list --repo ShunsukeHayashi/mergegate \
--label "🏗️ state:implementing"
# PR確認
gh pr list --repo ShunsukeHayashi/miyabi-cli-standalone
gh pr list --repo ShunsukeHayashi/mergegate
```
---
@ -64,7 +64,7 @@ gh pr list --repo ShunsukeHayashi/miyabi-cli-standalone
```bash
# 1. 前日のPRをレビュー
gh pr list --repo ShunsukeHayashi/miyabi-cli-standalone --state open
gh pr list --repo ShunsukeHayashi/mergegate --state open
# 2. ブロッカー確認
gh issue list --label "🚫 state:blocked"
@ -124,7 +124,7 @@ gh issue list --label "📥 state:pending" --milestone "v0.2.0 - Advanced Text R
miyabi status
# GitHub Issues
gh issue list --repo ShunsukeHayashi/miyabi-cli-standalone
gh issue list --repo ShunsukeHayashi/mergegate
# ラベル別
gh issue list --label "📊 priority:P0-Critical"
@ -233,7 +233,7 @@ cargo clippy --all-targets
```bash
# マイルストーン一覧
gh api repos/ShunsukeHayashi/miyabi-cli-standalone/milestones
gh api repos/ShunsukeHayashi/mergegate/milestones
# 特定マイルストーンの進捗
gh issue list --milestone "v0.2.0 - Advanced Text Rendering"
@ -328,7 +328,7 @@ gh issue list --label "🚫 state:blocked"
```bash
# 今すぐ開始
cd /Users/shunsuke/Dev/miyabi-cli-standalone
cd /Users/shunsuke/Dev/mergegate
# Critical Path開始
miyabi agent run coordinator --issue 19 # API Client (最重要)

View file

@ -1,4 +1,4 @@
# Preparation Operations - miyabi-cli-standalone
# Preparation Operations - MergeGate
**Purpose**: Sprint 1開始前の準備作業チェックリスト
**Target Date**: 2025-11-25 (Sprint 1 Start)
@ -71,7 +71,7 @@
- [ ] **必要なラベル確認**
```bash
gh label list --repo ShunsukeHayashi/miyabi-cli-standalone | wc -l
gh label list --repo ShunsukeHayashi/mergegate | wc -l
# Expected: 45+ labels
```
@ -85,7 +85,7 @@
- [ ] **マイルストーン確認**
```bash
gh api repos/ShunsukeHayashi/miyabi-cli-standalone/milestones | jq '.[].title'
gh api repos/ShunsukeHayashi/mergegate/milestones | jq '.[].title'
```
Expected:
@ -98,7 +98,7 @@
- [ ] **Issue一覧確認**
```bash
gh issue list --repo ShunsukeHayashi/miyabi-cli-standalone --state all | wc -l
gh issue list --repo ShunsukeHayashi/mergegate --state all | wc -l
# Expected: 28 issues
```
@ -266,7 +266,7 @@
- [ ] **Pulse確認**
```
https://github.com/ShunsukeHayashi/miyabi-cli-standalone/pulse
https://github.com/ShunsukeHayashi/mergegate/pulse
```
---
@ -277,7 +277,7 @@
- [ ] **Issue #10 (MarkdownStream core)**
```bash
gh issue view 10 --repo ShunsukeHayashi/miyabi-cli-standalone
gh issue view 10 --repo ShunsukeHayashi/mergegate
```
- [ ] **Issue #15 (DiffRender core)**
@ -316,7 +316,7 @@
```bash
# 1. 環境確認
cd /Users/shunsuke/Dev/miyabi-cli-standalone
cd /Users/shunsuke/Dev/mergegate
miyabi status
# 2. Critical Path 開始

View file

@ -1,4 +1,4 @@
# Product Specification - miyabi-cli-standalone
# Product Specification - MergeGate
**Version**: 1.0.0
**Status**: Draft

View file

@ -1,6 +1,6 @@
# Sprint Planning - miyabi-cli-standalone
# Sprint Planning - MergeGate
**Project**: miyabi-cli-standalone
**Project**: mergegate
**Total Sprints**: 4
**Sprint Duration**: 5 days (1 week)
**Start Date**: 2025-11-25
@ -389,7 +389,7 @@ gh issue list --label "🏗️ state:implementing"
### Sprint 1 Quick Start
```bash
cd /Users/shunsuke/Dev/miyabi-cli-standalone
cd /Users/shunsuke/Dev/mergegate
# Critical Path開始
miyabi agent run coordinator --issue 19 # API Client

View file

@ -1,4 +1,4 @@
# Miyabi CLI ユーザーマニュアル
# MergeGate ユーザーマニュアル
**Version**: 0.1.0
**Last Updated**: 2025-11-23
@ -22,7 +22,14 @@
## はじめに
Miyabi CLIは、ターミナルで動作するAIアシスタントです。Claude APIを使用して、対話型のチャットや自律的なタスク実行が可能です。
MergeGate は、AI-assisted development 向けの deterministic task execution and merge workflow です。CLI では `miyabi``mergegate` の両方を使え、対話型の TUI と `gate` ベースの repo workflow を提供します。
設計思想はシンプルです。
- `GitNexus`: コードベースを理解する
- `MergeGate`: 変更を安全に実行する
影響範囲が分かるだけでは、AI エージェントは安全に開発できません。どの task を登録し、どのファイルを lock し、どの順序で branch / PR / merge まで進めるかを protocol として固定するのが MergeGate の役割です。
### 主な機能
@ -44,21 +51,21 @@ Miyabi CLIは、ターミナルで動作するAIアシスタントです。Claud
```bash
# 1. リポジトリをクローン
git clone https://github.com/ShunsukeHayashi/miyabi-cli-standalone.git
cd miyabi-cli-standalone
git clone https://github.com/ShunsukeHayashi/mergegate.git
cd mergegate
# 2. リリースビルド
cargo build --release
# 3. バイナリの確認
ls -la target/release/miyabi
ls -la target/release/miyabi target/release/mergegate
```
### パスを通す(オプション)
```bash
# ~/.bashrc または ~/.zshrc に追加
export PATH="$PATH:/path/to/miyabi-cli-standalone/target/release"
export PATH="$PATH:/path/to/mergegate/target/release"
# 設定を反映
source ~/.bashrc # または source ~/.zshrc
@ -528,7 +535,7 @@ max_retries = 5
### 問題報告
GitHub Issues: https://github.com/ShunsukeHayashi/miyabi-cli-standalone/issues
GitHub Issues: https://github.com/ShunsukeHayashi/mergegate/issues
### TUIでのヘルプ

View file

@ -1,6 +1,6 @@
# Work Breakdown Structure (WBS)
**Project**: miyabi-cli-standalone
**Project**: mergegate
**Version**: v1.0.0
**Created**: 2025-11-22

View file

@ -1,6 +1,6 @@
# 改善サイクル — シータサイクル分析に基づく詳細プラン
_Polaris タスク: issue-86 | 2026-04-10_
_MergeGate タスク: issue-86 | 2026-04-10_
---

View file

@ -13,13 +13,13 @@
## リポジトリ
- **Miyabi-G-K/miyabi-cli-standalone**: Private
- **ShunsukeHayashi/mergegate**: Private
- **Miyabi-G-K/deterministic-task-protocol**: Private
- 公開しない。npm にも公開しない。
## 保護対象
1. **Polaris (DTP) アーキテクチャ**
1. **MergeGate (DTP) アーキテクチャ**
- GATE チェーンによる確定的状態遷移
- ファイルロック + DAG + ステートマシンの三位一体
- ワークツリー不要の論理的並列分離
@ -29,7 +29,7 @@
- ドリーミングevent log → 学び昇格)
- シータサイクル統合
3. **miyabi gate CLI**
3. **MergeGate CLI**
- 11+ サブコマンドの実装
- pre-commit / post-commit hook 統合
- Bus ドッキングブリッジ

Some files were not shown because too many files have changed in this diff Show more