2026-03-29 22:26:12 +00:00
|
|
|
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
|
|
2026-03-22 08:10:56 +00:00
|
|
|
package lib
|
|
|
|
|
|
|
|
|
|
import (
|
2026-03-30 18:38:39 +00:00
|
|
|
"embed"
|
2026-03-30 00:05:54 +00:00
|
|
|
"runtime"
|
2026-03-22 08:10:56 +00:00
|
|
|
"testing"
|
2026-03-26 06:38:02 +00:00
|
|
|
|
|
|
|
|
core "dappco.re/go/core"
|
2026-03-22 08:10:56 +00:00
|
|
|
)
|
|
|
|
|
|
2026-03-26 06:38:02 +00:00
|
|
|
var testFs = (&core.Fs{}).NewUnrestricted()
|
|
|
|
|
|
2026-03-30 18:38:39 +00:00
|
|
|
func breakLibMountForTest(t *testing.T) {
|
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
|
|
originalPromptFiles := promptFiles
|
|
|
|
|
promptFiles = embed.FS{}
|
refactor: AX compliance sweep — replace banned stdlib imports with core primitives
Replaced fmt, strings, sort, os, io, sync, encoding/json, path/filepath,
errors, log, reflect with core.Sprintf, core.E, core.Contains, core.Trim,
core.Split, core.Join, core.JoinPath, slices.Sort, c.Fs(), c.Lock(),
core.JSONMarshal, core.ReadAll and other CoreGO v0.8.0 primitives.
Framework boundary exceptions preserved where stdlib types are required
by external interfaces (Gin, net/http, CGo, Wails, bubbletea).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-13 09:32:00 +01:00
|
|
|
mountDone.Store(false)
|
2026-03-30 18:38:39 +00:00
|
|
|
mountResult = core.Result{}
|
|
|
|
|
data = nil
|
|
|
|
|
promptFS = nil
|
|
|
|
|
taskFS = nil
|
|
|
|
|
flowFS = nil
|
|
|
|
|
personaFS = nil
|
|
|
|
|
workspaceFS = nil
|
|
|
|
|
|
|
|
|
|
t.Cleanup(func() {
|
|
|
|
|
promptFiles = originalPromptFiles
|
refactor: AX compliance sweep — replace banned stdlib imports with core primitives
Replaced fmt, strings, sort, os, io, sync, encoding/json, path/filepath,
errors, log, reflect with core.Sprintf, core.E, core.Contains, core.Trim,
core.Split, core.Join, core.JoinPath, slices.Sort, c.Fs(), c.Lock(),
core.JSONMarshal, core.ReadAll and other CoreGO v0.8.0 primitives.
Framework boundary exceptions preserved where stdlib types are required
by external interfaces (Gin, net/http, CGo, Wails, bubbletea).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-13 09:32:00 +01:00
|
|
|
mountDone.Store(false)
|
2026-03-30 18:38:39 +00:00
|
|
|
mountResult = core.Result{}
|
|
|
|
|
data = nil
|
|
|
|
|
promptFS = nil
|
|
|
|
|
taskFS = nil
|
|
|
|
|
flowFS = nil
|
|
|
|
|
personaFS = nil
|
|
|
|
|
workspaceFS = nil
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func corruptLibMountForTest(t *testing.T) {
|
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
|
|
MountData(core.New())
|
|
|
|
|
data = nil
|
|
|
|
|
|
|
|
|
|
t.Cleanup(func() {
|
refactor: AX compliance sweep — replace banned stdlib imports with core primitives
Replaced fmt, strings, sort, os, io, sync, encoding/json, path/filepath,
errors, log, reflect with core.Sprintf, core.E, core.Contains, core.Trim,
core.Split, core.Join, core.JoinPath, slices.Sort, c.Fs(), c.Lock(),
core.JSONMarshal, core.ReadAll and other CoreGO v0.8.0 primitives.
Framework boundary exceptions preserved where stdlib types are required
by external interfaces (Gin, net/http, CGo, Wails, bubbletea).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-13 09:32:00 +01:00
|
|
|
mountDone.Store(false)
|
2026-03-30 18:38:39 +00:00
|
|
|
mountResult = core.Result{}
|
|
|
|
|
data = nil
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 19:14:14 +00:00
|
|
|
func requireExtractWorkspaceOK(t *testing.T, result core.Result) string {
|
|
|
|
|
t.Helper()
|
|
|
|
|
if !result.OK {
|
|
|
|
|
t.Fatalf("ExtractWorkspace failed: %v", result.Value)
|
|
|
|
|
}
|
|
|
|
|
path, ok := result.Value.(string)
|
|
|
|
|
if !ok {
|
|
|
|
|
t.Fatalf("ExtractWorkspace returned %T, want string", result.Value)
|
|
|
|
|
}
|
|
|
|
|
return path
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func requireExtractWorkspaceError(t *testing.T, result core.Result) error {
|
|
|
|
|
t.Helper()
|
|
|
|
|
if result.OK {
|
|
|
|
|
t.Fatalf("ExtractWorkspace unexpectedly succeeded: %#v", result.Value)
|
|
|
|
|
}
|
|
|
|
|
err, ok := result.Value.(error)
|
|
|
|
|
if !ok {
|
|
|
|
|
t.Fatalf("ExtractWorkspace returned %T, want error", result.Value)
|
|
|
|
|
}
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
feat(lib): migrate to Core Embed system with Result returns
- All public functions return core.Result instead of (string, error)
- Mount scopes basedir — no path prefix needed in ReadString calls
- Add Bundle struct replacing (string, map, error) anti-pattern
- listDir takes *core.Embed not embed.FS
- ListTasks/ListPersonas use FS() + BaseDirectory() for WalkDir
- Remove bytes, os, text/template imports
- 22 tests: Prompt, Task, TaskBundle, Flow, Persona, Template,
List functions, ExtractWorkspace (Good/Bad patterns)
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:35 +00:00
|
|
|
// --- Prompt ---
|
|
|
|
|
|
2026-03-26 06:38:02 +00:00
|
|
|
func TestLib_Prompt_Good(t *testing.T) {
|
feat(lib): migrate to Core Embed system with Result returns
- All public functions return core.Result instead of (string, error)
- Mount scopes basedir — no path prefix needed in ReadString calls
- Add Bundle struct replacing (string, map, error) anti-pattern
- listDir takes *core.Embed not embed.FS
- ListTasks/ListPersonas use FS() + BaseDirectory() for WalkDir
- Remove bytes, os, text/template imports
- 22 tests: Prompt, Task, TaskBundle, Flow, Persona, Template,
List functions, ExtractWorkspace (Good/Bad patterns)
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:35 +00:00
|
|
|
r := Prompt("coding")
|
|
|
|
|
if !r.OK {
|
|
|
|
|
t.Fatal("Prompt('coding') returned !OK")
|
|
|
|
|
}
|
|
|
|
|
if r.Value.(string) == "" {
|
|
|
|
|
t.Error("Prompt('coding') returned empty string")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 06:38:02 +00:00
|
|
|
func TestLib_Prompt_Bad(t *testing.T) {
|
feat(lib): migrate to Core Embed system with Result returns
- All public functions return core.Result instead of (string, error)
- Mount scopes basedir — no path prefix needed in ReadString calls
- Add Bundle struct replacing (string, map, error) anti-pattern
- listDir takes *core.Embed not embed.FS
- ListTasks/ListPersonas use FS() + BaseDirectory() for WalkDir
- Remove bytes, os, text/template imports
- 22 tests: Prompt, Task, TaskBundle, Flow, Persona, Template,
List functions, ExtractWorkspace (Good/Bad patterns)
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:35 +00:00
|
|
|
r := Prompt("nonexistent-slug")
|
|
|
|
|
if r.OK {
|
|
|
|
|
t.Error("Prompt('nonexistent-slug') should return !OK")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 18:38:39 +00:00
|
|
|
func TestLib_Prompt_Ugly(t *testing.T) {
|
|
|
|
|
r := Prompt("../coding")
|
|
|
|
|
if r.OK {
|
|
|
|
|
t.Error("Prompt('../coding') should return !OK")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat(lib): migrate to Core Embed system with Result returns
- All public functions return core.Result instead of (string, error)
- Mount scopes basedir — no path prefix needed in ReadString calls
- Add Bundle struct replacing (string, map, error) anti-pattern
- listDir takes *core.Embed not embed.FS
- ListTasks/ListPersonas use FS() + BaseDirectory() for WalkDir
- Remove bytes, os, text/template imports
- 22 tests: Prompt, Task, TaskBundle, Flow, Persona, Template,
List functions, ExtractWorkspace (Good/Bad patterns)
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:35 +00:00
|
|
|
// --- Task ---
|
|
|
|
|
|
2026-03-26 06:38:02 +00:00
|
|
|
func TestLib_Task_Good(t *testing.T) {
|
feat(lib): migrate to Core Embed system with Result returns
- All public functions return core.Result instead of (string, error)
- Mount scopes basedir — no path prefix needed in ReadString calls
- Add Bundle struct replacing (string, map, error) anti-pattern
- listDir takes *core.Embed not embed.FS
- ListTasks/ListPersonas use FS() + BaseDirectory() for WalkDir
- Remove bytes, os, text/template imports
- 22 tests: Prompt, Task, TaskBundle, Flow, Persona, Template,
List functions, ExtractWorkspace (Good/Bad patterns)
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:35 +00:00
|
|
|
r := Task("bug-fix")
|
|
|
|
|
if !r.OK {
|
|
|
|
|
t.Fatal("Task('bug-fix') returned !OK")
|
|
|
|
|
}
|
|
|
|
|
if r.Value.(string) == "" {
|
|
|
|
|
t.Error("Task('bug-fix') returned empty string")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 06:38:02 +00:00
|
|
|
func TestLib_TaskNested_Good(t *testing.T) {
|
feat(lib): migrate to Core Embed system with Result returns
- All public functions return core.Result instead of (string, error)
- Mount scopes basedir — no path prefix needed in ReadString calls
- Add Bundle struct replacing (string, map, error) anti-pattern
- listDir takes *core.Embed not embed.FS
- ListTasks/ListPersonas use FS() + BaseDirectory() for WalkDir
- Remove bytes, os, text/template imports
- 22 tests: Prompt, Task, TaskBundle, Flow, Persona, Template,
List functions, ExtractWorkspace (Good/Bad patterns)
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:35 +00:00
|
|
|
r := Task("code/review")
|
|
|
|
|
if !r.OK {
|
|
|
|
|
t.Fatal("Task('code/review') returned !OK")
|
|
|
|
|
}
|
|
|
|
|
if r.Value.(string) == "" {
|
|
|
|
|
t.Error("Task('code/review') returned empty string")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 06:38:02 +00:00
|
|
|
func TestLib_Task_Bad(t *testing.T) {
|
feat(lib): migrate to Core Embed system with Result returns
- All public functions return core.Result instead of (string, error)
- Mount scopes basedir — no path prefix needed in ReadString calls
- Add Bundle struct replacing (string, map, error) anti-pattern
- listDir takes *core.Embed not embed.FS
- ListTasks/ListPersonas use FS() + BaseDirectory() for WalkDir
- Remove bytes, os, text/template imports
- 22 tests: Prompt, Task, TaskBundle, Flow, Persona, Template,
List functions, ExtractWorkspace (Good/Bad patterns)
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:35 +00:00
|
|
|
r := Task("nonexistent-slug")
|
|
|
|
|
if r.OK {
|
|
|
|
|
t.Error("Task('nonexistent-slug') should return !OK")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 18:38:39 +00:00
|
|
|
func TestLib_Task_Ugly(t *testing.T) {
|
|
|
|
|
r := Task("../bug-fix")
|
|
|
|
|
if r.OK {
|
|
|
|
|
t.Error("Task('../bug-fix') should return !OK")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat(lib): migrate to Core Embed system with Result returns
- All public functions return core.Result instead of (string, error)
- Mount scopes basedir — no path prefix needed in ReadString calls
- Add Bundle struct replacing (string, map, error) anti-pattern
- listDir takes *core.Embed not embed.FS
- ListTasks/ListPersonas use FS() + BaseDirectory() for WalkDir
- Remove bytes, os, text/template imports
- 22 tests: Prompt, Task, TaskBundle, Flow, Persona, Template,
List functions, ExtractWorkspace (Good/Bad patterns)
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:35 +00:00
|
|
|
// --- TaskBundle ---
|
|
|
|
|
|
2026-03-26 06:38:02 +00:00
|
|
|
func TestLib_TaskBundle_Good(t *testing.T) {
|
feat(lib): migrate to Core Embed system with Result returns
- All public functions return core.Result instead of (string, error)
- Mount scopes basedir — no path prefix needed in ReadString calls
- Add Bundle struct replacing (string, map, error) anti-pattern
- listDir takes *core.Embed not embed.FS
- ListTasks/ListPersonas use FS() + BaseDirectory() for WalkDir
- Remove bytes, os, text/template imports
- 22 tests: Prompt, Task, TaskBundle, Flow, Persona, Template,
List functions, ExtractWorkspace (Good/Bad patterns)
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:35 +00:00
|
|
|
r := TaskBundle("code/review")
|
|
|
|
|
if !r.OK {
|
|
|
|
|
t.Fatal("TaskBundle('code/review') returned !OK")
|
|
|
|
|
}
|
|
|
|
|
b := r.Value.(Bundle)
|
|
|
|
|
if b.Main == "" {
|
|
|
|
|
t.Error("Bundle.Main is empty")
|
|
|
|
|
}
|
|
|
|
|
if len(b.Files) == 0 {
|
|
|
|
|
t.Error("Bundle.Files is empty — expected companion files")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 06:38:02 +00:00
|
|
|
func TestLib_TaskBundle_Bad(t *testing.T) {
|
feat(lib): migrate to Core Embed system with Result returns
- All public functions return core.Result instead of (string, error)
- Mount scopes basedir — no path prefix needed in ReadString calls
- Add Bundle struct replacing (string, map, error) anti-pattern
- listDir takes *core.Embed not embed.FS
- ListTasks/ListPersonas use FS() + BaseDirectory() for WalkDir
- Remove bytes, os, text/template imports
- 22 tests: Prompt, Task, TaskBundle, Flow, Persona, Template,
List functions, ExtractWorkspace (Good/Bad patterns)
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:35 +00:00
|
|
|
r := TaskBundle("nonexistent")
|
|
|
|
|
if r.OK {
|
|
|
|
|
t.Error("TaskBundle('nonexistent') should return !OK")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 18:38:39 +00:00
|
|
|
func TestLib_TaskBundle_Ugly(t *testing.T) {
|
|
|
|
|
r := TaskBundle("../code/review")
|
|
|
|
|
if r.OK {
|
|
|
|
|
t.Error("TaskBundle('../code/review') should return !OK")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat(lib): migrate to Core Embed system with Result returns
- All public functions return core.Result instead of (string, error)
- Mount scopes basedir — no path prefix needed in ReadString calls
- Add Bundle struct replacing (string, map, error) anti-pattern
- listDir takes *core.Embed not embed.FS
- ListTasks/ListPersonas use FS() + BaseDirectory() for WalkDir
- Remove bytes, os, text/template imports
- 22 tests: Prompt, Task, TaskBundle, Flow, Persona, Template,
List functions, ExtractWorkspace (Good/Bad patterns)
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:35 +00:00
|
|
|
// --- Flow ---
|
|
|
|
|
|
2026-03-26 06:38:02 +00:00
|
|
|
func TestLib_Flow_Good(t *testing.T) {
|
feat(lib): migrate to Core Embed system with Result returns
- All public functions return core.Result instead of (string, error)
- Mount scopes basedir — no path prefix needed in ReadString calls
- Add Bundle struct replacing (string, map, error) anti-pattern
- listDir takes *core.Embed not embed.FS
- ListTasks/ListPersonas use FS() + BaseDirectory() for WalkDir
- Remove bytes, os, text/template imports
- 22 tests: Prompt, Task, TaskBundle, Flow, Persona, Template,
List functions, ExtractWorkspace (Good/Bad patterns)
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:35 +00:00
|
|
|
r := Flow("go")
|
|
|
|
|
if !r.OK {
|
|
|
|
|
t.Fatal("Flow('go') returned !OK")
|
|
|
|
|
}
|
|
|
|
|
if r.Value.(string) == "" {
|
|
|
|
|
t.Error("Flow('go') returned empty string")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 18:38:39 +00:00
|
|
|
func TestLib_Flow_Bad(t *testing.T) {
|
|
|
|
|
r := Flow("nonexistent-flow")
|
|
|
|
|
if r.OK {
|
|
|
|
|
t.Error("Flow('nonexistent-flow') should return !OK")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLib_Flow_Ugly(t *testing.T) {
|
|
|
|
|
r := Flow("../go")
|
|
|
|
|
if r.OK {
|
|
|
|
|
t.Error("Flow('../go') should return !OK")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat(lib): migrate to Core Embed system with Result returns
- All public functions return core.Result instead of (string, error)
- Mount scopes basedir — no path prefix needed in ReadString calls
- Add Bundle struct replacing (string, map, error) anti-pattern
- listDir takes *core.Embed not embed.FS
- ListTasks/ListPersonas use FS() + BaseDirectory() for WalkDir
- Remove bytes, os, text/template imports
- 22 tests: Prompt, Task, TaskBundle, Flow, Persona, Template,
List functions, ExtractWorkspace (Good/Bad patterns)
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:35 +00:00
|
|
|
// --- Persona ---
|
|
|
|
|
|
2026-03-26 06:38:02 +00:00
|
|
|
func TestLib_Persona_Good(t *testing.T) {
|
feat(lib): migrate to Core Embed system with Result returns
- All public functions return core.Result instead of (string, error)
- Mount scopes basedir — no path prefix needed in ReadString calls
- Add Bundle struct replacing (string, map, error) anti-pattern
- listDir takes *core.Embed not embed.FS
- ListTasks/ListPersonas use FS() + BaseDirectory() for WalkDir
- Remove bytes, os, text/template imports
- 22 tests: Prompt, Task, TaskBundle, Flow, Persona, Template,
List functions, ExtractWorkspace (Good/Bad patterns)
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:35 +00:00
|
|
|
personas := ListPersonas()
|
|
|
|
|
if len(personas) == 0 {
|
|
|
|
|
t.Skip("no personas found")
|
|
|
|
|
}
|
|
|
|
|
r := Persona(personas[0])
|
|
|
|
|
if !r.OK {
|
|
|
|
|
t.Fatalf("Persona(%q) returned !OK", personas[0])
|
|
|
|
|
}
|
|
|
|
|
if r.Value.(string) == "" {
|
|
|
|
|
t.Errorf("Persona(%q) returned empty string", personas[0])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 18:38:39 +00:00
|
|
|
func TestLib_Persona_Bad(t *testing.T) {
|
|
|
|
|
r := Persona("nonexistent-persona")
|
|
|
|
|
if r.OK {
|
|
|
|
|
t.Error("Persona('nonexistent-persona') should return !OK")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLib_Persona_Ugly(t *testing.T) {
|
|
|
|
|
r := Persona("../secops/developer")
|
|
|
|
|
if r.OK {
|
|
|
|
|
t.Error("Persona('../secops/developer') should return !OK")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat(lib): migrate to Core Embed system with Result returns
- All public functions return core.Result instead of (string, error)
- Mount scopes basedir — no path prefix needed in ReadString calls
- Add Bundle struct replacing (string, map, error) anti-pattern
- listDir takes *core.Embed not embed.FS
- ListTasks/ListPersonas use FS() + BaseDirectory() for WalkDir
- Remove bytes, os, text/template imports
- 22 tests: Prompt, Task, TaskBundle, Flow, Persona, Template,
List functions, ExtractWorkspace (Good/Bad patterns)
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:35 +00:00
|
|
|
// --- Template ---
|
|
|
|
|
|
2026-03-26 06:38:02 +00:00
|
|
|
func TestLib_Template_Good(t *testing.T) {
|
feat(lib): migrate to Core Embed system with Result returns
- All public functions return core.Result instead of (string, error)
- Mount scopes basedir — no path prefix needed in ReadString calls
- Add Bundle struct replacing (string, map, error) anti-pattern
- listDir takes *core.Embed not embed.FS
- ListTasks/ListPersonas use FS() + BaseDirectory() for WalkDir
- Remove bytes, os, text/template imports
- 22 tests: Prompt, Task, TaskBundle, Flow, Persona, Template,
List functions, ExtractWorkspace (Good/Bad patterns)
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:35 +00:00
|
|
|
r := Template("coding")
|
|
|
|
|
if !r.OK {
|
|
|
|
|
t.Fatal("Template('coding') returned !OK")
|
|
|
|
|
}
|
|
|
|
|
if r.Value.(string) == "" {
|
|
|
|
|
t.Error("Template('coding') returned empty string")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 06:38:02 +00:00
|
|
|
func TestLib_TemplateFallback_Good(t *testing.T) {
|
feat(lib): migrate to Core Embed system with Result returns
- All public functions return core.Result instead of (string, error)
- Mount scopes basedir — no path prefix needed in ReadString calls
- Add Bundle struct replacing (string, map, error) anti-pattern
- listDir takes *core.Embed not embed.FS
- ListTasks/ListPersonas use FS() + BaseDirectory() for WalkDir
- Remove bytes, os, text/template imports
- 22 tests: Prompt, Task, TaskBundle, Flow, Persona, Template,
List functions, ExtractWorkspace (Good/Bad patterns)
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:35 +00:00
|
|
|
r := Template("bug-fix")
|
|
|
|
|
if !r.OK {
|
|
|
|
|
t.Fatal("Template('bug-fix') returned !OK — should fall through to Task")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 06:38:02 +00:00
|
|
|
func TestLib_Template_Bad(t *testing.T) {
|
feat(lib): migrate to Core Embed system with Result returns
- All public functions return core.Result instead of (string, error)
- Mount scopes basedir — no path prefix needed in ReadString calls
- Add Bundle struct replacing (string, map, error) anti-pattern
- listDir takes *core.Embed not embed.FS
- ListTasks/ListPersonas use FS() + BaseDirectory() for WalkDir
- Remove bytes, os, text/template imports
- 22 tests: Prompt, Task, TaskBundle, Flow, Persona, Template,
List functions, ExtractWorkspace (Good/Bad patterns)
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:35 +00:00
|
|
|
r := Template("nonexistent-slug")
|
|
|
|
|
if r.OK {
|
|
|
|
|
t.Error("Template('nonexistent-slug') should return !OK")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 18:38:39 +00:00
|
|
|
func TestLib_Template_Ugly(t *testing.T) {
|
|
|
|
|
r := Template("../coding")
|
|
|
|
|
if r.OK {
|
|
|
|
|
t.Error("Template('../coding') should return !OK")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- WorkspaceFile ---
|
|
|
|
|
|
|
|
|
|
func TestLib_WorkspaceFile_Good(t *testing.T) {
|
|
|
|
|
r := WorkspaceFile("default", "CODEX.md.tmpl")
|
|
|
|
|
if !r.OK {
|
|
|
|
|
t.Fatal("WorkspaceFile('default', 'CODEX.md.tmpl') returned !OK")
|
|
|
|
|
}
|
|
|
|
|
if r.Value.(string) == "" {
|
|
|
|
|
t.Error("WorkspaceFile('default', 'CODEX.md.tmpl') returned empty string")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLib_WorkspaceFile_Bad(t *testing.T) {
|
|
|
|
|
r := WorkspaceFile("missing-template", "CODEX.md.tmpl")
|
|
|
|
|
if r.OK {
|
|
|
|
|
t.Error("WorkspaceFile('missing-template', 'CODEX.md.tmpl') should return !OK")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLib_WorkspaceFile_Ugly(t *testing.T) {
|
|
|
|
|
r := WorkspaceFile("default", "../CODEX.md.tmpl")
|
|
|
|
|
if r.OK {
|
|
|
|
|
t.Error("WorkspaceFile('default', '../CODEX.md.tmpl') should return !OK")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- MountData ---
|
|
|
|
|
|
|
|
|
|
func TestLib_MountData_Good(t *testing.T) {
|
|
|
|
|
c := core.New()
|
|
|
|
|
MountData(c)
|
|
|
|
|
|
|
|
|
|
r := c.Data().ReadString("prompts/coding.md")
|
|
|
|
|
if !r.OK {
|
|
|
|
|
t.Fatal("MountData() did not register prompt data")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLib_MountData_Bad(t *testing.T) {
|
|
|
|
|
breakLibMountForTest(t)
|
|
|
|
|
|
|
|
|
|
c := core.New()
|
|
|
|
|
MountData(c)
|
|
|
|
|
|
|
|
|
|
r := c.Data().ReadString("prompts/coding.md")
|
|
|
|
|
if r.OK {
|
|
|
|
|
t.Error("MountData() should not register prompt data when mounting fails")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLib_MountData_Ugly(t *testing.T) {
|
|
|
|
|
corruptLibMountForTest(t)
|
|
|
|
|
|
|
|
|
|
panicked := false
|
|
|
|
|
func() {
|
|
|
|
|
defer func() {
|
|
|
|
|
if recover() != nil {
|
|
|
|
|
panicked = true
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
MountData(nil)
|
|
|
|
|
}()
|
|
|
|
|
if !panicked {
|
|
|
|
|
t.Fatal("MountData(nil) should panic")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat(lib): migrate to Core Embed system with Result returns
- All public functions return core.Result instead of (string, error)
- Mount scopes basedir — no path prefix needed in ReadString calls
- Add Bundle struct replacing (string, map, error) anti-pattern
- listDir takes *core.Embed not embed.FS
- ListTasks/ListPersonas use FS() + BaseDirectory() for WalkDir
- Remove bytes, os, text/template imports
- 22 tests: Prompt, Task, TaskBundle, Flow, Persona, Template,
List functions, ExtractWorkspace (Good/Bad patterns)
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:35 +00:00
|
|
|
// --- List Functions ---
|
|
|
|
|
|
2026-03-26 06:38:02 +00:00
|
|
|
func TestLib_ListPrompts_Good(t *testing.T) {
|
feat(lib): migrate to Core Embed system with Result returns
- All public functions return core.Result instead of (string, error)
- Mount scopes basedir — no path prefix needed in ReadString calls
- Add Bundle struct replacing (string, map, error) anti-pattern
- listDir takes *core.Embed not embed.FS
- ListTasks/ListPersonas use FS() + BaseDirectory() for WalkDir
- Remove bytes, os, text/template imports
- 22 tests: Prompt, Task, TaskBundle, Flow, Persona, Template,
List functions, ExtractWorkspace (Good/Bad patterns)
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:35 +00:00
|
|
|
prompts := ListPrompts()
|
|
|
|
|
if len(prompts) == 0 {
|
|
|
|
|
t.Error("ListPrompts() returned empty")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 18:38:39 +00:00
|
|
|
func TestLib_ListPrompts_Bad(t *testing.T) {
|
|
|
|
|
breakLibMountForTest(t)
|
|
|
|
|
|
|
|
|
|
if prompts := ListPrompts(); prompts != nil {
|
|
|
|
|
t.Error("ListPrompts() should return nil when mounting fails")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLib_ListPrompts_Ugly(t *testing.T) {
|
|
|
|
|
corruptLibMountForTest(t)
|
|
|
|
|
|
|
|
|
|
panicked := false
|
|
|
|
|
func() {
|
|
|
|
|
defer func() {
|
|
|
|
|
if recover() != nil {
|
|
|
|
|
panicked = true
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
_ = ListPrompts()
|
|
|
|
|
}()
|
|
|
|
|
if !panicked {
|
|
|
|
|
t.Fatal("ListPrompts() should panic when embedded state is corrupted")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 06:38:02 +00:00
|
|
|
func TestLib_ListTasks_Good(t *testing.T) {
|
feat(lib): migrate to Core Embed system with Result returns
- All public functions return core.Result instead of (string, error)
- Mount scopes basedir — no path prefix needed in ReadString calls
- Add Bundle struct replacing (string, map, error) anti-pattern
- listDir takes *core.Embed not embed.FS
- ListTasks/ListPersonas use FS() + BaseDirectory() for WalkDir
- Remove bytes, os, text/template imports
- 22 tests: Prompt, Task, TaskBundle, Flow, Persona, Template,
List functions, ExtractWorkspace (Good/Bad patterns)
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:35 +00:00
|
|
|
tasks := ListTasks()
|
|
|
|
|
if len(tasks) == 0 {
|
|
|
|
|
t.Fatal("ListTasks() returned empty")
|
|
|
|
|
}
|
|
|
|
|
found := false
|
|
|
|
|
for _, s := range tasks {
|
|
|
|
|
if s == "code/review" {
|
|
|
|
|
found = true
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if !found {
|
|
|
|
|
t.Error("ListTasks() missing nested path 'code/review'")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 18:38:39 +00:00
|
|
|
func TestLib_ListTasks_Bad(t *testing.T) {
|
|
|
|
|
breakLibMountForTest(t)
|
|
|
|
|
|
|
|
|
|
if tasks := ListTasks(); tasks != nil {
|
|
|
|
|
t.Error("ListTasks() should return nil when mounting fails")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLib_ListTasks_Ugly(t *testing.T) {
|
|
|
|
|
corruptLibMountForTest(t)
|
|
|
|
|
|
|
|
|
|
panicked := false
|
|
|
|
|
func() {
|
|
|
|
|
defer func() {
|
|
|
|
|
if recover() != nil {
|
|
|
|
|
panicked = true
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
_ = ListTasks()
|
|
|
|
|
}()
|
|
|
|
|
if !panicked {
|
|
|
|
|
t.Fatal("ListTasks() should panic when embedded state is corrupted")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 06:38:02 +00:00
|
|
|
func TestLib_ListPersonas_Good(t *testing.T) {
|
feat(lib): migrate to Core Embed system with Result returns
- All public functions return core.Result instead of (string, error)
- Mount scopes basedir — no path prefix needed in ReadString calls
- Add Bundle struct replacing (string, map, error) anti-pattern
- listDir takes *core.Embed not embed.FS
- ListTasks/ListPersonas use FS() + BaseDirectory() for WalkDir
- Remove bytes, os, text/template imports
- 22 tests: Prompt, Task, TaskBundle, Flow, Persona, Template,
List functions, ExtractWorkspace (Good/Bad patterns)
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:35 +00:00
|
|
|
personas := ListPersonas()
|
|
|
|
|
if len(personas) == 0 {
|
|
|
|
|
t.Error("ListPersonas() returned empty")
|
|
|
|
|
}
|
|
|
|
|
hasNested := false
|
|
|
|
|
for _, p := range personas {
|
2026-03-26 06:38:02 +00:00
|
|
|
if len(p) > 0 && core.PathDir(p) != "." {
|
feat(lib): migrate to Core Embed system with Result returns
- All public functions return core.Result instead of (string, error)
- Mount scopes basedir — no path prefix needed in ReadString calls
- Add Bundle struct replacing (string, map, error) anti-pattern
- listDir takes *core.Embed not embed.FS
- ListTasks/ListPersonas use FS() + BaseDirectory() for WalkDir
- Remove bytes, os, text/template imports
- 22 tests: Prompt, Task, TaskBundle, Flow, Persona, Template,
List functions, ExtractWorkspace (Good/Bad patterns)
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:35 +00:00
|
|
|
hasNested = true
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if !hasNested {
|
|
|
|
|
t.Error("ListPersonas() has no nested paths")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 18:38:39 +00:00
|
|
|
func TestLib_ListPersonas_Bad(t *testing.T) {
|
|
|
|
|
breakLibMountForTest(t)
|
|
|
|
|
|
|
|
|
|
if personas := ListPersonas(); personas != nil {
|
|
|
|
|
t.Error("ListPersonas() should return nil when mounting fails")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLib_ListPersonas_Ugly(t *testing.T) {
|
|
|
|
|
corruptLibMountForTest(t)
|
|
|
|
|
|
|
|
|
|
panicked := false
|
|
|
|
|
func() {
|
|
|
|
|
defer func() {
|
|
|
|
|
if recover() != nil {
|
|
|
|
|
panicked = true
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
_ = ListPersonas()
|
|
|
|
|
}()
|
|
|
|
|
if !panicked {
|
|
|
|
|
t.Fatal("ListPersonas() should panic when embedded state is corrupted")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 06:38:02 +00:00
|
|
|
func TestLib_ListFlows_Good(t *testing.T) {
|
feat(lib): migrate to Core Embed system with Result returns
- All public functions return core.Result instead of (string, error)
- Mount scopes basedir — no path prefix needed in ReadString calls
- Add Bundle struct replacing (string, map, error) anti-pattern
- listDir takes *core.Embed not embed.FS
- ListTasks/ListPersonas use FS() + BaseDirectory() for WalkDir
- Remove bytes, os, text/template imports
- 22 tests: Prompt, Task, TaskBundle, Flow, Persona, Template,
List functions, ExtractWorkspace (Good/Bad patterns)
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:35 +00:00
|
|
|
flows := ListFlows()
|
|
|
|
|
if len(flows) == 0 {
|
|
|
|
|
t.Error("ListFlows() returned empty")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 18:38:39 +00:00
|
|
|
func TestLib_ListFlows_Bad(t *testing.T) {
|
|
|
|
|
breakLibMountForTest(t)
|
|
|
|
|
|
|
|
|
|
if flows := ListFlows(); flows != nil {
|
|
|
|
|
t.Error("ListFlows() should return nil when mounting fails")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLib_ListFlows_Ugly(t *testing.T) {
|
|
|
|
|
corruptLibMountForTest(t)
|
|
|
|
|
|
|
|
|
|
panicked := false
|
|
|
|
|
func() {
|
|
|
|
|
defer func() {
|
|
|
|
|
if recover() != nil {
|
|
|
|
|
panicked = true
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
_ = ListFlows()
|
|
|
|
|
}()
|
|
|
|
|
if !panicked {
|
|
|
|
|
t.Fatal("ListFlows() should panic when embedded state is corrupted")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 06:38:02 +00:00
|
|
|
func TestLib_ListWorkspaces_Good(t *testing.T) {
|
feat(lib): migrate to Core Embed system with Result returns
- All public functions return core.Result instead of (string, error)
- Mount scopes basedir — no path prefix needed in ReadString calls
- Add Bundle struct replacing (string, map, error) anti-pattern
- listDir takes *core.Embed not embed.FS
- ListTasks/ListPersonas use FS() + BaseDirectory() for WalkDir
- Remove bytes, os, text/template imports
- 22 tests: Prompt, Task, TaskBundle, Flow, Persona, Template,
List functions, ExtractWorkspace (Good/Bad patterns)
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:35 +00:00
|
|
|
workspaces := ListWorkspaces()
|
|
|
|
|
if len(workspaces) == 0 {
|
|
|
|
|
t.Error("ListWorkspaces() returned empty")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 18:38:39 +00:00
|
|
|
func TestLib_ListWorkspaces_Bad(t *testing.T) {
|
|
|
|
|
breakLibMountForTest(t)
|
|
|
|
|
|
|
|
|
|
if workspaces := ListWorkspaces(); workspaces != nil {
|
|
|
|
|
t.Error("ListWorkspaces() should return nil when mounting fails")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLib_ListWorkspaces_Ugly(t *testing.T) {
|
|
|
|
|
corruptLibMountForTest(t)
|
|
|
|
|
|
|
|
|
|
panicked := false
|
|
|
|
|
func() {
|
|
|
|
|
defer func() {
|
|
|
|
|
if recover() != nil {
|
|
|
|
|
panicked = true
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
_ = ListWorkspaces()
|
|
|
|
|
}()
|
|
|
|
|
if !panicked {
|
|
|
|
|
t.Fatal("ListWorkspaces() should panic when embedded state is corrupted")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat(lib): migrate to Core Embed system with Result returns
- All public functions return core.Result instead of (string, error)
- Mount scopes basedir — no path prefix needed in ReadString calls
- Add Bundle struct replacing (string, map, error) anti-pattern
- listDir takes *core.Embed not embed.FS
- ListTasks/ListPersonas use FS() + BaseDirectory() for WalkDir
- Remove bytes, os, text/template imports
- 22 tests: Prompt, Task, TaskBundle, Flow, Persona, Template,
List functions, ExtractWorkspace (Good/Bad patterns)
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 09:08:35 +00:00
|
|
|
// --- ExtractWorkspace ---
|
|
|
|
|
|
2026-03-26 06:38:02 +00:00
|
|
|
func TestLib_ExtractWorkspace_Good(t *testing.T) {
|
2026-03-22 08:10:56 +00:00
|
|
|
dir := t.TempDir()
|
|
|
|
|
data := &WorkspaceData{Repo: "test-repo", Task: "test task"}
|
|
|
|
|
|
2026-03-30 19:14:14 +00:00
|
|
|
requireExtractWorkspaceOK(t, ExtractWorkspace("default", dir, data))
|
2026-03-22 08:10:56 +00:00
|
|
|
|
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
|
|
|
for _, name := range []string{"CODEX.md", "CLAUDE.md", "PROMPT.md", "TODO.md", "CONTEXT.md", "go.work"} {
|
2026-03-26 06:38:02 +00:00
|
|
|
if !testFs.Exists(core.JoinPath(dir, name)) {
|
2026-03-22 08:10:56 +00:00
|
|
|
t.Errorf("expected %s to exist", name)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 06:38:02 +00:00
|
|
|
func TestLib_ExtractWorkspaceSubdirs_Good(t *testing.T) {
|
2026-03-22 08:10:56 +00:00
|
|
|
dir := t.TempDir()
|
|
|
|
|
data := &WorkspaceData{Repo: "test-repo", Task: "test task"}
|
|
|
|
|
|
2026-03-30 19:14:14 +00:00
|
|
|
requireExtractWorkspaceOK(t, ExtractWorkspace("default", dir, data))
|
2026-03-22 08:10:56 +00:00
|
|
|
|
2026-03-26 06:38:02 +00:00
|
|
|
refDir := core.JoinPath(dir, ".core", "reference")
|
|
|
|
|
if !testFs.IsDir(refDir) {
|
2026-03-22 08:10:56 +00:00
|
|
|
t.Fatalf(".core/reference/ directory not created")
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 06:38:02 +00:00
|
|
|
axSpec := core.JoinPath(refDir, "RFC-025-AGENT-EXPERIENCE.md")
|
|
|
|
|
if !testFs.Exists(axSpec) {
|
2026-03-22 08:10:56 +00:00
|
|
|
t.Errorf("AX spec not extracted: %s", axSpec)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 06:38:02 +00:00
|
|
|
goFiles := core.PathGlob(core.JoinPath(refDir, "*.go"))
|
|
|
|
|
if len(goFiles) == 0 {
|
2026-03-22 08:10:56 +00:00
|
|
|
t.Error("no .go files in .core/reference/")
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 06:38:02 +00:00
|
|
|
docsDir := core.JoinPath(refDir, "docs")
|
|
|
|
|
if !testFs.IsDir(docsDir) {
|
2026-03-22 08:10:56 +00:00
|
|
|
t.Errorf(".core/reference/docs/ not created")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 06:38:02 +00:00
|
|
|
func TestLib_ExtractWorkspaceTemplate_Good(t *testing.T) {
|
2026-03-22 08:10:56 +00:00
|
|
|
dir := t.TempDir()
|
|
|
|
|
data := &WorkspaceData{Repo: "my-repo", Task: "fix the bug"}
|
|
|
|
|
|
2026-03-30 19:14:14 +00:00
|
|
|
requireExtractWorkspaceOK(t, ExtractWorkspace("default", dir, data))
|
2026-03-22 08:10:56 +00:00
|
|
|
|
2026-03-26 06:38:02 +00:00
|
|
|
r := testFs.Read(core.JoinPath(dir, "TODO.md"))
|
|
|
|
|
if !r.OK {
|
|
|
|
|
t.Fatalf("failed to read TODO.md")
|
2026-03-22 08:10:56 +00:00
|
|
|
}
|
2026-03-26 06:38:02 +00:00
|
|
|
if r.Value.(string) == "" {
|
2026-03-22 08:10:56 +00:00
|
|
|
t.Error("TODO.md is empty")
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-29 23:52:27 +00:00
|
|
|
|
2026-03-30 00:41:43 +00:00
|
|
|
func TestLib_ExtractWorkspace_Bad(t *testing.T) {
|
2026-03-30 19:14:14 +00:00
|
|
|
requireExtractWorkspaceError(t, ExtractWorkspace("missing-template", t.TempDir(), &WorkspaceData{Repo: "test-repo"}))
|
2026-03-30 00:41:43 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 18:38:39 +00:00
|
|
|
func TestLib_ExtractWorkspace_Ugly(t *testing.T) {
|
2026-03-30 19:14:14 +00:00
|
|
|
requireExtractWorkspaceError(t, ExtractWorkspace("default", t.TempDir(), nil))
|
2026-03-30 18:38:39 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-29 23:52:27 +00:00
|
|
|
func TestLib_ExtractWorkspace_Good_AXConventions(t *testing.T) {
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
data := &WorkspaceData{Repo: "test-repo", Task: "align AX docs"}
|
|
|
|
|
|
2026-03-30 19:14:14 +00:00
|
|
|
requireExtractWorkspaceOK(t, ExtractWorkspace("default", dir, data))
|
2026-03-29 23:52:27 +00:00
|
|
|
|
|
|
|
|
r := testFs.Read(core.JoinPath(dir, "CODEX.md"))
|
|
|
|
|
if !r.OK {
|
|
|
|
|
t.Fatalf("failed to read CODEX.md")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
text := r.Value.(string)
|
|
|
|
|
for _, banned := range []string{
|
|
|
|
|
"c.PERFORM(",
|
|
|
|
|
"c.RegisterTask(",
|
|
|
|
|
"OnStartup(ctx context.Context) error",
|
|
|
|
|
"OnShutdown(ctx context.Context) error",
|
|
|
|
|
} {
|
|
|
|
|
if core.Contains(text, banned) {
|
|
|
|
|
t.Errorf("CODEX.md still contains deprecated AX guidance: %s", banned)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, required := range []string{
|
|
|
|
|
"core.WithService(",
|
|
|
|
|
"c.Action(\"workspace.create\"",
|
|
|
|
|
"c.Task(\"deploy\"",
|
|
|
|
|
"c.Process().RunIn(",
|
|
|
|
|
"TestFile_Function_Good",
|
|
|
|
|
} {
|
|
|
|
|
if !core.Contains(text, required) {
|
|
|
|
|
t.Errorf("CODEX.md missing AX guidance: %s", required)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-30 00:05:54 +00:00
|
|
|
|
|
|
|
|
func TestLib_ReferenceFiles_Good_SPDXHeaders(t *testing.T) {
|
|
|
|
|
_, file, _, ok := runtime.Caller(0)
|
|
|
|
|
if !ok {
|
|
|
|
|
t.Fatal("runtime.Caller(0) failed")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
repoRoot := core.PathDir(core.PathDir(core.PathDir(file)))
|
|
|
|
|
refDir := core.JoinPath(repoRoot, ".core", "reference")
|
|
|
|
|
goFiles := core.PathGlob(core.JoinPath(refDir, "*.go"))
|
|
|
|
|
if len(goFiles) == 0 {
|
|
|
|
|
t.Fatalf("no .go files found in %s", refDir)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, path := range goFiles {
|
|
|
|
|
assertSPDXHeader(t, path)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLib_ExtractWorkspace_Good_ReferenceHeaders(t *testing.T) {
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
data := &WorkspaceData{Repo: "test-repo", Task: "carry SPDX headers into workspace references"}
|
|
|
|
|
|
2026-03-30 19:14:14 +00:00
|
|
|
requireExtractWorkspaceOK(t, ExtractWorkspace("default", dir, data))
|
2026-03-30 00:05:54 +00:00
|
|
|
|
|
|
|
|
refDir := core.JoinPath(dir, ".core", "reference")
|
|
|
|
|
goFiles := core.PathGlob(core.JoinPath(refDir, "*.go"))
|
|
|
|
|
if len(goFiles) == 0 {
|
|
|
|
|
t.Fatalf("no extracted .go files found in %s", refDir)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, path := range goFiles {
|
|
|
|
|
assertSPDXHeader(t, path)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 00:52:39 +00:00
|
|
|
func TestLib_ExtractWorkspace_Good_ReferenceUsageExamples(t *testing.T) {
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
data := &WorkspaceData{Repo: "test-repo", Task: "carry AX usage examples into workspace references"}
|
|
|
|
|
|
2026-03-30 19:14:14 +00:00
|
|
|
requireExtractWorkspaceOK(t, ExtractWorkspace("default", dir, data))
|
2026-03-30 00:52:39 +00:00
|
|
|
|
|
|
|
|
cases := map[string][]string{
|
|
|
|
|
core.JoinPath(dir, ".core", "reference", "array.go"): {
|
|
|
|
|
`arr := core.NewArray("prep", "dispatch")`,
|
|
|
|
|
`arr.Add("verify", "merge")`,
|
|
|
|
|
`arr.AddUnique("verify", "verify", "merge")`,
|
|
|
|
|
},
|
|
|
|
|
core.JoinPath(dir, ".core", "reference", "config.go"): {
|
|
|
|
|
`timeout := core.ConfigGet[int](c.Config(), "agent.timeout")`,
|
|
|
|
|
},
|
|
|
|
|
core.JoinPath(dir, ".core", "reference", "embed.go"): {
|
|
|
|
|
`core.AddAsset("docs", "RFC.md", packed)`,
|
|
|
|
|
`r := core.GeneratePack(pkg)`,
|
|
|
|
|
},
|
|
|
|
|
core.JoinPath(dir, ".core", "reference", "error.go"): {
|
|
|
|
|
`if core.Is(err, context.Canceled) { return }`,
|
|
|
|
|
`stack := core.FormatStackTrace(err)`,
|
|
|
|
|
`r := c.Error().Reports(10)`,
|
|
|
|
|
},
|
|
|
|
|
core.JoinPath(dir, ".core", "reference", "log.go"): {
|
|
|
|
|
`log := core.NewLog(core.LogOptions{Level: core.LevelDebug, Output: os.Stdout})`,
|
|
|
|
|
`core.SetRedactKeys("token", "password")`,
|
|
|
|
|
`core.Security("entitlement.denied", "action", "process.run")`,
|
|
|
|
|
},
|
|
|
|
|
core.JoinPath(dir, ".core", "reference", "runtime.go"): {
|
|
|
|
|
`r := c.ServiceStartup(context.Background(), nil)`,
|
|
|
|
|
`r := core.NewRuntime(app)`,
|
|
|
|
|
`name := runtime.ServiceName()`,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for path, snippets := range cases {
|
|
|
|
|
r := testFs.Read(path)
|
|
|
|
|
if !r.OK {
|
|
|
|
|
t.Fatalf("failed to read %s", path)
|
|
|
|
|
}
|
|
|
|
|
text := r.Value.(string)
|
|
|
|
|
for _, snippet := range snippets {
|
|
|
|
|
if !core.Contains(text, snippet) {
|
|
|
|
|
t.Errorf("%s missing usage example snippet %q", path, snippet)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 00:41:43 +00:00
|
|
|
func TestLib_MountEmbed_Bad(t *testing.T) {
|
|
|
|
|
result := mountEmbed(promptFiles, "missing-dir")
|
|
|
|
|
if result.OK {
|
|
|
|
|
t.Fatal("mountEmbed should fail for a missing embedded directory")
|
|
|
|
|
}
|
|
|
|
|
if _, ok := result.Value.(error); !ok {
|
|
|
|
|
t.Fatal("mountEmbed should return an error value")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 00:05:54 +00:00
|
|
|
func assertSPDXHeader(t *testing.T, path string) {
|
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
|
|
r := testFs.Read(path)
|
|
|
|
|
if !r.OK {
|
|
|
|
|
t.Fatalf("failed to read %s", path)
|
|
|
|
|
}
|
|
|
|
|
if !core.HasPrefix(r.Value.(string), "// SPDX-License-Identifier: EUPL-1.2") {
|
|
|
|
|
t.Fatalf("%s missing SPDX header", path)
|
|
|
|
|
}
|
|
|
|
|
}
|