bingo-light 2.1.2 → 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.
@@ -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 dep 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
 
@@ -39,7 +39,7 @@ _bingo_light() {
39
39
  dep)
40
40
  cmd="dep"
41
41
  ;;
42
- init|setup|doctor|auto-sync|log|undo|version|help|conflict-analyze|conflict-resolve|config|history|test|workspace|ws|smart-sync|session)
42
+ init|setup|doctor|auto-sync|log|undo|version|help|conflict-analyze|conflict-resolve|config|history|test|workspace|ws|smart-sync|session|report)
43
43
  cmd="${words[i]}"
44
44
  ;;
45
45
  *)
@@ -94,10 +94,20 @@ _bingo_light() {
94
94
  return
95
95
  fi
96
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
+
97
103
  # Inside "dep" -- complete subcommands
98
104
  if [[ "$cmd" == "dep" ]]; then
99
105
  if [[ -z "$subcmd" ]]; then
100
- COMPREPLY=( $(compgen -W "patch apply sync status list drop" -- "$cur") )
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") )
101
111
  else
102
112
  COMPREPLY=( $(compgen -W "--help -h" -- "$cur") )
103
113
  fi
@@ -86,6 +86,7 @@ complete -c bingo-light -n __bingo_light_needs_command -a test -d 'Run config
86
86
  complete -c bingo-light -n __bingo_light_needs_command -a workspace -d 'Manage multiple forks'
87
87
  complete -c bingo-light -n __bingo_light_needs_command -a smart-sync -d 'Smart sync with circuit breaker and partial state'
88
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'
89
90
 
90
91
  # Short aliases
91
92
  complete -c bingo-light -n __bingo_light_needs_command -a p -d 'Alias for patch'
@@ -123,9 +124,11 @@ complete -c bingo-light -n '__bingo_light_using_command version' -s h -l help
123
124
 
124
125
  # ---- conflict-resolve flags ----
125
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'
126
129
 
127
130
  # ---- help: complete with command names ----
128
- 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' -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'
129
132
 
130
133
  # ---- patch subcommands (also alias "p") ----
131
134
  complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a new -d 'Create a new patch'
@@ -138,6 +141,12 @@ complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a import -d 'I
138
141
  complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a reorder -d 'Reorder the patch stack'
139
142
  complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a squash -d 'Squash two patches into one'
140
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'
141
150
 
142
151
  # Patch short aliases
143
152
  complete -c bingo-light -n __bingo_light_patch_needs_subcommand -a ls -d 'Alias for list'
@@ -163,6 +172,14 @@ complete -c bingo-light -n '__bingo_light_patch_using_subcommand import'
163
172
  complete -c bingo-light -n '__bingo_light_patch_using_subcommand reorder' -s h -l help -d 'Show help'
164
173
  complete -c bingo-light -n '__bingo_light_patch_using_subcommand squash' -s h -l help -d 'Show help'
165
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'
166
183
 
167
184
  # ---- dep subcommands ----
168
185
 
@@ -185,7 +202,9 @@ complete -c bingo-light -n __bingo_light_dep_needs_subcommand -a apply -d 'Re-a
185
202
  complete -c bingo-light -n __bingo_light_dep_needs_subcommand -a sync -d 'Re-apply after update, detect conflicts'
186
203
  complete -c bingo-light -n __bingo_light_dep_needs_subcommand -a status -d 'Show patch health'
187
204
  complete -c bingo-light -n __bingo_light_dep_needs_subcommand -a list -d 'List all dependency patches'
188
- complete -c bingo-light -n __bingo_light_dep_needs_subcommand -a drop -d 'Remove a dependency patch'
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'
189
208
 
190
209
  # ---- workspace subcommands (also alias "ws") ----
191
210
 
@@ -219,3 +238,5 @@ complete -c bingo-light -n '__bingo_light_using_command test' -s h -
219
238
  complete -c bingo-light -n '__bingo_light_using_command workspace ws' -s h -l help -d 'Show help'
220
239
  complete -c bingo-light -n '__bingo_light_using_command smart-sync' -s h -l help -d 'Show help'
221
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'
@@ -30,6 +30,7 @@ _bingo-light() {
30
30
  'workspace:Manage multiple forks'
31
31
  'smart-sync:Smart sync with circuit breaker and partial state'
32
32
  'session:Manage session memory'
33
+ 'report:Generate fork health report'
33
34
  )
34
35
 
35
36
  local -a toplevel_aliases=(
@@ -51,6 +52,12 @@ _bingo-light() {
51
52
  'reorder:Reorder the patch stack'
52
53
  'squash:Squash two patches into one'
53
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'
54
61
  )
55
62
 
56
63
  local -a patch_aliases=(
@@ -121,7 +128,7 @@ _bingo-light() {
121
128
  list|ls)
122
129
  _arguments $patch_list_flags
123
130
  ;;
124
- 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)
125
132
  _arguments $help_flag
126
133
  ;;
127
134
  esac
@@ -145,6 +152,8 @@ _bingo-light() {
145
152
  'status:Show patch health'
146
153
  'list:List all dependency patches'
147
154
  'drop:Remove a dependency patch'
155
+ 'override:Manage npm overrides/resolutions'
156
+ 'fork:Track fork-as-dependency drift'
148
157
  )
149
158
  _arguments -C \
150
159
  '(- *)'{-h,--help}'[Show help]' \
@@ -175,7 +184,14 @@ _bingo-light() {
175
184
  ;;
176
185
  esac
177
186
  ;;
178
- init|setup|doctor|auto-sync|log|undo|version|conflict-analyze|conflict-resolve|config|history|test|smart-sync|session)
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)
179
195
  _arguments $help_flag
180
196
  ;;
181
197
  help)
