bingo-light 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +522 -0
- package/README.zh-CN.md +534 -0
- package/bin/cli.js +46 -0
- package/bin/mcp.js +45 -0
- package/bingo-light +1094 -0
- package/bingo_core/__init__.py +77 -0
- package/bingo_core/__pycache__/__init__.cpython-313.pyc +0 -0
- package/bingo_core/__pycache__/_entry.cpython-313.pyc +0 -0
- package/bingo_core/__pycache__/config.cpython-313.pyc +0 -0
- package/bingo_core/__pycache__/exceptions.cpython-313.pyc +0 -0
- package/bingo_core/__pycache__/git.cpython-313.pyc +0 -0
- package/bingo_core/__pycache__/models.cpython-313.pyc +0 -0
- package/bingo_core/__pycache__/repo.cpython-313.pyc +0 -0
- package/bingo_core/__pycache__/setup.cpython-313.pyc +0 -0
- package/bingo_core/__pycache__/state.cpython-313.pyc +0 -0
- package/bingo_core/config.py +110 -0
- package/bingo_core/exceptions.py +48 -0
- package/bingo_core/git.py +194 -0
- package/bingo_core/models.py +37 -0
- package/bingo_core/repo.py +2376 -0
- package/bingo_core/setup.py +549 -0
- package/bingo_core/state.py +306 -0
- package/completions/bingo-light.bash +118 -0
- package/completions/bingo-light.fish +197 -0
- package/completions/bingo-light.zsh +169 -0
- package/mcp-server.py +788 -0
- package/package.json +34 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
"""
|
|
2
|
+
bingo_core.state — State management (.bingo/ directory) for bingo-light.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import subprocess
|
|
10
|
+
import tempfile
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
from typing import List, Optional, Tuple
|
|
13
|
+
|
|
14
|
+
from bingo_core import (
|
|
15
|
+
BINGO_DIR,
|
|
16
|
+
CIRCUIT_BREAKER_LIMIT,
|
|
17
|
+
SYNC_HISTORY_MAX,
|
|
18
|
+
)
|
|
19
|
+
from bingo_core.exceptions import BingoError
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class State:
|
|
23
|
+
"""Manages .bingo/ directory state: undo, circuit breaker, metadata, etc."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, repo_dir: str):
|
|
26
|
+
self.repo_dir = repo_dir
|
|
27
|
+
self.bingo_dir = os.path.join(repo_dir, BINGO_DIR)
|
|
28
|
+
self.metadata_file = os.path.join(self.bingo_dir, "metadata.json")
|
|
29
|
+
self.sync_history_file = os.path.join(self.bingo_dir, "sync-history.json")
|
|
30
|
+
self.session_file = os.path.join(self.bingo_dir, "session.md")
|
|
31
|
+
|
|
32
|
+
def _ensure_dir(self) -> None:
|
|
33
|
+
"""Create .bingo/ directory if it doesn't exist."""
|
|
34
|
+
os.makedirs(self.bingo_dir, exist_ok=True)
|
|
35
|
+
|
|
36
|
+
def acquire_lock(self) -> None:
|
|
37
|
+
"""Acquire an exclusive operation lock. Raises BingoError if locked."""
|
|
38
|
+
self._ensure_dir()
|
|
39
|
+
lock_path = os.path.join(self.bingo_dir, ".lock")
|
|
40
|
+
if os.path.isfile(lock_path):
|
|
41
|
+
try:
|
|
42
|
+
with open(lock_path) as f:
|
|
43
|
+
pid = int(f.read().strip())
|
|
44
|
+
# Check if the locking process is still running
|
|
45
|
+
os.kill(pid, 0)
|
|
46
|
+
raise BingoError(
|
|
47
|
+
f"Another bingo-light operation is in progress (pid {pid}). "
|
|
48
|
+
"If this is stale, remove .bingo/.lock"
|
|
49
|
+
)
|
|
50
|
+
except (ValueError, OSError):
|
|
51
|
+
pass # Stale lock — process is gone
|
|
52
|
+
with open(lock_path, "w") as f:
|
|
53
|
+
f.write(str(os.getpid()))
|
|
54
|
+
|
|
55
|
+
def release_lock(self) -> None:
|
|
56
|
+
"""Release the operation lock."""
|
|
57
|
+
lock_path = os.path.join(self.bingo_dir, ".lock")
|
|
58
|
+
try:
|
|
59
|
+
os.unlink(lock_path)
|
|
60
|
+
except OSError:
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
# -- Undo --
|
|
64
|
+
|
|
65
|
+
def save_undo(self, head: str, tracking: str) -> None:
|
|
66
|
+
"""Save undo state for rollback."""
|
|
67
|
+
self._ensure_dir()
|
|
68
|
+
self._write(os.path.join(self.bingo_dir, ".undo-head"), head)
|
|
69
|
+
self._write(os.path.join(self.bingo_dir, ".undo-tracking"), tracking)
|
|
70
|
+
# Clear undo marker -- new sync starts a new cycle
|
|
71
|
+
self._remove(os.path.join(self.bingo_dir, ".undo-active"))
|
|
72
|
+
|
|
73
|
+
def load_undo(self) -> Tuple[Optional[str], Optional[str]]:
|
|
74
|
+
"""Load saved undo state. Returns (head, tracking) or (None, None)."""
|
|
75
|
+
head = self._read(os.path.join(self.bingo_dir, ".undo-head"))
|
|
76
|
+
tracking = self._read(os.path.join(self.bingo_dir, ".undo-tracking"))
|
|
77
|
+
return head, tracking
|
|
78
|
+
|
|
79
|
+
def mark_undo_active(self) -> None:
|
|
80
|
+
"""Mark that undo was used -- prevents _fix_stale_tracking from auto-advancing."""
|
|
81
|
+
self._ensure_dir()
|
|
82
|
+
self._write(os.path.join(self.bingo_dir, ".undo-active"), "")
|
|
83
|
+
|
|
84
|
+
def is_undo_active(self) -> bool:
|
|
85
|
+
"""Check if undo marker is set."""
|
|
86
|
+
return os.path.isfile(os.path.join(self.bingo_dir, ".undo-active"))
|
|
87
|
+
|
|
88
|
+
def clear_undo_tracking(self) -> None:
|
|
89
|
+
"""Remove the undo tracking file after restoring."""
|
|
90
|
+
self._remove(os.path.join(self.bingo_dir, ".undo-tracking"))
|
|
91
|
+
|
|
92
|
+
# -- Circuit Breaker --
|
|
93
|
+
|
|
94
|
+
def check_circuit_breaker(self, upstream_target: str) -> bool:
|
|
95
|
+
"""Check if circuit breaker is tripped (3+ failures on same commit).
|
|
96
|
+
|
|
97
|
+
Returns True if we should STOP (breaker is tripped).
|
|
98
|
+
"""
|
|
99
|
+
path = os.path.join(self.bingo_dir, ".sync-failures")
|
|
100
|
+
if not os.path.isfile(path):
|
|
101
|
+
return False
|
|
102
|
+
try:
|
|
103
|
+
content = self._read(path)
|
|
104
|
+
if content is None:
|
|
105
|
+
return False
|
|
106
|
+
lines = content.strip().split("\n")
|
|
107
|
+
if len(lines) < 2:
|
|
108
|
+
return False
|
|
109
|
+
target = lines[0]
|
|
110
|
+
count = int(lines[1])
|
|
111
|
+
return target == upstream_target and count >= CIRCUIT_BREAKER_LIMIT
|
|
112
|
+
except (ValueError, IndexError):
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
def record_circuit_breaker(self, upstream_target: str) -> None:
|
|
116
|
+
"""Record a sync failure for circuit breaker."""
|
|
117
|
+
self._ensure_dir()
|
|
118
|
+
path = os.path.join(self.bingo_dir, ".sync-failures")
|
|
119
|
+
prev_count = 0
|
|
120
|
+
if os.path.isfile(path):
|
|
121
|
+
try:
|
|
122
|
+
content = self._read(path)
|
|
123
|
+
if content:
|
|
124
|
+
lines = content.strip().split("\n")
|
|
125
|
+
if len(lines) >= 2 and lines[0] == upstream_target:
|
|
126
|
+
prev_count = int(lines[1])
|
|
127
|
+
except (ValueError, IndexError):
|
|
128
|
+
pass
|
|
129
|
+
self._write(path, f"{upstream_target}\n{prev_count + 1}")
|
|
130
|
+
|
|
131
|
+
def clear_circuit_breaker(self) -> None:
|
|
132
|
+
"""Reset circuit breaker on success."""
|
|
133
|
+
self._remove(os.path.join(self.bingo_dir, ".sync-failures"))
|
|
134
|
+
|
|
135
|
+
# -- Metadata --
|
|
136
|
+
|
|
137
|
+
def _load_metadata(self) -> dict:
|
|
138
|
+
"""Load metadata.json, creating it if needed."""
|
|
139
|
+
self._ensure_dir()
|
|
140
|
+
if not os.path.isfile(self.metadata_file):
|
|
141
|
+
return {"patches": {}}
|
|
142
|
+
try:
|
|
143
|
+
with open(self.metadata_file) as f:
|
|
144
|
+
return json.load(f)
|
|
145
|
+
except (json.JSONDecodeError, IOError):
|
|
146
|
+
return {"patches": {}}
|
|
147
|
+
|
|
148
|
+
def _save_metadata(self, data: dict) -> None:
|
|
149
|
+
"""Atomically write metadata.json."""
|
|
150
|
+
self._ensure_dir()
|
|
151
|
+
self._write_json(self.metadata_file, data)
|
|
152
|
+
|
|
153
|
+
def patch_meta_get(self, patch_name: str) -> dict:
|
|
154
|
+
"""Get metadata for a patch."""
|
|
155
|
+
data = self._load_metadata()
|
|
156
|
+
return data.get("patches", {}).get(
|
|
157
|
+
patch_name,
|
|
158
|
+
{
|
|
159
|
+
"reason": "",
|
|
160
|
+
"tags": [],
|
|
161
|
+
"expires": None,
|
|
162
|
+
"upstream_pr": "",
|
|
163
|
+
"status": "permanent",
|
|
164
|
+
},
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
def patch_meta_set(self, patch_name: str, key: str, value: str) -> None:
|
|
168
|
+
"""Set a metadata field for a patch."""
|
|
169
|
+
data = self._load_metadata()
|
|
170
|
+
patches = data.setdefault("patches", {})
|
|
171
|
+
p = patches.setdefault(
|
|
172
|
+
patch_name,
|
|
173
|
+
{
|
|
174
|
+
"reason": "",
|
|
175
|
+
"tags": [],
|
|
176
|
+
"expires": None,
|
|
177
|
+
"upstream_pr": "",
|
|
178
|
+
"status": "permanent",
|
|
179
|
+
"created": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
180
|
+
},
|
|
181
|
+
)
|
|
182
|
+
if key in ("tag", "tags"):
|
|
183
|
+
tags_list = p.setdefault("tags", [])
|
|
184
|
+
for t in (v.strip() for v in value.split(",")):
|
|
185
|
+
if t and t not in tags_list:
|
|
186
|
+
tags_list.append(t)
|
|
187
|
+
elif key in ("reason", "expires", "upstream_pr", "status"):
|
|
188
|
+
p[key] = value
|
|
189
|
+
self._save_metadata(data)
|
|
190
|
+
|
|
191
|
+
# -- Sync History --
|
|
192
|
+
|
|
193
|
+
def record_sync(
|
|
194
|
+
self,
|
|
195
|
+
behind: int,
|
|
196
|
+
upstream_before: str,
|
|
197
|
+
upstream_after: str,
|
|
198
|
+
patches: List[dict],
|
|
199
|
+
) -> None:
|
|
200
|
+
"""Record a sync event."""
|
|
201
|
+
self._ensure_dir()
|
|
202
|
+
data: dict = {"syncs": []}
|
|
203
|
+
if os.path.isfile(self.sync_history_file):
|
|
204
|
+
try:
|
|
205
|
+
with open(self.sync_history_file) as f:
|
|
206
|
+
data = json.load(f)
|
|
207
|
+
except (json.JSONDecodeError, IOError):
|
|
208
|
+
data = {"syncs": []}
|
|
209
|
+
|
|
210
|
+
data["syncs"].append(
|
|
211
|
+
{
|
|
212
|
+
"timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
213
|
+
"upstream_before": upstream_before,
|
|
214
|
+
"upstream_after": upstream_after,
|
|
215
|
+
"upstream_commits_integrated": behind,
|
|
216
|
+
"patches": patches,
|
|
217
|
+
}
|
|
218
|
+
)
|
|
219
|
+
# Keep only the last N entries
|
|
220
|
+
data["syncs"] = data["syncs"][-SYNC_HISTORY_MAX:]
|
|
221
|
+
self._write_json(self.sync_history_file, data)
|
|
222
|
+
|
|
223
|
+
def get_sync_history(self) -> dict:
|
|
224
|
+
"""Get sync history."""
|
|
225
|
+
if not os.path.isfile(self.sync_history_file):
|
|
226
|
+
return {"syncs": []}
|
|
227
|
+
try:
|
|
228
|
+
with open(self.sync_history_file) as f:
|
|
229
|
+
return json.load(f)
|
|
230
|
+
except (json.JSONDecodeError, IOError):
|
|
231
|
+
return {"syncs": []}
|
|
232
|
+
|
|
233
|
+
# -- Session --
|
|
234
|
+
|
|
235
|
+
def update_session(self, content: str) -> None:
|
|
236
|
+
"""Write session notes."""
|
|
237
|
+
self._ensure_dir()
|
|
238
|
+
self._write(self.session_file, content)
|
|
239
|
+
|
|
240
|
+
def get_session(self) -> Optional[str]:
|
|
241
|
+
"""Read session notes."""
|
|
242
|
+
return self._read(self.session_file)
|
|
243
|
+
|
|
244
|
+
# -- Hooks --
|
|
245
|
+
|
|
246
|
+
def run_hook(self, event: str, data: Optional[dict] = None) -> None:
|
|
247
|
+
"""Run a hook script if it exists: .bingo/hooks/<event>."""
|
|
248
|
+
hook_path = os.path.join(self.bingo_dir, "hooks", event)
|
|
249
|
+
if os.path.isfile(hook_path) and os.access(hook_path, os.X_OK):
|
|
250
|
+
try:
|
|
251
|
+
json_data = json.dumps(data or {})
|
|
252
|
+
result = subprocess.run(
|
|
253
|
+
[hook_path],
|
|
254
|
+
cwd=self.repo_dir,
|
|
255
|
+
input=json_data,
|
|
256
|
+
capture_output=True,
|
|
257
|
+
text=True,
|
|
258
|
+
timeout=30,
|
|
259
|
+
)
|
|
260
|
+
if result.returncode != 0:
|
|
261
|
+
import sys
|
|
262
|
+
print(
|
|
263
|
+
f"warning: hook '{event}' exited {result.returncode}",
|
|
264
|
+
file=sys.stderr,
|
|
265
|
+
)
|
|
266
|
+
except subprocess.TimeoutExpired:
|
|
267
|
+
import sys
|
|
268
|
+
print(f"warning: hook '{event}' timed out", file=sys.stderr)
|
|
269
|
+
except OSError:
|
|
270
|
+
pass # Hook not executable or missing interpreter
|
|
271
|
+
|
|
272
|
+
# -- Internal helpers --
|
|
273
|
+
|
|
274
|
+
def _write(self, path: str, content: str) -> None:
|
|
275
|
+
with open(path, "w") as f:
|
|
276
|
+
f.write(content)
|
|
277
|
+
|
|
278
|
+
def _read(self, path: str) -> Optional[str]:
|
|
279
|
+
if not os.path.isfile(path):
|
|
280
|
+
return None
|
|
281
|
+
try:
|
|
282
|
+
with open(path) as f:
|
|
283
|
+
return f.read().strip()
|
|
284
|
+
except IOError:
|
|
285
|
+
return None
|
|
286
|
+
|
|
287
|
+
def _remove(self, path: str) -> None:
|
|
288
|
+
try:
|
|
289
|
+
os.remove(path)
|
|
290
|
+
except FileNotFoundError:
|
|
291
|
+
pass
|
|
292
|
+
|
|
293
|
+
def _write_json(self, path: str, data: dict) -> None:
|
|
294
|
+
"""Atomically write JSON file using temp file + rename."""
|
|
295
|
+
dir_name = os.path.dirname(path) or "."
|
|
296
|
+
fd, tmp_path = tempfile.mkstemp(suffix=".tmp", dir=dir_name)
|
|
297
|
+
try:
|
|
298
|
+
with os.fdopen(fd, "w") as f:
|
|
299
|
+
json.dump(data, f, indent=2)
|
|
300
|
+
os.replace(tmp_path, path)
|
|
301
|
+
except Exception:
|
|
302
|
+
try:
|
|
303
|
+
os.unlink(tmp_path)
|
|
304
|
+
except FileNotFoundError:
|
|
305
|
+
pass
|
|
306
|
+
raise
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Bash completion for bingo-light
|
|
2
|
+
# Source this file or place it in /etc/bash_completion.d/
|
|
3
|
+
#
|
|
4
|
+
# Usage:
|
|
5
|
+
# source bingo-light.bash
|
|
6
|
+
# # or
|
|
7
|
+
# cp bingo-light.bash /etc/bash_completion.d/bingo-light
|
|
8
|
+
|
|
9
|
+
_bingo_light() {
|
|
10
|
+
local cur prev words cword
|
|
11
|
+
_init_completion || return
|
|
12
|
+
|
|
13
|
+
local -r toplevel_commands="init setup patch sync status doctor auto-sync log undo diff version help conflict-analyze conflict-resolve config history test workspace smart-sync session"
|
|
14
|
+
local -r toplevel_aliases="p s st d ws"
|
|
15
|
+
local -r all_toplevel="${toplevel_commands} ${toplevel_aliases}"
|
|
16
|
+
|
|
17
|
+
local -r patch_subcommands="new list show edit drop export import reorder squash meta"
|
|
18
|
+
local -r patch_aliases="ls add create rm remove"
|
|
19
|
+
local -r all_patch="${patch_subcommands} ${patch_aliases}"
|
|
20
|
+
|
|
21
|
+
# Walk backward through the command line to find the active command context.
|
|
22
|
+
local cmd=""
|
|
23
|
+
local subcmd=""
|
|
24
|
+
local i
|
|
25
|
+
for (( i=1; i < cword; i++ )); do
|
|
26
|
+
case "${words[i]}" in
|
|
27
|
+
patch|p)
|
|
28
|
+
cmd="patch"
|
|
29
|
+
;;
|
|
30
|
+
sync|s)
|
|
31
|
+
cmd="sync"
|
|
32
|
+
;;
|
|
33
|
+
status|st)
|
|
34
|
+
cmd="status"
|
|
35
|
+
;;
|
|
36
|
+
diff|d)
|
|
37
|
+
cmd="diff"
|
|
38
|
+
;;
|
|
39
|
+
init|setup|doctor|auto-sync|log|undo|version|help|conflict-analyze|conflict-resolve|config|history|test|workspace|ws|smart-sync|session)
|
|
40
|
+
cmd="${words[i]}"
|
|
41
|
+
;;
|
|
42
|
+
*)
|
|
43
|
+
# If we already have a command, this may be a subcommand.
|
|
44
|
+
if [[ -n "$cmd" && -z "$subcmd" ]]; then
|
|
45
|
+
subcmd="${words[i]}"
|
|
46
|
+
fi
|
|
47
|
+
;;
|
|
48
|
+
esac
|
|
49
|
+
done
|
|
50
|
+
|
|
51
|
+
# ---- Completions based on context ----
|
|
52
|
+
|
|
53
|
+
# Inside "patch" (or alias "p") -- complete subcommands and flags
|
|
54
|
+
if [[ "$cmd" == "patch" ]]; then
|
|
55
|
+
# If a subcommand is already chosen, offer its flags
|
|
56
|
+
case "$subcmd" in
|
|
57
|
+
list|ls)
|
|
58
|
+
COMPREPLY=( $(compgen -W "-v --verbose --help -h" -- "$cur") )
|
|
59
|
+
return
|
|
60
|
+
;;
|
|
61
|
+
new|add|create|show|edit|drop|rm|remove|export|import|reorder)
|
|
62
|
+
COMPREPLY=( $(compgen -W "--help -h" -- "$cur") )
|
|
63
|
+
return
|
|
64
|
+
;;
|
|
65
|
+
esac
|
|
66
|
+
|
|
67
|
+
# No subcommand yet -- offer patch subcommands (including aliases)
|
|
68
|
+
if [[ "$cur" == -* ]]; then
|
|
69
|
+
COMPREPLY=( $(compgen -W "--help -h" -- "$cur") )
|
|
70
|
+
else
|
|
71
|
+
COMPREPLY=( $(compgen -W "${all_patch}" -- "$cur") )
|
|
72
|
+
fi
|
|
73
|
+
return
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
# Inside "sync" (or alias "s") -- complete flags
|
|
77
|
+
if [[ "$cmd" == "sync" ]]; then
|
|
78
|
+
COMPREPLY=( $(compgen -W "--dry-run -n --force -f --help -h" -- "$cur") )
|
|
79
|
+
return
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# Inside "status" (or alias "st")
|
|
83
|
+
if [[ "$cmd" == "status" ]]; then
|
|
84
|
+
COMPREPLY=( $(compgen -W "--help -h" -- "$cur") )
|
|
85
|
+
return
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
# Inside "diff" (or alias "d")
|
|
89
|
+
if [[ "$cmd" == "diff" ]]; then
|
|
90
|
+
COMPREPLY=( $(compgen -W "--help -h" -- "$cur") )
|
|
91
|
+
return
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# Inside "workspace" (or alias "ws") -- complete subcommands
|
|
95
|
+
if [[ "$cmd" == "workspace" || "$cmd" == "ws" ]]; then
|
|
96
|
+
if [[ -z "$subcmd" ]]; then
|
|
97
|
+
COMPREPLY=( $(compgen -W "init add remove list sync status" -- "$cur") )
|
|
98
|
+
else
|
|
99
|
+
COMPREPLY=( $(compgen -W "--help -h" -- "$cur") )
|
|
100
|
+
fi
|
|
101
|
+
return
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
# Other known commands that take no further subcommands
|
|
105
|
+
if [[ -n "$cmd" ]]; then
|
|
106
|
+
COMPREPLY=( $(compgen -W "--help -h" -- "$cur") )
|
|
107
|
+
return
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
# Top-level: no command selected yet
|
|
111
|
+
if [[ "$cur" == -* ]]; then
|
|
112
|
+
COMPREPLY=( $(compgen -W "--help -h --version --json --yes -y" -- "$cur") )
|
|
113
|
+
else
|
|
114
|
+
COMPREPLY=( $(compgen -W "${all_toplevel}" -- "$cur") )
|
|
115
|
+
fi
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
complete -F _bingo_light bingo-light
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# Fish completion for bingo-light
|
|
2
|
+
# Place in ~/.config/fish/completions/ or source directly.
|
|
3
|
+
#
|
|
4
|
+
# Usage:
|
|
5
|
+
# cp bingo-light.fish ~/.config/fish/completions/
|
|
6
|
+
|
|
7
|
+
# ---- Helper: detect whether a specific command/subcommand is already on the line ----
|
|
8
|
+
|
|
9
|
+
# Returns true when no subcommand has been given yet (top-level completions).
|
|
10
|
+
function __bingo_light_needs_command
|
|
11
|
+
set -l cmd (commandline -opc)
|
|
12
|
+
if test (count $cmd) -eq 1
|
|
13
|
+
return 0
|
|
14
|
+
end
|
|
15
|
+
return 1
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Returns true when the first positional argument matches any of the supplied values.
|
|
19
|
+
function __bingo_light_using_command
|
|
20
|
+
set -l cmd (commandline -opc)
|
|
21
|
+
if test (count $cmd) -lt 2
|
|
22
|
+
return 1
|
|
23
|
+
end
|
|
24
|
+
for arg in $argv
|
|
25
|
+
if test "$cmd[2]" = "$arg"
|
|
26
|
+
return 0
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
return 1
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Returns true when we are inside "patch" (or "p") and need a patch subcommand.
|
|
33
|
+
function __bingo_light_patch_needs_subcommand
|
|
34
|
+
set -l cmd (commandline -opc)
|
|
35
|
+
if test (count $cmd) -lt 2
|
|
36
|
+
return 1
|
|
37
|
+
end
|
|
38
|
+
if test "$cmd[2]" != patch -a "$cmd[2]" != p
|
|
39
|
+
return 1
|
|
40
|
+
end
|
|
41
|
+
if test (count $cmd) -eq 2
|
|
42
|
+
return 0
|
|
43
|
+
end
|
|
44
|
+
return 1
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Returns true when the patch subcommand matches any of the supplied values.
|
|
48
|
+
function __bingo_light_patch_using_subcommand
|
|
49
|
+
set -l cmd (commandline -opc)
|
|
50
|
+
if test (count $cmd) -lt 3
|
|
51
|
+
return 1
|
|
52
|
+
end
|
|
53
|
+
if test "$cmd[2]" != patch -a "$cmd[2]" != p
|
|
54
|
+
return 1
|
|
55
|
+
end
|
|
56
|
+
for arg in $argv
|
|
57
|
+
if test "$cmd[3]" = "$arg"
|
|
58
|
+
return 0
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
return 1
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# ---- Disable file completions by default ----
|
|
65
|
+
complete -c bingo-light -f
|
|
66
|
+
|
|
67
|
+
# ---- Top-level commands ----
|
|
68
|
+
complete -c bingo-light -n __bingo_light_needs_command -a init -d 'Initialize a new bingo-light project'
|
|
69
|
+
complete -c bingo-light -n __bingo_light_needs_command -a setup -d 'Configure MCP for AI tools (interactive)'
|
|
70
|
+
complete -c bingo-light -n __bingo_light_needs_command -a patch -d 'Manage patches'
|
|
71
|
+
complete -c bingo-light -n __bingo_light_needs_command -a sync -d 'Synchronize changes with upstream'
|
|
72
|
+
complete -c bingo-light -n __bingo_light_needs_command -a status -d 'Show current status'
|
|
73
|
+
complete -c bingo-light -n __bingo_light_needs_command -a doctor -d 'Diagnose and fix common problems'
|
|
74
|
+
complete -c bingo-light -n __bingo_light_needs_command -a auto-sync -d 'Enable or configure automatic synchronization'
|
|
75
|
+
complete -c bingo-light -n __bingo_light_needs_command -a log -d 'Show change log'
|
|
76
|
+
complete -c bingo-light -n __bingo_light_needs_command -a undo -d 'Undo the last operation'
|
|
77
|
+
complete -c bingo-light -n __bingo_light_needs_command -a diff -d 'Show differences between states'
|
|
78
|
+
complete -c bingo-light -n __bingo_light_needs_command -a version -d 'Print version information'
|
|
79
|
+
complete -c bingo-light -n __bingo_light_needs_command -a help -d 'Show help for a command'
|
|
80
|
+
complete -c bingo-light -n __bingo_light_needs_command -a conflict-analyze -d 'Analyze conflicts during rebase'
|
|
81
|
+
complete -c bingo-light -n __bingo_light_needs_command -a conflict-resolve -d 'Resolve a conflict file and continue'
|
|
82
|
+
complete -c bingo-light -n __bingo_light_needs_command -a config -d 'Get/set/list configuration'
|
|
83
|
+
complete -c bingo-light -n __bingo_light_needs_command -a history -d 'Show sync history with hash mappings'
|
|
84
|
+
complete -c bingo-light -n __bingo_light_needs_command -a test -d 'Run configured test suite'
|
|
85
|
+
complete -c bingo-light -n __bingo_light_needs_command -a workspace -d 'Manage multiple forks'
|
|
86
|
+
complete -c bingo-light -n __bingo_light_needs_command -a smart-sync -d 'Smart sync with circuit breaker and partial state'
|
|
87
|
+
complete -c bingo-light -n __bingo_light_needs_command -a session -d 'Manage session memory'
|
|
88
|
+
|
|
89
|
+
# Short aliases
|
|
90
|
+
complete -c bingo-light -n __bingo_light_needs_command -a p -d 'Alias for patch'
|
|
91
|
+
complete -c bingo-light -n __bingo_light_needs_command -a s -d 'Alias for sync'
|
|
92
|
+
complete -c bingo-light -n __bingo_light_needs_command -a st -d 'Alias for status'
|
|
93
|
+
complete -c bingo-light -n __bingo_light_needs_command -a d -d 'Alias for diff'
|
|
94
|
+
complete -c bingo-light -n __bingo_light_needs_command -a ws -d 'Alias for workspace'
|
|
95
|
+
|
|
96
|
+
# Global flags
|
|
97
|
+
complete -c bingo-light -n __bingo_light_needs_command -s h -l help -d 'Show help'
|
|
98
|
+
complete -c bingo-light -n __bingo_light_needs_command -l version -d 'Show version'
|
|
99
|
+
complete -c bingo-light -l json -d 'Output structured JSON'
|
|
100
|
+
complete -c bingo-light -l yes -d 'Non-interactive mode, auto-confirm prompts'
|
|
101
|
+
complete -c bingo-light -s y -d 'Non-interactive mode (short for --yes)'
|
|
102
|
+
|
|
103
|
+
# ---- sync flags (also alias "s") ----
|
|
104
|
+
complete -c bingo-light -n '__bingo_light_using_command sync s' -s n -l dry-run -d 'Show what would be done without making changes'
|
|
105
|
+
complete -c bingo-light -n '__bingo_light_using_command sync s' -s f -l force -d 'Force sync, overwriting conflicts'
|
|
106
|
+
complete -c bingo-light -n '__bingo_light_using_command sync s' -s t -l test -d 'Run test suite after sync'
|
|
107
|
+
complete -c bingo-light -n '__bingo_light_using_command sync s' -s h -l help -d 'Show help'
|
|
108
|
+
|
|
109
|
+
# ---- status flags (also alias "st") ----
|
|
110
|
+
complete -c bingo-light -n '__bingo_light_using_command status st' -s h -l help -d 'Show help'
|
|
111
|
+
|
|
112
|
+
# ---- diff flags (also alias "d") ----
|
|
113
|
+
complete -c bingo-light -n '__bingo_light_using_command diff d' -s h -l help -d 'Show help'
|
|
114
|
+
|
|
115
|
+
# ---- Simple commands (no subcommands, just --help) ----
|
|
116
|
+
complete -c bingo-light -n '__bingo_light_using_command init' -s h -l help -d 'Show help'
|
|
117
|
+
complete -c bingo-light -n '__bingo_light_using_command doctor' -s h -l help -d 'Show help'
|
|
118
|
+
complete -c bingo-light -n '__bingo_light_using_command auto-sync' -s h -l help -d 'Show help'
|
|
119
|
+
complete -c bingo-light -n '__bingo_light_using_command log' -s h -l help -d 'Show help'
|
|
120
|
+
complete -c bingo-light -n '__bingo_light_using_command undo' -s h -l help -d 'Show help'
|
|
121
|
+
complete -c bingo-light -n '__bingo_light_using_command version' -s h -l help -d 'Show help'
|
|
122
|
+
|
|
123
|
+
# ---- conflict-resolve flags ----
|
|
124
|
+
complete -c bingo-light -n '__bingo_light_using_command conflict-resolve' -s h -l help -d 'Show help'
|
|
125
|
+
|
|
126
|
+
# ---- help: complete with command names ----
|
|
127
|
+
complete -c bingo-light -n '__bingo_light_using_command help' -a 'init setup patch sync status doctor auto-sync log undo diff version conflict-analyze conflict-resolve config history test workspace smart-sync session' -d 'Command'
|
|
128
|
+
|
|
129
|
+
# ---- patch subcommands (also alias "p") ----
|
|
130
|
+
complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a new -d 'Create a new patch'
|
|
131
|
+
complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a list -d 'List all patches'
|
|
132
|
+
complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a show -d 'Show details of a patch'
|
|
133
|
+
complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a edit -d 'Edit an existing patch'
|
|
134
|
+
complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a drop -d 'Remove a patch'
|
|
135
|
+
complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a export -d 'Export patches to files'
|
|
136
|
+
complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a import -d 'Import patches from files'
|
|
137
|
+
complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a reorder -d 'Reorder the patch stack'
|
|
138
|
+
complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a squash -d 'Squash two patches into one'
|
|
139
|
+
complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a meta -d 'Get/set patch metadata'
|
|
140
|
+
|
|
141
|
+
# Patch short aliases
|
|
142
|
+
complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a ls -d 'Alias for list'
|
|
143
|
+
complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a add -d 'Alias for new'
|
|
144
|
+
complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a create -d 'Alias for new'
|
|
145
|
+
complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a rm -d 'Alias for drop'
|
|
146
|
+
complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a remove -d 'Alias for drop'
|
|
147
|
+
|
|
148
|
+
# Help flag for patch itself
|
|
149
|
+
complete -c bingo-light -n __bingo_light_patch_needs_subcommand -s h -l help -d 'Show help'
|
|
150
|
+
|
|
151
|
+
# ---- patch list / ls flags ----
|
|
152
|
+
complete -c bingo-light -n '__bingo_light_patch_using_subcommand list ls' -s v -l verbose -d 'Show detailed patch information'
|
|
153
|
+
complete -c bingo-light -n '__bingo_light_patch_using_subcommand list ls' -s h -l help -d 'Show help'
|
|
154
|
+
|
|
155
|
+
# ---- patch subcommands that only take --help ----
|
|
156
|
+
complete -c bingo-light -n '__bingo_light_patch_using_subcommand new add create' -s h -l help -d 'Show help'
|
|
157
|
+
complete -c bingo-light -n '__bingo_light_patch_using_subcommand show' -s h -l help -d 'Show help'
|
|
158
|
+
complete -c bingo-light -n '__bingo_light_patch_using_subcommand edit' -s h -l help -d 'Show help'
|
|
159
|
+
complete -c bingo-light -n '__bingo_light_patch_using_subcommand drop rm remove' -s h -l help -d 'Show help'
|
|
160
|
+
complete -c bingo-light -n '__bingo_light_patch_using_subcommand export' -s h -l help -d 'Show help'
|
|
161
|
+
complete -c bingo-light -n '__bingo_light_patch_using_subcommand import' -s h -l help -d 'Show help'
|
|
162
|
+
complete -c bingo-light -n '__bingo_light_patch_using_subcommand reorder' -s h -l help -d 'Show help'
|
|
163
|
+
complete -c bingo-light -n '__bingo_light_patch_using_subcommand squash' -s h -l help -d 'Show help'
|
|
164
|
+
complete -c bingo-light -n '__bingo_light_patch_using_subcommand meta' -s h -l help -d 'Show help'
|
|
165
|
+
|
|
166
|
+
# ---- workspace subcommands (also alias "ws") ----
|
|
167
|
+
|
|
168
|
+
# Returns true when we are inside "workspace" (or "ws") and need a workspace subcommand.
|
|
169
|
+
function __bingo_light_workspace_needs_subcommand
|
|
170
|
+
set -l cmd (commandline -opc)
|
|
171
|
+
if test (count $cmd) -lt 2
|
|
172
|
+
return 1
|
|
173
|
+
end
|
|
174
|
+
if test "$cmd[2]" != workspace -a "$cmd[2]" != ws
|
|
175
|
+
return 1
|
|
176
|
+
end
|
|
177
|
+
if test (count $cmd) -eq 2
|
|
178
|
+
return 0
|
|
179
|
+
end
|
|
180
|
+
return 1
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
complete -c bingo-light -n __bingo_light_workspace_needs_subcommand -a init -d 'Initialize workspace'
|
|
184
|
+
complete -c bingo-light -n __bingo_light_workspace_needs_subcommand -a add -d 'Add a repo to workspace'
|
|
185
|
+
complete -c bingo-light -n __bingo_light_workspace_needs_subcommand -a remove -d 'Remove a repo from workspace'
|
|
186
|
+
complete -c bingo-light -n __bingo_light_workspace_needs_subcommand -a list -d 'List workspace repos'
|
|
187
|
+
complete -c bingo-light -n __bingo_light_workspace_needs_subcommand -a sync -d 'Sync all workspace repos'
|
|
188
|
+
complete -c bingo-light -n __bingo_light_workspace_needs_subcommand -a status -d 'Show workspace status'
|
|
189
|
+
|
|
190
|
+
# ---- New top-level command flags ----
|
|
191
|
+
complete -c bingo-light -n '__bingo_light_using_command conflict-analyze' -s h -l help -d 'Show help'
|
|
192
|
+
complete -c bingo-light -n '__bingo_light_using_command config' -s h -l help -d 'Show help'
|
|
193
|
+
complete -c bingo-light -n '__bingo_light_using_command history' -s h -l help -d 'Show help'
|
|
194
|
+
complete -c bingo-light -n '__bingo_light_using_command test' -s h -l help -d 'Show help'
|
|
195
|
+
complete -c bingo-light -n '__bingo_light_using_command workspace ws' -s h -l help -d 'Show help'
|
|
196
|
+
complete -c bingo-light -n '__bingo_light_using_command smart-sync' -s h -l help -d 'Show help'
|
|
197
|
+
complete -c bingo-light -n '__bingo_light_using_command session' -s h -l help -d 'Show help'
|