feat: devops plugin, CLI commands, Codex dispatch fixes, AX sweep
DevOps plugin (5 skills):
- install-core-agent, repair-core-agent, merge-workspace,
update-deps, clean-workspaces
CLI commands: version, check, extract for diagnostics.
Codex dispatch: --skip-git-repo-check, removed broken
--model-reasoning-effort, --sandbox workspace-write via
--full-auto. Workspace template extracts to wsDir not srcDir.
AX sweep (Codex-generated): sanitise.go extracted from prep/plan,
mirror.go JSON parsing via encoding/json, setup/config.go URL
parsing via net/url, strings/fmt imports eliminated from setup.
CODEX.md template updated with Env/Path patterns.
Review workspace template with audit-only PROMPT.md.
Marketplace updated with devops plugin.
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 13:30:27 +00:00
|
|
|
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
|
|
|
|
|
|
package agentic
|
|
|
|
|
|
2026-04-02 08:58:17 +00:00
|
|
|
import core "dappco.re/go/core"
|
|
|
|
|
|
|
|
|
|
func validateName(text string) (string, bool) {
|
|
|
|
|
name := core.Trim(text)
|
|
|
|
|
if name == "" || name == "." || name == ".." {
|
|
|
|
|
return "", false
|
|
|
|
|
}
|
|
|
|
|
if core.Contains(name, "/") || core.Contains(name, "\\") {
|
|
|
|
|
return "", false
|
|
|
|
|
}
|
|
|
|
|
return name, true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func pathKey(text string) string {
|
|
|
|
|
safe := core.PathBase(core.Trim(text))
|
|
|
|
|
if safe == "." || safe == ".." || safe == "" {
|
|
|
|
|
return "invalid"
|
|
|
|
|
}
|
|
|
|
|
return safe
|
|
|
|
|
}
|
|
|
|
|
|
feat: devops plugin, CLI commands, Codex dispatch fixes, AX sweep
DevOps plugin (5 skills):
- install-core-agent, repair-core-agent, merge-workspace,
update-deps, clean-workspaces
CLI commands: version, check, extract for diagnostics.
Codex dispatch: --skip-git-repo-check, removed broken
--model-reasoning-effort, --sandbox workspace-write via
--full-auto. Workspace template extracts to wsDir not srcDir.
AX sweep (Codex-generated): sanitise.go extracted from prep/plan,
mirror.go JSON parsing via encoding/json, setup/config.go URL
parsing via net/url, strings/fmt imports eliminated from setup.
CODEX.md template updated with Env/Path patterns.
Review workspace template with audit-only PROMPT.md.
Marketplace updated with devops plugin.
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 13:30:27 +00:00
|
|
|
func sanitiseBranchSlug(text string, max int) string {
|
|
|
|
|
out := make([]rune, 0, len(text))
|
|
|
|
|
for _, r := range text {
|
|
|
|
|
switch {
|
|
|
|
|
case r >= 'a' && r <= 'z' || r >= '0' && r <= '9' || r == '-':
|
|
|
|
|
out = append(out, r)
|
|
|
|
|
case r >= 'A' && r <= 'Z':
|
|
|
|
|
out = append(out, r+32)
|
|
|
|
|
default:
|
|
|
|
|
out = append(out, '-')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if max > 0 && len(out) >= max {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return trimRuneEdges(string(out), '-')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func sanitisePlanSlug(text string) string {
|
|
|
|
|
out := make([]rune, 0, len(text))
|
|
|
|
|
for _, r := range text {
|
|
|
|
|
switch {
|
|
|
|
|
case r >= 'a' && r <= 'z' || r >= '0' && r <= '9' || r == '-':
|
|
|
|
|
out = append(out, r)
|
|
|
|
|
case r >= 'A' && r <= 'Z':
|
|
|
|
|
out = append(out, r+32)
|
|
|
|
|
case r == ' ':
|
|
|
|
|
out = append(out, '-')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
slug := collapseRepeatedRune(string(out), '-')
|
|
|
|
|
slug = trimRuneEdges(slug, '-')
|
|
|
|
|
if len(slug) > 30 {
|
|
|
|
|
slug = slug[:30]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return trimRuneEdges(slug, '-')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func sanitiseFilename(text string) string {
|
|
|
|
|
out := make([]rune, 0, len(text))
|
|
|
|
|
for _, r := range text {
|
|
|
|
|
switch {
|
|
|
|
|
case r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z' || r >= '0' && r <= '9' || r == '-' || r == '_' || r == '.':
|
|
|
|
|
out = append(out, r)
|
|
|
|
|
default:
|
|
|
|
|
out = append(out, '-')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return string(out)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func collapseRepeatedRune(text string, target rune) string {
|
|
|
|
|
runes := []rune(text)
|
|
|
|
|
out := make([]rune, 0, len(runes))
|
|
|
|
|
lastWasTarget := false
|
|
|
|
|
|
|
|
|
|
for _, r := range runes {
|
|
|
|
|
if r == target {
|
|
|
|
|
if lastWasTarget {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
lastWasTarget = true
|
|
|
|
|
} else {
|
|
|
|
|
lastWasTarget = false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out = append(out, r)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return string(out)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func trimRuneEdges(text string, target rune) string {
|
|
|
|
|
runes := []rune(text)
|
|
|
|
|
start := 0
|
|
|
|
|
end := len(runes)
|
|
|
|
|
|
|
|
|
|
for start < end && runes[start] == target {
|
|
|
|
|
start++
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for end > start && runes[end-1] == target {
|
|
|
|
|
end--
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return string(runes[start:end])
|
|
|
|
|
}
|