[object Object]

← back to Chief Of Operations

auto-claim one DW-priority open ticket

585906585091c1da01abf0a552d19ced40c5012f · 2026-09-17 09:01:07 -0700 · Steve Abrams

Files touched

Diff

commit 585906585091c1da01abf0a552d19ced40c5012f
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Sep 17 09:01:07 2026 -0700

    auto-claim one DW-priority open ticket
---
 README.md          |  3 ++-
 chief_ops.py       | 29 +++++++++++++++++++++++++++--
 supervisor_loop.sh |  1 +
 3 files changed, 30 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index 8e2b9fe..eac96c6 100644
--- a/README.md
+++ b/README.md
@@ -14,8 +14,9 @@ The Arrange action delegates to the existing master router through the desktop b
 
 ## Continuous ticket supervision
 
-`supervisor_loop.sh` refreshes `state/queue.json` every 60 seconds under launchd (`com.steve.chief-of-operations`). The queue includes open, blocked, and doing tickets, scores Designer Wallcoverings/client-impact work first, and records the next safe step. It does not auto-claim tickets or bypass approval gates.
+`supervisor_loop.sh` refreshes `state/queue.json` every 60 seconds under launchd (`com.steve.chief-of-operations`). The queue includes open, blocked, and doing tickets, scores Designer Wallcoverings/client-impact work first, and records the next safe step. It automatically claims one highest-priority open ticket at a time as `chief-of-operations`; blocked tickets remain surfaced for blocker review.
 
 ```sh
 ./chief_ops.py queue
+./chief_ops.py claim
 ```
diff --git a/chief_ops.py b/chief_ops.py
index 2df3443..c9ea583 100755
--- a/chief_ops.py
+++ b/chief_ops.py
@@ -9,7 +9,9 @@ HOME = Path.home()
 ROUTER = HOME / '.claude/skills/dot-screen-router'
 DOTBAR = HOME / 'Projects/desktop-dotbar'
 TICKET_LIB = HOME / 'Projects/ticket-system/lib.js'
+TK = HOME / 'Projects/ticket-system/tk'
 STATE = Path(__file__).with_name('state')
+OPS_AGENT = 'chief-of-operations'
 STATUS = HOME / '.local/state/abrams-terminal-status/ttys016.json'
 TICKET = 'TK-11882-chief-of-operations-harness-for-terminal'
 SCREENSHOTS = [HOME/'Desktop/arrange-click-screen1.png', HOME/'Desktop/arrange-click-screen2.png']
@@ -101,6 +103,28 @@ def ticket_queue(write=False):
         STATE.mkdir(exist_ok=True); (STATE/'queue.json').write_text(json.dumps(result,indent=2)+'\n')
     return result
 
+def auto_claim():
+    q=ticket_queue(write=True)
+    if not q.get('ok'): return {'ok':False,'error':q.get('error','queue unavailable')}
+    claimed_path=STATE/'claimed.json'
+    current=None
+    if claimed_path.exists():
+        try: current=json.loads(claimed_path.read_text()).get('ticket')
+        except Exception: current=None
+    if current:
+        active=next((x for x in q['items'] if x['id']==current), None)
+        if active and active.get('status') in ('open','doing') and active.get('assignee')==OPS_AGENT:
+            return {'ok':True,'action':'hold','ticket':current,'reason':'already claimed and active'}
+    candidate=next((x for x in q['items'] if x.get('status')=='open'), None)
+    if not candidate:
+        return {'ok':True,'action':'none','reason':'no open ticket; blocked work remains surfaced','blocked_count':q.get('blocked_count',0)}
+    rc,out,err=cmd([str(TK),'take',candidate['id'],'-a',OPS_AGENT], timeout=10)
+    if rc:
+        return {'ok':False,'action':'claim-failed','ticket':candidate['id'],'error':err or out}
+    STATE.mkdir(exist_ok=True)
+    claimed_path.write_text(json.dumps({'ticket':candidate['id'],'claimed_at':time.strftime('%Y-%m-%dT%H:%M:%SZ',time.gmtime()),'title':candidate['title']},indent=2)+'\n')
+    return {'ok':True,'action':'claimed','ticket':candidate['id'],'title':candidate['title'],'score':candidate['score']}
+
 
 def check():
     status={}
@@ -152,9 +176,10 @@ class Handler(BaseHTTPRequestHandler):
         b=json.dumps(data, indent=2).encode(); self.send_response(200); self.send_header('Content-Type','application/json'); self.send_header('Content-Length',str(len(b))); self.end_headers(); self.wfile.write(b)
 
 def main():
-    ap=argparse.ArgumentParser(); ap.add_argument('action', choices=['check','arrange','queue','serve'], nargs='?', default='check'); ap.add_argument('--port',type=int,default=9896); ap.add_argument('--write',action='store_true'); a=ap.parse_args()
+    ap=argparse.ArgumentParser(); ap.add_argument('action', choices=['check','arrange','queue','claim','serve'], nargs='?', default='check'); ap.add_argument('--port',type=int,default=9896); ap.add_argument('--write',action='store_true'); a=ap.parse_args()
     if a.action=='check': print(json.dumps(check(), indent=2)); return
     if a.action=='arrange': print(json.dumps(arrange(), indent=2)); return
-    if a.action=='queue': print(json.dumps(ticket_queue(write=a.write or True), indent=2)); return
+    if a.action=='queue': print(json.dumps(ticket_queue(write=True), indent=2)); return
+    if a.action=='claim': print(json.dumps(auto_claim(), indent=2)); return
     print(f'Chief of Operations on http://127.0.0.1:{a.port}', flush=True); ThreadingHTTPServer(('127.0.0.1',a.port), Handler).serve_forever()
 if __name__=='__main__': main()
diff --git a/supervisor_loop.sh b/supervisor_loop.sh
index 6060911..9f500e3 100755
--- a/supervisor_loop.sh
+++ b/supervisor_loop.sh
@@ -3,6 +3,7 @@ set -u
 cd "$(dirname "$0")"
 INTERVAL="${CHIEF_OPS_INTERVAL:-60}"
 while true; do
+  ./chief_ops.py claim >> state/supervisor.log 2>&1 || true
   ./chief_ops.py queue --write >> state/supervisor.log 2>&1 || true
   sleep "$INTERVAL"
 done

← 030f843 report supervisor launchd health  ·  back to Chief Of Operations  ·  monitor all configured exo hosts 777f586 →