beads: natural sort for hierarchical IDs
Sort bd-1.2 before bd-1.10 instead of lexicographic order.
This commit is contained in:
parent
d70162ad13
commit
daf50d9783
|
|
@ -5,6 +5,7 @@ filesystem:
|
|||
- "/etc/alternatives"
|
||||
- "/etc/ld.so.cache"
|
||||
- "/etc/passwd"
|
||||
- "{HOME}/.local/share/anvillm/mail"
|
||||
- "/dev/urandom"
|
||||
rox:
|
||||
- "/usr"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ filesystem:
|
|||
- "{XDG_CONFIG_HOME}/git"
|
||||
- "{HOME}/.ssh"
|
||||
- "{HOME}/.netrc"
|
||||
- "{HOME}/.local/share/anvillm/mail"
|
||||
- "/etc/gitconfig"
|
||||
- "/etc/gitattributes"
|
||||
- "/etc/ssh/ssh_config"
|
||||
|
|
|
|||
|
|
@ -28,6 +28,11 @@ const windowName = "/AnviLLM/"
|
|||
// Terminal to use for tmux attach (configurable via environment or flag)
|
||||
var terminalCommand = getTerminalCommand()
|
||||
|
||||
// shellEscape escapes a string for use inside single quotes in shell commands.
|
||||
func shellEscape(s string) string {
|
||||
return strings.ReplaceAll(s, "'", "'\\''")
|
||||
}
|
||||
|
||||
func getTerminalCommand() string {
|
||||
if term := os.Getenv("ANVILLM_TERMINAL"); term != "" {
|
||||
return term
|
||||
|
|
@ -2506,7 +2511,7 @@ func createBeadFromMarkdown(content, parentID string, mount string) error {
|
|||
}
|
||||
|
||||
// Build command: new 'title' 'description' [parent-id] [blockers=...]
|
||||
cmd := fmt.Sprintf("new '%s' '%s'", title, description)
|
||||
cmd := fmt.Sprintf("new '%s' '%s'", shellEscape(title), shellEscape(description))
|
||||
if parentID != "" {
|
||||
cmd = fmt.Sprintf("%s %s", cmd, parentID)
|
||||
}
|
||||
|
|
@ -2897,11 +2902,11 @@ func updateBead(beadID, content string, origBlockers []string, mount string) err
|
|||
|
||||
// Update title and description
|
||||
if title != "" {
|
||||
cmd := fmt.Sprintf("update %s title '%s'", beadID, title)
|
||||
cmd := fmt.Sprintf("update %s title '%s'", beadID, shellEscape(title))
|
||||
writeFile(filepath.Join("beads", mount, "ctl"), []byte(cmd))
|
||||
}
|
||||
if description != "" {
|
||||
cmd := fmt.Sprintf("update %s description '%s'", beadID, description)
|
||||
cmd := fmt.Sprintf("update %s description '%s'", beadID, shellEscape(description))
|
||||
writeFile(filepath.Join("beads", mount, "ctl"), []byte(cmd))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -308,7 +308,8 @@ func executeCode(code, language string, timeout int, sandboxName string, trusted
|
|||
return "", fmt.Errorf("unsupported language: %s (supported: bash)", language)
|
||||
}
|
||||
|
||||
// Limit output size (10MB)
|
||||
// Limit output size (10MB raw, 8KB returned)
|
||||
const maxToolOutputSize = 8000
|
||||
var outputBuf bytes.Buffer
|
||||
lw := &limitedWriter{w: &outputBuf, limit: 10 * 1024 * 1024}
|
||||
cmd.Stdout = lw
|
||||
|
|
@ -350,7 +351,11 @@ func executeCode(code, language string, timeout int, sandboxName string, trusted
|
|||
}
|
||||
|
||||
logExecution(execLog)
|
||||
return string(output), nil
|
||||
result := string(output)
|
||||
if len(result) > maxToolOutputSize {
|
||||
result = result[:maxToolOutputSize] + "\n... (output truncated)"
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type limitedWriter struct {
|
||||
|
|
|
|||
|
|
@ -472,7 +472,7 @@ func (b *BeadsFS) readListFromStore(store bd.Storage, limit int) ([]byte, error)
|
|||
}
|
||||
// Sort by ID for hierarchical view (bd-a3f8, bd-a3f8.1, bd-a3f8.1.1, etc.)
|
||||
sort.Slice(issues, func(i, j int) bool {
|
||||
return issues[i].ID < issues[j].ID
|
||||
return naturalLess(issues[i].ID, issues[j].ID)
|
||||
})
|
||||
|
||||
// Enrich with blockers, labels, and capability level.
|
||||
|
|
@ -510,6 +510,48 @@ func (b *BeadsFS) readListFromStore(store bd.Storage, limit int) ([]byte, error)
|
|||
return json.MarshalIndent(items, "", " ")
|
||||
}
|
||||
|
||||
// naturalLess compares two strings with natural number ordering.
|
||||
// "jira-100.2" < "jira-100.10" (unlike lexicographic where .10 < .2)
|
||||
func naturalLess(a, b string) bool {
|
||||
for {
|
||||
if a == "" {
|
||||
return b != ""
|
||||
}
|
||||
if b == "" {
|
||||
return false
|
||||
}
|
||||
// Find leading non-digit prefix
|
||||
ai, bi := 0, 0
|
||||
for ai < len(a) && (a[ai] < '0' || a[ai] > '9') {
|
||||
ai++
|
||||
}
|
||||
for bi < len(b) && (b[bi] < '0' || b[bi] > '9') {
|
||||
bi++
|
||||
}
|
||||
if a[:ai] != b[:bi] {
|
||||
return a[:ai] < b[:bi]
|
||||
}
|
||||
a, b = a[ai:], b[bi:]
|
||||
if a == "" || b == "" {
|
||||
continue
|
||||
}
|
||||
// Extract numeric part
|
||||
ai, bi = 0, 0
|
||||
for ai < len(a) && a[ai] >= '0' && a[ai] <= '9' {
|
||||
ai++
|
||||
}
|
||||
for bi < len(b) && b[bi] >= '0' && b[bi] <= '9' {
|
||||
bi++
|
||||
}
|
||||
na, _ := strconv.Atoi(a[:ai])
|
||||
nb, _ := strconv.Atoi(b[:bi])
|
||||
if na != nb {
|
||||
return na < nb
|
||||
}
|
||||
a, b = a[ai:], b[bi:]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func (b *BeadsFS) readReadyFromStore(store bd.Storage, limit int) ([]byte, error) {
|
||||
filter := bd.WorkFilter{Limit: limit}
|
||||
|
|
|
|||
4
mkfile
4
mkfile
|
|
@ -6,16 +6,12 @@ build:V:
|
|||
go build -o $INSTALL_PATH/anvilsrv ./cmd/anvilsrv
|
||||
go build -o $INSTALL_PATH/Assist ./cmd/Assist
|
||||
go build -o $INSTALL_PATH/anvilmcp ./cmd/anvilmcp
|
||||
cp scripts/Alog $INSTALL_PATH/Alog
|
||||
chmod 0755 $INSTALL_PATH/Alog
|
||||
cp scripts/anvillm-notify $INSTALL_PATH/anvillm-notify
|
||||
chmod 0755 $INSTALL_PATH/anvillm-notify
|
||||
cp scripts/anvillm-skills $INSTALL_PATH/anvillm-skills
|
||||
chmod 0755 $INSTALL_PATH/anvillm-skills
|
||||
cp scripts/anvillm-hook $INSTALL_PATH/anvillm-hook
|
||||
chmod 0755 $INSTALL_PATH/anvillm-hook
|
||||
cp scripts/9p-read-inbox $INSTALL_PATH/9p-read-inbox
|
||||
chmod 0755 $INSTALL_PATH/9p-read-inbox
|
||||
cp scripts/anvilspawn $INSTALL_PATH/anvilspawn
|
||||
chmod 0755 $INSTALL_PATH/anvilspawn
|
||||
cp scripts/anvillm-supervisor $INSTALL_PATH/anvillm-supervisor
|
||||
|
|
|
|||
Reference in New Issue