beads: add atomic ctl operations; replace update_bead.sh

Add to 9p ctl handler: open, defer [until <RFC3339>], reopen, relate,
unrelate, config. Add corresponding mcptools scripts. Remove
update_bead.sh in favour of atomic operation scripts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Levi Neely 2026-03-14 13:14:39 +01:00
parent 5dbd4cf777
commit e55742d691
8 changed files with 183 additions and 25 deletions

View File

@ -988,8 +988,73 @@ func (b *BeadsFS) executeCtlOnStore(store bd.Storage, cmd string) error {
}
return store.UpdateIssue(b.ctx, args[0], updates, actor)
case "open":
// Promote a deferred bead to open (ready for scheduling).
// Usage: open <bead-id>
if len(args) < 1 {
return fmt.Errorf("usage: open <bead-id>")
}
return store.UpdateIssue(b.ctx, args[0], map[string]any{"status": bd.StatusOpen}, actor)
case "defer":
// Defer a bead, optionally until a specific time (RFC3339).
// Usage: defer <bead-id> [until <RFC3339-time>]
if len(args) < 1 {
return fmt.Errorf("usage: defer <bead-id> [until <RFC3339-time>]")
}
updates := map[string]any{"status": bd.StatusDeferred}
if len(args) >= 3 && args[1] == "until" {
t, err := time.Parse(time.RFC3339, args[2])
if err != nil {
return fmt.Errorf("invalid time %q: expected RFC3339 format", args[2])
}
updates["defer_until"] = t
}
return store.UpdateIssue(b.ctx, args[0], updates, actor)
case "reopen":
// Reopen a closed bead (reset to open, clear closed_at).
// Usage: reopen <bead-id>
if len(args) < 1 {
return fmt.Errorf("usage: reopen <bead-id>")
}
updates := map[string]any{
"status": bd.StatusOpen,
"closed_at": (*time.Time)(nil),
}
return store.UpdateIssue(b.ctx, args[0], updates, actor)
case "relate":
// Add a bidirectional relates-to dependency between two beads.
// Usage: relate <bead-id-1> <bead-id-2>
if len(args) < 2 {
return fmt.Errorf("usage: relate <bead-id-1> <bead-id-2>")
}
dep := &bd.Dependency{
IssueID: args[0],
DependsOnID: args[1],
Type: bd.DepRelated,
}
return store.AddDependency(b.ctx, dep, actor)
case "unrelate":
// Remove a relates-to dependency between two beads.
// Usage: unrelate <bead-id-1> <bead-id-2>
if len(args) < 2 {
return fmt.Errorf("usage: unrelate <bead-id-1> <bead-id-2>")
}
return store.RemoveDependency(b.ctx, args[0], args[1], actor)
case "config":
// Set a beads configuration key.
// Usage: config <key> <value>
if len(args) < 2 {
return fmt.Errorf("usage: config <key> <value>")
}
return store.SetConfig(b.ctx, args[0], args[1])
default:
return fmt.Errorf("unknown command: %s (supported: init, new, update, delete, claim, unclaim, complete, fail, dep, undep, pending-approval, pending-review, resume, label, unlabel, set-capability)", command)
return fmt.Errorf("unknown command: %s (supported: init, new, update, delete, claim, unclaim, open, defer, reopen, complete, fail, dep, undep, relate, unrelate, pending-approval, pending-review, resume, label, unlabel, set-capability, config)", command)
}
}

18
mcptools/config_beads.sh Executable file
View File

