bingo-light 2.1.1 → 2.1.3
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/README.en.md +20 -7
- package/README.md +209 -126
- package/bingo-light +385 -11
- package/bingo_core/__init__.py +3 -1
- package/bingo_core/dep.py +1012 -0
- package/bingo_core/dep_fork.py +268 -0
- package/bingo_core/dep_npm.py +113 -0
- package/bingo_core/dep_pip.py +178 -0
- package/bingo_core/repo.py +795 -8
- package/bingo_core/setup.py +73 -17
- package/bingo_core/state.py +1 -1
- package/bingo_core/team.py +170 -0
- package/completions/bingo-light.bash +26 -3
- package/completions/bingo-light.fish +46 -1
- package/completions/bingo-light.zsh +38 -2
- package/mcp-server.py +346 -6
- package/package.json +1 -1
package/bingo_core/setup.py
CHANGED
|
@@ -151,39 +151,85 @@ def _get_tools() -> List[AITool]:
|
|
|
151
151
|
# ─── MCP Server Path Detection ──────────────────────────────────────────────
|
|
152
152
|
|
|
153
153
|
|
|
154
|
+
def _find_python_for_mcp() -> str:
|
|
155
|
+
"""Find the correct python3 for running the MCP server.
|
|
156
|
+
|
|
157
|
+
When installed via pipx, system python3 can't import bingo_core.
|
|
158
|
+
We need to use the same python that runs bingo-light itself.
|
|
159
|
+
"""
|
|
160
|
+
# Use the same python interpreter that's running this code
|
|
161
|
+
return sys.executable
|
|
162
|
+
|
|
163
|
+
|
|
154
164
|
def find_mcp_server() -> Tuple[str, List[str]]:
|
|
155
165
|
"""Find the MCP server command and args.
|
|
156
166
|
|
|
157
|
-
Returns (command, args) tuple.
|
|
158
|
-
1. Installed `bingo-light-mcp` on PATH
|
|
159
|
-
2. mcp-server.py next to the running script
|
|
160
|
-
3. mcp-server.py next to bingo_core package
|
|
167
|
+
Returns (command, args) tuple.
|
|
161
168
|
"""
|
|
162
|
-
|
|
169
|
+
python = _find_python_for_mcp()
|
|
170
|
+
|
|
171
|
+
# 1. Installed binary on PATH (npm installs bingo-light-mcp)
|
|
163
172
|
for name in ("bingo-light-mcp", "mcp-server.py"):
|
|
164
173
|
mcp_bin = shutil.which(name)
|
|
165
174
|
if mcp_bin:
|
|
166
|
-
return (
|
|
175
|
+
return (python, [mcp_bin])
|
|
167
176
|
|
|
168
177
|
# 2. Relative to running script
|
|
169
178
|
script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
|
|
170
179
|
candidate = os.path.join(script_dir, "mcp-server.py")
|
|
171
180
|
if os.path.isfile(candidate):
|
|
172
|
-
return (
|
|
181
|
+
return (python, [candidate])
|
|
173
182
|
|
|
174
183
|
# 3. Relative to bingo_core package
|
|
175
184
|
pkg_dir = os.path.dirname(os.path.abspath(__file__))
|
|
176
185
|
candidate = os.path.join(os.path.dirname(pkg_dir), "mcp-server.py")
|
|
177
186
|
if os.path.isfile(candidate):
|
|
178
|
-
return (
|
|
187
|
+
return (python, [candidate])
|
|
179
188
|
|
|
180
189
|
# Fallback
|
|
181
|
-
return (
|
|
190
|
+
return (python, ["bingo-light-mcp"])
|
|
182
191
|
|
|
183
192
|
|
|
184
193
|
# ─── Config Writer ───────────────────────────────────────────────────────────
|
|
185
194
|
|
|
186
195
|
|
|
196
|
+
def _configure_claude_code_via_cli(
|
|
197
|
+
command: str,
|
|
198
|
+
args: List[str],
|
|
199
|
+
server_name: str = "bingo-light",
|
|
200
|
+
) -> Optional[Dict[str, Any]]:
|
|
201
|
+
"""Try to configure Claude Code using `claude mcp add` CLI.
|
|
202
|
+
|
|
203
|
+
Returns result dict if successful, None if claude CLI not available.
|
|
204
|
+
"""
|
|
205
|
+
claude_bin = shutil.which("claude")
|
|
206
|
+
if not claude_bin:
|
|
207
|
+
return None
|
|
208
|
+
|
|
209
|
+
import subprocess
|
|
210
|
+
# Remove existing entry first (ignore errors if not present)
|
|
211
|
+
subprocess.run(
|
|
212
|
+
[claude_bin, "mcp", "remove", server_name],
|
|
213
|
+
capture_output=True, text=True,
|
|
214
|
+
)
|
|
215
|
+
# Add new entry: claude mcp add <name> -- <command> <args...>
|
|
216
|
+
cmd = [claude_bin, "mcp", "add", server_name, "--", command] + args
|
|
217
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
218
|
+
if result.returncode == 0:
|
|
219
|
+
return {
|
|
220
|
+
"ok": True,
|
|
221
|
+
"tool": "claude-code",
|
|
222
|
+
"name": "Claude Code",
|
|
223
|
+
"config_path": "~/.claude.json (via claude mcp add)",
|
|
224
|
+
"action": "created",
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
"ok": False,
|
|
228
|
+
"tool": "claude-code",
|
|
229
|
+
"error": result.stderr.strip() or "claude mcp add failed",
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
|
|
187
233
|
def write_mcp_config(
|
|
188
234
|
tool: AITool,
|
|
189
235
|
command: str,
|
|
@@ -194,6 +240,13 @@ def write_mcp_config(
|
|
|
194
240
|
|
|
195
241
|
Returns dict with ok, tool, config_path, action (created|updated|error).
|
|
196
242
|
"""
|
|
243
|
+
# Claude Code: prefer `claude mcp add` CLI (writes to correct location)
|
|
244
|
+
if tool.id == "claude-code":
|
|
245
|
+
cli_result = _configure_claude_code_via_cli(command, args, server_name)
|
|
246
|
+
if cli_result:
|
|
247
|
+
return cli_result
|
|
248
|
+
# Fall through to JSON file method if claude CLI not available
|
|
249
|
+
|
|
197
250
|
config_path = tool.expand_path()
|
|
198
251
|
config_dir = os.path.dirname(config_path)
|
|
199
252
|
|
|
@@ -594,17 +647,20 @@ def _get_skill_targets() -> List[SkillTarget]:
|
|
|
594
647
|
|
|
595
648
|
def find_skill_file() -> Optional[str]:
|
|
596
649
|
"""Find the bingo.md skill file in common locations."""
|
|
597
|
-
candidates = []
|
|
598
|
-
|
|
599
|
-
script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
|
|
600
|
-
candidates.append(os.path.join(script_dir, ".claude", "commands", "bingo.md"))
|
|
601
|
-
|
|
602
650
|
pkg_dir = os.path.dirname(os.path.abspath(__file__))
|
|
651
|
+
script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
|
|
603
652
|
repo_dir = os.path.dirname(pkg_dir)
|
|
604
|
-
candidates.append(os.path.join(repo_dir, ".claude", "commands", "bingo.md"))
|
|
605
653
|
|
|
606
|
-
|
|
607
|
-
|
|
654
|
+
candidates = [
|
|
655
|
+
# Bundled inside bingo_core/ (pip/npm installs)
|
|
656
|
+
os.path.join(pkg_dir, "_skill.md"),
|
|
657
|
+
# Repo layout
|
|
658
|
+
os.path.join(repo_dir, ".claude", "commands", "bingo.md"),
|
|
659
|
+
# Script-relative (install.sh)
|
|
660
|
+
os.path.join(script_dir, ".claude", "commands", "bingo.md"),
|
|
661
|
+
# npm package layout
|
|
662
|
+
os.path.join(script_dir, "..", ".claude", "commands", "bingo.md"),
|
|
663
|
+
]
|
|
608
664
|
|
|
609
665
|
for c in candidates:
|
|
610
666
|
if os.path.isfile(c):
|
package/bingo_core/state.py
CHANGED
|
@@ -184,7 +184,7 @@ class State:
|
|
|
184
184
|
for t in (v.strip() for v in value.split(",")):
|
|
185
185
|
if t and t not in tags_list:
|
|
186
186
|
tags_list.append(t)
|
|
187
|
-
elif key in ("reason", "expires", "upstream_pr", "status"):
|
|
187
|
+
elif key in ("reason", "expires", "upstream_pr", "status", "owner"):
|
|
188
188
|
p[key] = value
|
|
189
189
|
self._save_metadata(data)
|
|
190
190
|
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""
|
|
2
|
+
bingo_core.team — Team collaboration state (.bingo/team.json).
|
|
3
|
+
|
|
4
|
+
Manages patch locks and team membership for multi-person fork maintenance.
|
|
5
|
+
Advisory locking: prevents accidental concurrent edits, not a security boundary.
|
|
6
|
+
|
|
7
|
+
Storage:
|
|
8
|
+
.bingo/team.json
|
|
9
|
+
{
|
|
10
|
+
"locks": {
|
|
11
|
+
"<patch_name>": {
|
|
12
|
+
"owner": "<user>",
|
|
13
|
+
"locked_at": "ISO8601",
|
|
14
|
+
"reason": ""
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
Python 3.8+ stdlib only.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import os
|
|
26
|
+
import tempfile
|
|
27
|
+
from datetime import datetime, timezone
|
|
28
|
+
from typing import List, Optional
|
|
29
|
+
|
|
30
|
+
from bingo_core import BINGO_DIR
|
|
31
|
+
from bingo_core.exceptions import BingoError
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TeamState:
|
|
35
|
+
"""Manages .bingo/team.json for patch locking and team coordination."""
|
|
36
|
+
|
|
37
|
+
def __init__(self, repo_dir: str, git=None):
|
|
38
|
+
self.repo_dir = repo_dir
|
|
39
|
+
self._git = git # optional Git instance for get_user()
|
|
40
|
+
self.bingo_dir = os.path.join(repo_dir, BINGO_DIR)
|
|
41
|
+
self.team_file = os.path.join(self.bingo_dir, "team.json")
|
|
42
|
+
|
|
43
|
+
def _load(self) -> dict:
|
|
44
|
+
"""Load team.json, returning empty structure if missing."""
|
|
45
|
+
if not os.path.isfile(self.team_file):
|
|
46
|
+
return {"locks": {}}
|
|
47
|
+
try:
|
|
48
|
+
with open(self.team_file) as f:
|
|
49
|
+
data = json.load(f)
|
|
50
|
+
if "locks" not in data:
|
|
51
|
+
data["locks"] = {}
|
|
52
|
+
return data
|
|
53
|
+
except (json.JSONDecodeError, IOError):
|
|
54
|
+
return {"locks": {}}
|
|
55
|
+
|
|
56
|
+
def _save(self, data: dict) -> None:
|
|
57
|
+
"""Atomically write team.json."""
|
|
58
|
+
os.makedirs(self.bingo_dir, exist_ok=True)
|
|
59
|
+
dir_name = os.path.dirname(self.team_file)
|
|
60
|
+
fd, tmp_path = tempfile.mkstemp(suffix=".tmp", dir=dir_name)
|
|
61
|
+
try:
|
|
62
|
+
with os.fdopen(fd, "w") as f:
|
|
63
|
+
json.dump(data, f, indent=2)
|
|
64
|
+
os.replace(tmp_path, self.team_file)
|
|
65
|
+
except Exception:
|
|
66
|
+
try:
|
|
67
|
+
os.unlink(tmp_path)
|
|
68
|
+
except FileNotFoundError:
|
|
69
|
+
pass
|
|
70
|
+
raise
|
|
71
|
+
|
|
72
|
+
def get_user(self) -> str:
|
|
73
|
+
"""Detect current user from git config or environment."""
|
|
74
|
+
if self._git:
|
|
75
|
+
for key in ("user.name", "user.email"):
|
|
76
|
+
try:
|
|
77
|
+
val = self._git.run("config", key, check=False)
|
|
78
|
+
if val and val.strip():
|
|
79
|
+
return val.strip()
|
|
80
|
+
except Exception:
|
|
81
|
+
pass
|
|
82
|
+
return os.environ.get("USER", "unknown")
|
|
83
|
+
|
|
84
|
+
def lock(self, patch_name: str, owner: str = "", reason: str = "") -> dict:
|
|
85
|
+
"""Lock a patch for exclusive editing.
|
|
86
|
+
|
|
87
|
+
Returns {"ok": True, "patch": ..., "owner": ..., "locked_at": ...}
|
|
88
|
+
Raises BingoError if already locked by another user.
|
|
89
|
+
"""
|
|
90
|
+
if not owner:
|
|
91
|
+
owner = self.get_user()
|
|
92
|
+
data = self._load()
|
|
93
|
+
existing = data["locks"].get(patch_name)
|
|
94
|
+
if existing and existing["owner"] != owner:
|
|
95
|
+
raise BingoError(
|
|
96
|
+
f"Patch '{patch_name}' is locked by {existing['owner']} "
|
|
97
|
+
f"(since {existing['locked_at']}). "
|
|
98
|
+
f"They must unlock it first, or use --force."
|
|
99
|
+
)
|
|
100
|
+
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
101
|
+
data["locks"][patch_name] = {
|
|
102
|
+
"owner": owner,
|
|
103
|
+
"locked_at": now,
|
|
104
|
+
"reason": reason,
|
|
105
|
+
}
|
|
106
|
+
self._save(data)
|
|
107
|
+
return {
|
|
108
|
+
"ok": True,
|
|
109
|
+
"patch": patch_name,
|
|
110
|
+
"owner": owner,
|
|
111
|
+
"locked_at": now,
|
|
112
|
+
"reason": reason,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
def unlock(self, patch_name: str, owner: str = "", force: bool = False) -> dict:
|
|
116
|
+
"""Unlock a patch.
|
|
117
|
+
|
|
118
|
+
Returns {"ok": True, "patch": ..., "owner": ...}
|
|
119
|
+
Raises BingoError if locked by someone else (unless force=True).
|
|
120
|
+
"""
|
|
121
|
+
if not owner:
|
|
122
|
+
owner = self.get_user()
|
|
123
|
+
data = self._load()
|
|
124
|
+
existing = data["locks"].get(patch_name)
|
|
125
|
+
if not existing:
|
|
126
|
+
return {"ok": True, "patch": patch_name, "owner": owner, "was_locked": False}
|
|
127
|
+
if existing["owner"] != owner and not force:
|
|
128
|
+
raise BingoError(
|
|
129
|
+
f"Patch '{patch_name}' is locked by {existing['owner']}. "
|
|
130
|
+
"Use --force to override."
|
|
131
|
+
)
|
|
132
|
+
del data["locks"][patch_name]
|
|
133
|
+
self._save(data)
|
|
134
|
+
return {
|
|
135
|
+
"ok": True,
|
|
136
|
+
"patch": patch_name,
|
|
137
|
+
"owner": owner,
|
|
138
|
+
"was_locked": True,
|
|
139
|
+
"previous_owner": existing["owner"],
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
def get_lock(self, patch_name: str) -> Optional[dict]:
|
|
143
|
+
"""Get lock info for a patch, or None if unlocked."""
|
|
144
|
+
data = self._load()
|
|
145
|
+
return data["locks"].get(patch_name)
|
|
146
|
+
|
|
147
|
+
def is_locked_by_other(self, patch_name: str, current_user: str = "") -> bool:
|
|
148
|
+
"""Check if a patch is locked by someone other than current_user."""
|
|
149
|
+
if not current_user:
|
|
150
|
+
current_user = self.get_user()
|
|
151
|
+
lock = self.get_lock(patch_name)
|
|
152
|
+
if not lock:
|
|
153
|
+
return False
|
|
154
|
+
return lock["owner"] != current_user
|
|
155
|
+
|
|
156
|
+
def list_locks(self) -> List[dict]:
|
|
157
|
+
"""List all active locks.
|
|
158
|
+
|
|
159
|
+
Returns list of {"patch": ..., "owner": ..., "locked_at": ..., "reason": ...}
|
|
160
|
+
"""
|
|
161
|
+
data = self._load()
|
|
162
|
+
result = []
|
|
163
|
+
for patch_name, info in data["locks"].items():
|
|
164
|
+
result.append({
|
|
165
|
+
"patch": patch_name,
|
|
166
|
+
"owner": info.get("owner", ""),
|
|
167
|
+
"locked_at": info.get("locked_at", ""),
|
|
168
|
+
"reason": info.get("reason", ""),
|
|
169
|
+
})
|
|
170
|
+
return result
|
|
@@ -10,11 +10,11 @@ _bingo_light() {
|
|
|
10
10
|
local cur prev words cword
|
|
11
11
|
_init_completion || return
|
|
12
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"
|
|
13
|
+
local -r toplevel_commands="init setup patch dep sync status doctor auto-sync log undo diff version help conflict-analyze conflict-resolve config history test workspace smart-sync session report"
|
|
14
14
|
local -r toplevel_aliases="p s st d ws"
|
|
15
15
|
local -r all_toplevel="${toplevel_commands} ${toplevel_aliases}"
|
|
16
16
|
|
|
17
|
-
local -r patch_subcommands="new list show edit drop export import reorder squash meta"
|
|
17
|
+
local -r patch_subcommands="new list show edit drop export import reorder squash meta lock unlock check upstream expire stats"
|
|
18
18
|
local -r patch_aliases="ls add create rm remove"
|
|
19
19
|
local -r all_patch="${patch_subcommands} ${patch_aliases}"
|
|
20
20
|
|
|
@@ -36,7 +36,10 @@ _bingo_light() {
|
|
|
36
36
|
diff|d)
|
|
37
37
|
cmd="diff"
|
|
38
38
|
;;
|
|
39
|
-
|
|
39
|
+
dep)
|
|
40
|
+
cmd="dep"
|
|
41
|
+
;;
|
|
42
|
+
init|setup|doctor|auto-sync|log|undo|version|help|conflict-analyze|conflict-resolve|config|history|test|workspace|ws|smart-sync|session|report)
|
|
40
43
|
cmd="${words[i]}"
|
|
41
44
|
;;
|
|
42
45
|
*)
|
|
@@ -91,6 +94,26 @@ _bingo_light() {
|
|
|
91
94
|
return
|
|
92
95
|
fi
|
|
93
96
|
|
|
97
|
+
# Inside "conflict-resolve"
|
|
98
|
+
if [[ "$cmd" == "conflict-resolve" ]]; then
|
|
99
|
+
COMPREPLY=( $(compgen -W "--verify --content-stdin --help -h" -- "$cur") )
|
|
100
|
+
return
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
# Inside "dep" -- complete subcommands
|
|
104
|
+
if [[ "$cmd" == "dep" ]]; then
|
|
105
|
+
if [[ -z "$subcmd" ]]; then
|
|
106
|
+
COMPREPLY=( $(compgen -W "patch apply sync status list drop override fork" -- "$cur") )
|
|
107
|
+
elif [[ "$subcmd" == "override" ]]; then
|
|
108
|
+
COMPREPLY=( $(compgen -W "list check add drop" -- "$cur") )
|
|
109
|
+
elif [[ "$subcmd" == "fork" ]]; then
|
|
110
|
+
COMPREPLY=( $(compgen -W "list check sync" -- "$cur") )
|
|
111
|
+
else
|
|
112
|
+
COMPREPLY=( $(compgen -W "--help -h" -- "$cur") )
|
|
113
|
+
fi
|
|
114
|
+
return
|
|
115
|
+
fi
|
|
116
|
+
|
|
94
117
|
# Inside "workspace" (or alias "ws") -- complete subcommands
|
|
95
118
|
if [[ "$cmd" == "workspace" || "$cmd" == "ws" ]]; then
|
|
96
119
|
if [[ -z "$subcmd" ]]; then
|
|
@@ -68,6 +68,7 @@ complete -c bingo-light -f
|
|
|
68
68
|
complete -c bingo-light -n __bingo_light_needs_command -a init -d 'Initialize a new bingo-light project'
|
|
69
69
|
complete -c bingo-light -n __bingo_light_needs_command -a setup -d 'Configure MCP for AI tools (interactive)'
|
|
70
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 dep -d 'Patch npm/pip dependencies'
|
|
71
72
|
complete -c bingo-light -n __bingo_light_needs_command -a sync -d 'Synchronize changes with upstream'
|
|
72
73
|
complete -c bingo-light -n __bingo_light_needs_command -a status -d 'Show current status'
|
|
73
74
|
complete -c bingo-light -n __bingo_light_needs_command -a doctor -d 'Diagnose and fix common problems'
|
|
@@ -85,6 +86,7 @@ complete -c bingo-light -n __bingo_light_needs_command -a test -d 'Run config
|
|
|
85
86
|
complete -c bingo-light -n __bingo_light_needs_command -a workspace -d 'Manage multiple forks'
|
|
86
87
|
complete -c bingo-light -n __bingo_light_needs_command -a smart-sync -d 'Smart sync with circuit breaker and partial state'
|
|
87
88
|
complete -c bingo-light -n __bingo_light_needs_command -a session -d 'Manage session memory'
|
|
89
|
+
complete -c bingo-light -n __bingo_light_needs_command -a report -d 'Generate fork health report'
|
|
88
90
|
|
|
89
91
|
# Short aliases
|
|
90
92
|
complete -c bingo-light -n __bingo_light_needs_command -a p -d 'Alias for patch'
|
|
@@ -122,9 +124,11 @@ complete -c bingo-light -n '__bingo_light_using_command version' -s h -l help
|
|
|
122
124
|
|
|
123
125
|
# ---- conflict-resolve flags ----
|
|
124
126
|
complete -c bingo-light -n '__bingo_light_using_command conflict-resolve' -s h -l help -d 'Show help'
|
|
127
|
+
complete -c bingo-light -n '__bingo_light_using_command conflict-resolve' -l verify -d 'Run test.command after the final rebase continues'
|
|
128
|
+
complete -c bingo-light -n '__bingo_light_using_command conflict-resolve' -l content-stdin -d 'Read resolved content from stdin'
|
|
125
129
|
|
|
126
130
|
# ---- 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'
|
|
131
|
+
complete -c bingo-light -n '__bingo_light_using_command help' -a 'init setup patch dep sync status doctor auto-sync log undo diff version conflict-analyze conflict-resolve config history test workspace smart-sync session report' -d 'Command'
|
|
128
132
|
|
|
129
133
|
# ---- patch subcommands (also alias "p") ----
|
|
130
134
|
complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a new -d 'Create a new patch'
|
|
@@ -137,6 +141,12 @@ complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a import -d 'I
|
|
|
137
141
|
complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a reorder -d 'Reorder the patch stack'
|
|
138
142
|
complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a squash -d 'Squash two patches into one'
|
|
139
143
|
complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a meta -d 'Get/set patch metadata'
|
|
144
|
+
complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a lock -d 'Lock a patch for exclusive editing'
|
|
145
|
+
complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a unlock -d 'Unlock a patch'
|
|
146
|
+
complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a check -d 'Check if patches are still needed'
|
|
147
|
+
complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a upstream -d 'Export patch as PR-ready diff'
|
|
148
|
+
complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a expire -d 'List expired patches'
|
|
149
|
+
complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a stats -d 'Show patch health metrics'
|
|
140
150
|
|
|
141
151
|
# Patch short aliases
|
|
142
152
|
complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a ls -d 'Alias for list'
|
|
@@ -162,6 +172,39 @@ complete -c bingo-light -n '__bingo_light_patch_using_subcommand import'
|
|
|
162
172
|
complete -c bingo-light -n '__bingo_light_patch_using_subcommand reorder' -s h -l help -d 'Show help'
|
|
163
173
|
complete -c bingo-light -n '__bingo_light_patch_using_subcommand squash' -s h -l help -d 'Show help'
|
|
164
174
|
complete -c bingo-light -n '__bingo_light_patch_using_subcommand meta' -s h -l help -d 'Show help'
|
|
175
|
+
complete -c bingo-light -n '__bingo_light_patch_using_subcommand lock' -s h -l help -d 'Show help'
|
|
176
|
+
complete -c bingo-light -n '__bingo_light_patch_using_subcommand lock' -l reason -d 'Reason for locking'
|
|
177
|
+
complete -c bingo-light -n '__bingo_light_patch_using_subcommand unlock' -s h -l help -d 'Show help'
|
|
178
|
+
complete -c bingo-light -n '__bingo_light_patch_using_subcommand unlock' -l force -d 'Force unlock even if locked by someone else'
|
|
179
|
+
complete -c bingo-light -n '__bingo_light_patch_using_subcommand check' -s h -l help -d 'Show help'
|
|
180
|
+
complete -c bingo-light -n '__bingo_light_patch_using_subcommand upstream' -s h -l help -d 'Show help'
|
|
181
|
+
complete -c bingo-light -n '__bingo_light_patch_using_subcommand expire' -s h -l help -d 'Show help'
|
|
182
|
+
complete -c bingo-light -n '__bingo_light_patch_using_subcommand stats' -s h -l help -d 'Show help'
|
|
183
|
+
|
|
184
|
+
# ---- dep subcommands ----
|
|
185
|
+
|
|
186
|
+
function __bingo_light_dep_needs_subcommand
|
|
187
|
+
set -l cmd (commandline -opc)
|
|
188
|
+
if test (count $cmd) -lt 2
|
|
189
|
+
return 1
|
|
190
|
+
end
|
|
191
|
+
if test "$cmd[2]" != dep
|
|
192
|
+
return 1
|
|
193
|
+
end
|
|
194
|
+
if test (count $cmd) -eq 2
|
|
195
|
+
return 0
|
|
196
|
+
end
|
|
197
|
+
return 1
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
complete -c bingo-light -n __bingo_light_dep_needs_subcommand -a patch -d 'Patch a modified dependency'
|
|
201
|
+
complete -c bingo-light -n __bingo_light_dep_needs_subcommand -a apply -d 'Re-apply patches after install'
|
|
202
|
+
complete -c bingo-light -n __bingo_light_dep_needs_subcommand -a sync -d 'Re-apply after update, detect conflicts'
|
|
203
|
+
complete -c bingo-light -n __bingo_light_dep_needs_subcommand -a status -d 'Show patch health'
|
|
204
|
+
complete -c bingo-light -n __bingo_light_dep_needs_subcommand -a list -d 'List all dependency patches'
|
|
205
|
+
complete -c bingo-light -n __bingo_light_dep_needs_subcommand -a drop -d 'Remove a dependency patch'
|
|
206
|
+
complete -c bingo-light -n __bingo_light_dep_needs_subcommand -a override -d 'Manage npm overrides/resolutions'
|
|
207
|
+
complete -c bingo-light -n __bingo_light_dep_needs_subcommand -a fork -d 'Track fork-as-dependency drift'
|
|
165
208
|
|
|
166
209
|
# ---- workspace subcommands (also alias "ws") ----
|
|
167
210
|
|
|
@@ -195,3 +238,5 @@ complete -c bingo-light -n '__bingo_light_using_command test' -s h -
|
|
|
195
238
|
complete -c bingo-light -n '__bingo_light_using_command workspace ws' -s h -l help -d 'Show help'
|
|
196
239
|
complete -c bingo-light -n '__bingo_light_using_command smart-sync' -s h -l help -d 'Show help'
|
|
197
240
|
complete -c bingo-light -n '__bingo_light_using_command session' -s h -l help -d 'Show help'
|
|
241
|
+
complete -c bingo-light -n '__bingo_light_using_command report' -s h -l help -d 'Show help'
|
|
242
|
+
complete -c bingo-light -n '__bingo_light_using_command doctor' -l report -d 'Include team, expiry, and dep checks'
|
|
@@ -11,6 +11,7 @@ _bingo-light() {
|
|
|
11
11
|
local -a toplevel_commands=(
|
|
12
12
|
'init:Initialize a new bingo-light project'
|
|
13
13
|
'patch:Manage patches'
|
|
14
|
+
'dep:Patch npm/pip dependencies'
|
|
14
15
|
'setup:Configure MCP for AI tools (interactive)'
|
|
15
16
|
'sync:Synchronize changes with upstream'
|
|
16
17
|
'status:Show current status'
|
|
@@ -29,6 +30,7 @@ _bingo-light() {
|
|
|
29
30
|
'workspace:Manage multiple forks'
|
|
30
31
|
'smart-sync:Smart sync with circuit breaker and partial state'
|
|
31
32
|
'session:Manage session memory'
|
|
33
|
+
'report:Generate fork health report'
|
|
32
34
|
)
|
|
33
35
|
|
|
34
36
|
local -a toplevel_aliases=(
|
|
@@ -50,6 +52,12 @@ _bingo-light() {
|
|
|
50
52
|
'reorder:Reorder the patch stack'
|
|
51
53
|
'squash:Squash two patches into one'
|
|
52
54
|
'meta:Get/set patch metadata'
|
|
55
|
+
'lock:Lock a patch for exclusive editing'
|
|
56
|
+
'unlock:Unlock a patch'
|
|
57
|
+
'check:Check if patches are still needed'
|
|
58
|
+
'upstream:Export patch as PR-ready diff'
|
|
59
|
+
'expire:List expired patches'
|
|
60
|
+
'stats:Show patch health metrics'
|
|
53
61
|
)
|
|
54
62
|
|
|
55
63
|
local -a patch_aliases=(
|
|
@@ -120,7 +128,7 @@ _bingo-light() {
|
|
|
120
128
|
list|ls)
|
|
121
129
|
_arguments $patch_list_flags
|
|
122
130
|
;;
|
|
123
|
-
new|add|create|show|edit|drop|rm|remove|export|import|reorder|squash|meta)
|
|
131
|
+
new|add|create|show|edit|drop|rm|remove|export|import|reorder|squash|meta|lock|unlock|check|upstream|expire|stats)
|
|
124
132
|
_arguments $help_flag
|
|
125
133
|
;;
|
|
126
134
|
esac
|
|
@@ -136,6 +144,27 @@ _bingo-light() {
|
|
|
136
144
|
diff|d)
|
|
137
145
|
_arguments $help_flag
|
|
138
146
|
;;
|
|
147
|
+
dep)
|
|
148
|
+
local -a dep_subcommands=(
|
|
149
|
+
'patch:Patch a modified dependency'
|
|
150
|
+
'apply:Re-apply patches after install'
|
|
151
|
+
'sync:Re-apply after update, detect conflicts'
|
|
152
|
+
'status:Show patch health'
|
|
153
|
+
'list:List all dependency patches'
|
|
154
|
+
'drop:Remove a dependency patch'
|
|
155
|
+
'override:Manage npm overrides/resolutions'
|
|
156
|
+
'fork:Track fork-as-dependency drift'
|
|
157
|
+
)
|
|
158
|
+
_arguments -C \
|
|
159
|
+
'(- *)'{-h,--help}'[Show help]' \
|
|
160
|
+
'1:subcommand:->dep_subcmd' && return
|
|
161
|
+
|
|
162
|
+
case $state in
|
|
163
|
+
dep_subcmd)
|
|
164
|
+
_describe -t subcommands 'dep subcommand' dep_subcommands
|
|
165
|
+
;;
|
|
166
|
+
esac
|
|
167
|
+
;;
|
|
139
168
|
workspace|ws)
|
|
140
169
|
local -a ws_subcommands=(
|
|
141
170
|
'init:Initialize workspace'
|
|
@@ -155,7 +184,14 @@ _bingo-light() {
|
|
|
155
184
|
;;
|
|
156
185
|
esac
|
|
157
186
|
;;
|
|
158
|
-
|
|
187
|
+
conflict-resolve)
|
|
188
|
+
_arguments \
|
|
189
|
+
'--verify[Run test.command after the final rebase continues]' \
|
|
190
|
+
'--content-stdin[Read resolved content from stdin]' \
|
|
191
|
+
$help_flag \
|
|
192
|
+
'*:file:_files'
|
|
193
|
+
;;
|
|
194
|
+
init|setup|doctor|auto-sync|log|undo|version|conflict-analyze|config|history|test|smart-sync|session|report)
|
|
159
195
|
_arguments $help_flag
|
|
160
196
|
;;
|
|
161
197
|
help)
|