package/mcp-server.py CHANGED
@@ -231,7 +231,7 @@ TOOLS = [
231
231
  "name": "bingo_doctor",
232
232
  "description": (
233
233
  "Diagnose setup issues: checks git version, rerere, upstream remote, branch structure, "
234
- "and tests whether patches apply cleanly on latest upstream."
234
+ "and tests whether patches apply cleanly on latest upstream. Use report=true for extended checks."
235
235
  ),
236
236
  "inputSchema": {
237
237
  "type": "object",
@@ -239,6 +239,10 @@ TOOLS = [
239
239
  "cwd": {
240
240
  "type": "string",
241
241
  "description": "Path to the git repository"
242
+ },
243
+ "report": {
244
+ "type": "boolean",
245
+ "description": "Include extended checks (team locks, expiry, deps)"
242
246
  }
243
247
  },
244
248
  "required": ["cwd"]
@@ -283,8 +287,10 @@ TOOLS = [
283
287
  {
284
288
  "name": "bingo_conflict_analyze",
285
289
  "description": (
286
- "Analyze current rebase conflicts. Returns structured info about each conflicted file: "
287
- "the 'ours' version (upstream), 'theirs' version (your patch), conflict count, and resolution hints. "
290
+ "Analyze current rebase conflicts. Returns structured info about each conflicted file "
291
+ "(ours/theirs, conflict count, hints) plus patch_intent (name, subject, full commit "
292
+ "message, original_sha, original_diff, meta, stack_position) and verify "
293
+ "(test_command + per-file syntax/parse commands). "
288
294
  "Call this when bingo_sync reports a conflict to understand what needs fixing."
289
295
  ),
290
296
  "inputSchema": {
@@ -302,7 +308,9 @@ TOOLS = [
302
308
  "name": "bingo_conflict_resolve",
303
309
  "description": (
304
310
  "Resolve a conflict during rebase by writing the resolved content to a file, "
305
- "staging it, and continuing the rebase. Use after bingo_conflict_analyze."
311
+ "staging it, and continuing the rebase. Use after bingo_conflict_analyze. "
312
+ "Set verify=true to run test.command after the final rebase --continue; "
313
+ "the result is attached as verify_result."
306
314
  ),
307
315
  "inputSchema": {
308
316
  "type": "object",
@@ -318,6 +326,10 @@ TOOLS = [
318
326
  "content": {
319
327
  "type": "string",
320
328
  "description": "The fully resolved file content (no conflict markers)"
329
+ },
330
+ "verify": {
331
+ "type": "boolean",
332
+ "description": "If true, run test.command after the final rebase --continue. Default false."
321
333
  }
322
334
  },
323
335
  "required": ["cwd", "file", "content"]
@@ -549,6 +561,176 @@ TOOLS = [
549
561
  "required": ["cwd", "package"]
550
562
  }
551
563
  },
564
+ # ── Team / Locking tools ────────────────────────────────────────────
565
+ {
566
+ "name": "bingo_patch_lock",
567
+ "description": "Lock a patch for exclusive editing. Prevents other team members from editing or dropping it.",
568
+ "inputSchema": {
569
+ "type": "object",
570
+ "properties": {
571
+ "cwd": {"type": "string", "description": "Path to the git repository"},
572
+ "name": {"type": "string", "description": "Patch name to lock"},
573
+ "reason": {"type": "string", "description": "Why you are locking this patch"}
574
+ },
575
+ "required": ["cwd", "name"]
576
+ }
577
+ },
578
+ {
579
+ "name": "bingo_patch_unlock",
580
+ "description": "Unlock a patch, allowing other team members to edit or drop it.",
581
+ "inputSchema": {
582
+ "type": "object",
583
+ "properties": {
584
+ "cwd": {"type": "string", "description": "Path to the git repository"},
585
+ "name": {"type": "string", "description": "Patch name to unlock"},
586
+ "force": {"type": "boolean", "description": "Force unlock even if locked by someone else"}
587
+ },
588
+ "required": ["cwd", "name"]
589
+ }
590
+ },
591
+ # ── Smart Patch Management tools ────────────────────────────────────
592
+ {
593
+ "name": "bingo_patch_check",
594
+ "description": "Check if patches are still needed. Detects if upstream has merged equivalent changes, making a patch obsolete.",
595
+ "inputSchema": {
596
+ "type": "object",
597
+ "properties": {
598
+ "cwd": {"type": "string", "description": "Path to the git repository"},
599
+ "name": {"type": "string", "description": "Patch name to check (omit for all patches)"}
600
+ },
601
+ "required": ["cwd"]
602
+ }
603
+ },
604
+ {
605
+ "name": "bingo_patch_upstream",
606
+ "description": "Export a patch as a clean PR-ready diff suitable for submitting to the upstream repository.",
607
+ "inputSchema": {
608
+ "type": "object",
609
+ "properties": {
610
+ "cwd": {"type": "string", "description": "Path to the git repository"},
611
+ "name": {"type": "string", "description": "Patch name to export"}
612
+ },
613
+ "required": ["cwd", "name"]
614
+ }
615
+ },
616
+ {
617
+ "name": "bingo_patch_expire",
618
+ "description": "List patches that have passed their expiry date or are expiring soon (within 7 days).",
619
+ "inputSchema": {
620
+ "type": "object",
621
+ "properties": {
622
+ "cwd": {"type": "string", "description": "Path to the git repository"}
623
+ },
624
+ "required": ["cwd"]
625
+ }
626
+ },
627
+ {
628
+ "name": "bingo_patch_stats",
629
+ "description": "Get health metrics for all patches: age, size, conflict frequency.",
630
+ "inputSchema": {
631
+ "type": "object",
632
+ "properties": {
633
+ "cwd": {"type": "string", "description": "Path to the git repository"}
634
+ },
635
+ "required": ["cwd"]
636
+ }
637
+ },
638
+ # ── Report tool ─────────────────────────────────────────────────────
639
+ {
640
+ "name": "bingo_report",
641
+ "description": "Generate a comprehensive markdown health report covering patches, sync status, team locks, expiry, and dependencies.",
642
+ "inputSchema": {
643
+ "type": "object",
644
+ "properties": {
645
+ "cwd": {"type": "string", "description": "Path to the git repository"}
646
+ },
647
+ "required": ["cwd"]
648
+ }
649
+ },
650
+ # ── npm Override Management tools ───────────────────────────────────
651
+ {
652
+ "name": "bingo_dep_override_list",
653
+ "description": "List all npm overrides/yarn resolutions with tracked reasons.",
654
+ "inputSchema": {
655
+ "type": "object",
656
+ "properties": {
657
+ "cwd": {"type": "string", "description": "Project directory"}
658
+ },
659
+ "required": ["cwd"]
660
+ }
661
+ },
662
+ {
663
+ "name": "bingo_dep_override_check",
664
+ "description": "Check if npm overrides are still needed by comparing against package-lock.json resolved versions.",
665
+ "inputSchema": {
666
+ "type": "object",
667
+ "properties": {
668
+ "cwd": {"type": "string", "description": "Project directory"}
669
+ },
670
+ "required": ["cwd"]
671
+ }
672
+ },
673
+ {
674
+ "name": "bingo_dep_override_add",
675
+ "description": "Add an npm override to package.json with reason tracking.",
676
+ "inputSchema": {
677
+ "type": "object",
678
+ "properties": {
679
+ "cwd": {"type": "string", "description": "Project directory"},
680
+ "package": {"type": "string", "description": "Package to override"},
681
+ "version": {"type": "string", "description": "Version to force"},
682
+ "reason": {"type": "string", "description": "Why this override exists"}
683
+ },
684
+ "required": ["cwd", "package", "version"]
685
+ }
686
+ },
687
+ {
688
+ "name": "bingo_dep_override_drop",
689
+ "description": "Remove an npm override from package.json and tracking.",
690
+ "inputSchema": {
691
+ "type": "object",
692
+ "properties": {
693
+ "cwd": {"type": "string", "description": "Project directory"},
694
+ "package": {"type": "string", "description": "Package to remove override for"}
695
+ },
696
+ "required": ["cwd", "package"]
697
+ }
698
+ },
699
+ # ── Fork-as-Dependency Tracking tools ───────────────────────────────
700
+ {
701
+ "name": "bingo_dep_fork_list",
702
+ "description": "List all git-based dependencies in package.json (github:user/repo, git+https://, etc.).",
703
+ "inputSchema": {
704
+ "type": "object",
705
+ "properties": {
706
+ "cwd": {"type": "string", "description": "Project directory"}
707
+ },
708
+ "required": ["cwd"]
709
+ }
710
+ },
711
+ {
712
+ "name": "bingo_dep_fork_check",
713
+ "description": "Check fork drift: compare git dependency refs against latest npm releases and GitHub commits.",
714
+ "inputSchema": {
715
+ "type": "object",
716
+ "properties": {
717
+ "cwd": {"type": "string", "description": "Project directory"}
718
+ },
719
+ "required": ["cwd"]
720
+ }
721
+ },
722
+ {
723
+ "name": "bingo_dep_fork_sync",
724
+ "description": "Update a fork dependency ref in package.json to the latest GitHub commit.",
725
+ "inputSchema": {
726
+ "type": "object",
727
+ "properties": {
728
+ "cwd": {"type": "string", "description": "Project directory"},
729
+ "package": {"type": "string", "description": "Package to update"}
730
+ },
731
+ "required": ["cwd", "package"]
732
+ }
733
+ },
552
734
  ]
553
735
 
554
736
  # ─── Command Mapping ──────────────────────────────────────────────────────────
@@ -600,7 +782,7 @@ def handle_tool_call(name: str, arguments: dict) -> dict:
600
782
  return _result(repo.undo())
601
783
 
602
784
  elif name == "bingo_doctor":
603
- return _result(repo.doctor())
785
+ return _result(repo.doctor(report=arguments.get("report", False)))
604
786
 
605
787
  elif name == "bingo_diff":
606
788
  return _result(repo.diff())
@@ -615,6 +797,7 @@ def handle_tool_call(name: str, arguments: dict) -> dict:
615
797
  return _result(repo.conflict_resolve(
616
798
  arguments.get("file", ""),
617
799
  arguments.get("content", ""),
800
+ verify=bool(arguments.get("verify", False)),
618
801
  ))
619
802
 
620
803
  elif name == "bingo_log":
@@ -709,6 +892,36 @@ def handle_tool_call(name: str, arguments: dict) -> dict:
709
892
  elif name == "bingo_workspace_status":
710
893
  return _result(repo.workspace_status())
711
894
 
895
+ # ── Team / Locking tools ──────────────────────────────────────────
896
+ elif name == "bingo_patch_lock":
897
+ return _result(repo.patch_lock(
898
+ arguments["name"],
899
+ reason=arguments.get("reason", ""),
900
+ ))
901
+
902
+ elif name == "bingo_patch_unlock":
903
+ return _result(repo.patch_unlock(
904
+ arguments["name"],
905
+ force=arguments.get("force", False),
906
+ ))
907
+
908
+ # ── Smart Patch Management tools ──────────────────────────────────
909
+ elif name == "bingo_patch_check":
910
+ return _result(repo.patch_check(arguments.get("name", "")))
911
+
912
+ elif name == "bingo_patch_upstream":
913
+ return _result(repo.patch_upstream(arguments["name"]))
914
+
915
+ elif name == "bingo_patch_expire":
916
+ return _result(repo.patch_expire())
917
+
918
+ elif name == "bingo_patch_stats":
919
+ return _result(repo.patch_stats())
920
+
921
+ # ── Report tool ───────────────────────────────────────────────────
922
+ elif name == "bingo_report":
923
+ return _result(repo.report())
924
+
712
925
  # ── Dependency patching tools ────────────────────────────────────
713
926
  elif name.startswith("bingo_dep_"):
714
927
  from bingo_core.dep import DepManager
@@ -733,6 +946,28 @@ def handle_tool_call(name: str, arguments: dict) -> dict:
733
946
  arguments["package"],
734
947
  arguments.get("patch_name", ""),
735
948
  ))
949
+ # Override management
950
+ elif name == "bingo_dep_override_list":
951
+ return _result(dm.override_list())
952
+ elif name == "bingo_dep_override_check":
953
+ return _result(dm.override_check())
954
+ elif name == "bingo_dep_override_add":
955
+ return _result(dm.override_add(
956
+ arguments["package"], arguments["version"],
957
+ arguments.get("reason", ""),
958
+ ))
959
+ elif name == "bingo_dep_override_drop":
960
+ return _result(dm.override_drop(arguments["package"]))
961
+ # Fork tracking
962
+ elif name.startswith("bingo_dep_fork_"):
963
+ from bingo_core.dep_fork import ForkTracker
964
+ ft = ForkTracker(cwd)
965
+ if name == "bingo_dep_fork_list":
966
+ return _result(ft.fork_list())
967
+ elif name == "bingo_dep_fork_check":
968
+ return _result(ft.fork_check())
969
+ elif name == "bingo_dep_fork_sync":
970
+ return _result(ft.fork_sync(arguments["package"]))
736
971
 
737
972
  else:
738
973
  return {
@@ -859,7 +1094,7 @@ def main():
859
1094
  "capabilities": {"tools": {}},
860
1095
  "serverInfo": {
861
1096
  "name": "bingo-light",
862
- "version": "2.1.2",
1097
+ "version": "2.1.3",
863
1098
  },
864
1099
  }))
865
1100