@ -0,0 +1,18 @@
#!/bin/bash
# capabilities: beads
# description: Set a beads database configuration key
# Usage: config_beads.sh <mount> <key> <value>
set -euo pipefail
if cat /etc/shadow >/dev/null 2>&1; then
echo "Error: This script must be run via execute_code tool" >&2
exit 1
fi
if [ $# -lt 3 ]; then
echo "usage: config_beads.sh <mount> <key> <value>" >&2
exit 1
fi
echo "config $2 $3" | 9p write anvillm/beads/$1/ctl
echo "config $2 = $3"

27
mcptools/defer_bead.sh Executable file
View File

@ -0,0 +1,27 @@
#!/bin/bash
# capabilities: beads
# description: Defer a bead, optionally until a specific time
# Usage: defer_bead.sh <mount> <bead-id> [until <RFC3339-time>]
set -euo pipefail
if cat /etc/shadow >/dev/null 2>&1; then
echo "Error: This script must be run via execute_code tool" >&2
exit 1
fi
if [ $# -lt 2 ]; then
echo "usage: defer_bead.sh <mount> <bead-id> [until <RFC3339-time>]" >&2
exit 1
fi
MOUNT="$1"
BEAD_ID="$2"
shift 2
if [ $# -ge 2 ] && [ "$1" = "until" ]; then
echo "defer $BEAD_ID until $2" | 9p write anvillm/beads/$MOUNT/ctl
echo "deferred $BEAD_ID until $2"
else
echo "defer $BEAD_ID" | 9p write anvillm/beads/$MOUNT/ctl
echo "deferred $BEAD_ID"
fi

18
mcptools/open_bead.sh Executable file
View File

@ -0,0 +1,18 @@
#!/bin/bash
# capabilities: beads
# description: Promote a deferred bead to open (ready for scheduling)
# Usage: open_bead.sh <mount> <bead-id>
set -euo pipefail
if cat /etc/shadow >/dev/null 2>&1; then
echo "Error: This script must be run via execute_code tool" >&2
exit 1
fi
if [ $# -lt 2 ]; then
echo "usage: open_bead.sh <mount> <bead-id>" >&2
exit 1
fi
echo "open $2" | 9p write anvillm/beads/$1/ctl
echo "opened $2"

18
mcptools/relate_beads.sh Executable file
View File

@ -0,0 +1,18 @@
#!/bin/bash
# capabilities: beads
# description: Add a relates-to link between two beads
# Usage: relate_beads.sh <mount> <bead-id-1> <bead-id-2>
set -euo pipefail
if cat /etc/shadow >/dev/null 2>&1; then
echo "Error: This script must be run via execute_code tool" >&2
exit 1
fi
if [ $# -lt 3 ]; then
echo "usage: relate_beads.sh <mount> <bead-id-1> <bead-id-2>" >&2
exit 1
fi
echo "relate $2 $3" | 9p write anvillm/beads/$1/ctl
echo "related $2 ↔ $3"

18
mcptools/reopen_bead.sh Executable file
View File

@ -0,0 +1,18 @@
#!/bin/bash
# capabilities: beads
# description: Reopen a closed bead (reset to open)
# Usage: reopen_bead.sh <mount> <bead-id>
set -euo pipefail
if cat /etc/shadow >/dev/null 2>&1; then
echo "Error: This script must be run via execute_code tool" >&2
exit 1
fi
if [ $# -lt 2 ]; then
echo "usage: reopen_bead.sh <mount> <bead-id>" >&2
exit 1
fi
echo "reopen $2" | 9p write anvillm/beads/$1/ctl
echo "reopened $2"

18
mcptools/unrelate_beads.sh Executable file
View File

@ -0,0 +1,18 @@
#!/bin/bash
# capabilities: beads
# description: Remove a relates-to link between two beads
# Usage: unrelate_beads.sh <mount> <bead-id-1> <bead-id-2>
set -euo pipefail
if cat /etc/shadow >/dev/null 2>&1; then
echo "Error: This script must be run via execute_code tool" >&2
exit 1
fi
if [ $# -lt 3 ]; then
echo "usage: unrelate_beads.sh <mount> <bead-id-1> <bead-id-2>" >&2
exit 1
fi
echo "unrelate $2 $3" | 9p write anvillm/beads/$1/ctl
echo "unrelated $2 ↔ $3"

View File

@ -1,24 +0,0 @@
#!/bin/bash
# capabilities: beads
# description: Update a bead field
# Usage: update_bead.sh <mount> <bead-id> <field> <value>
set -euo pipefail
if cat /etc/shadow >/dev/null 2>&1; then
echo "Error: This script must be run via execute_code tool" >&2
exit 1
fi
if [ $# -lt 4 ]; then
echo "usage: update_bead.sh <mount> <bead-id> <field> <value>" >&2
exit 1
fi
MOUNT="$1"
BEAD_ID="$2"
FIELD="$3"
shift 3
VALUE="$*"
printf "update %s %s '%s'\n" "$BEAD_ID" "$FIELD" "$VALUE" | 9p write anvillm/beads/$MOUNT/ctl
echo "updated $BEAD_ID.$FIELD: $VALUE"