dirge-agent 0.5.1

Minimalistic coding agent written in Rust, optimized for memory footprint and performance
# dap-watch — expression watchpoints with auto-refresh via DAP Janet FFI
#
# At every tool-end event, checks if a DAP session is active and
# then evaluates all registered watch expressions via `dap/eval`.
# Prints results inline as notifications.
#
# Usage:
#   /dap-watch add counter.value      Start watching an expression
#   /dap-watch add "x + y"
#   /dap-watch add "len(items)"
#   /dap-watch list                   List all watches
#   /dap-watch remove 0               Remove watch #0
#   /dap-watch clear                  Remove all watches
#   /dap-watch once "x"               One-shot eval, no register

(def hooks ["on-tool-end"])

# ── State ────────────────────────────────────────────────────────────

(var watch-exprs @[])  # list of expression strings

# ── Hook — evaluate watches on tool-end ──────────────────────────────

(defn on-tool-end [ctx]
  (when (and (dap/session-active?) (not (empty? watch-exprs)))
    (def status-str (dap/sessions))
    (when (and status-str (string/find "\"stopped\"" status-str))
      (var output "")
      (for i 0 (length watch-exprs)
        (def expr (get watch-exprs i))
        (def val (dap/eval expr))
        (if val
          (set output (string output "[" i "] " expr " = " val "\n"))
          (set output (string output "[" i "] " expr " = <failed>\n"))))
      (when (not (empty? output))
        (harness/notify (string "WATCH:\n" output) :info)))))

# ── Slash commands ──────────────────────────────────────────────────

(defn watch-cmd [args]
  (def parts (string/split " " args))
  (def sub (get parts 0))
  (def rest (string/join (array/slice parts 1) " "))

  (match sub
    "add" (do
      (when (empty? rest)
        (break "usage: /dap-watch add <expression>"))
      (array/push watch-exprs rest)
      (string "added watch #" (- (length watch-exprs) 1) ": " rest))

    "remove" (do
      (when (empty? rest)
        (break "usage: /dap-watch remove <index>"))
      (def idx (math/parse-int rest))
      (if (or (< idx 0) (>= idx (length watch-exprs)))
        (break (string "invalid index " idx " (0.." (- (length watch-exprs) 1) ")")))
      (def removed (get watch-exprs idx))
      (set watch-exprs (array/remove watch-exprs idx))
      (string "removed: " removed))

    "list" (do
      (if (empty? watch-exprs)
        "no watches"
        (do
          (var out "watches:\n")
          (for i 0 (length watch-exprs)
            (set out (string out "  [" i "] " (get watch-exprs i) "\n")))
          out)))

    "clear" (do
      (set watch-exprs @[])
      "all watches cleared")

    "once" (do
      (when (empty? rest)
        (break "usage: /dap-watch once <expression>"))
      (if (not (dap/session-active?))
        (break "no active DAP session"))
      (def val (dap/eval rest))
      (or val "eval failed"))

    (string "unknown: " sub " — try add, remove, list, clear, once")))

(harness/register-command "dap-watch" "watch-cmd")