Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions cmd/gc/builtin_prompts_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,66 @@
package main

import (
"io"
"os"
"path/filepath"
"strings"
"testing"

"github.com/gastownhall/gascity/internal/citylayout"
"github.com/gastownhall/gascity/internal/fsys"
)

func materializeBuiltinPromptsForTest(t *testing.T) string {
t.Helper()

dir := t.TempDir()
if err := materializeBuiltinPrompts(dir); err != nil {
t.Fatalf("materializeBuiltinPrompts: %v", err)
}
return dir
}

func renderBuiltinPromptForTest(t *testing.T, dir, name string, ctx PromptContext) string {
t.Helper()

got := renderPrompt(fsys.OSFS{}, dir, "gastown", filepath.Join(citylayout.PromptsRoot, name), ctx, "", io.Discard, nil, nil, nil)
if got == "" {
t.Fatalf("renderPrompt(%s) returned empty output", name)
}
return got
}

func assertRenderedPromptContains(t *testing.T, rendered, name string, want []string) {
t.Helper()

for _, needle := range want {
if strings.Contains(rendered, needle) {
continue
}
t.Errorf("prompt %s missing %q", name, needle)
}
}

func assertRenderedPromptDoesNotContain(t *testing.T, rendered, name string, blocked []string) {
t.Helper()

for _, needle := range blocked {
if !strings.Contains(rendered, needle) {
continue
}
t.Errorf("prompt %s unexpectedly contains %q", name, needle)
}
}

func currentHookCommand(target string) string {
return "gc " + newHookCmd(io.Discard, io.Discard).Name() + " " + target
}

func currentSlingCommand(target, bead string) string {
return "gc " + newSlingCmd(io.Discard, io.Discard).Name() + " " + target + " " + bead
}

func TestMaterializeBuiltinPrompts(t *testing.T) {
dir := t.TempDir()
if err := materializeBuiltinPrompts(dir); err != nil {
Expand Down Expand Up @@ -59,6 +112,43 @@ func TestMaterializeBuiltinPromptsOverwrites(t *testing.T) {
}
}

func TestMaterializeBuiltinFixedWorkerPromptsUseCurrentHookCommand(t *testing.T) {
dir := materializeBuiltinPromptsForTest(t)

tests := map[string]PromptContext{
"worker.md": {AgentName: "mayor", TemplateName: "mayor"},
"one-shot.md": {AgentName: "mayor", TemplateName: "mayor"},
"scoped-worker.md": {AgentName: "hello-world/worker", TemplateName: "worker", RigName: "hello-world", WorkDir: "/city/hello-world"},
}
for name, ctx := range tests {
rendered := renderBuiltinPromptForTest(t, dir, name, ctx)
assertRenderedPromptContains(t, rendered, name, []string{currentHookCommand("$GC_AGENT")})
assertRenderedPromptDoesNotContain(t, rendered, name, []string{"gc agent claimed"})
}
}

func TestMaterializeBuiltinLoopPromptsUseCurrentHookAndSlingCommands(t *testing.T) {
dir := materializeBuiltinPromptsForTest(t)

tests := map[string]PromptContext{
"loop.md": {AgentName: "worker", TemplateName: "worker"},
"loop-mail.md": {AgentName: "worker", TemplateName: "worker"},
}
want := []string{
currentHookCommand("$GC_AGENT"),
currentSlingCommand("$GC_AGENT", "<id>"),
}
blocked := []string{
"gc agent claimed",
"gc agent claim",
}
for name, ctx := range tests {
rendered := renderBuiltinPromptForTest(t, dir, name, ctx)
assertRenderedPromptContains(t, rendered, name, want)
assertRenderedPromptDoesNotContain(t, rendered, name, blocked)
}
}

func TestMaterializeBuiltinFormulas(t *testing.T) {
dir := t.TempDir()
if err := materializeBuiltinFormulas(dir); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cmd/gc/cmd_hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ func doHook(workQuery, dir string, inject bool, runner WorkQueryRunner, stdout,

if inject {
if hasWork {
fmt.Fprintf(stdout, "<system-reminder>\nYou have pending work. Pick up the next item:\n\n<work-items>\n%s\n</work-items>\n\nClaim it and start working. Run 'gc hook' to see the full queue.\n</system-reminder>\n", trimmed) //nolint:errcheck // best-effort stdout
fmt.Fprintf(stdout, "<system-reminder>\nYou have pending work. Pick up the next item:\n\n<work-items>\n%s\n</work-items>\n\nStart working on it. Run 'gc hook' to see the full queue.\n</system-reminder>\n", trimmed) //nolint:errcheck // best-effort stdout
}
return 0 // --inject always exits 0
}
Expand Down
6 changes: 6 additions & 0 deletions cmd/gc/cmd_hook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ func TestHookInjectFormatsOutput(t *testing.T) {
if !strings.Contains(out, "gc hook") {
t.Errorf("stdout missing 'gc hook' hint: %q", out)
}
if !strings.Contains(out, "Start working on it.") {
t.Errorf("stdout missing updated work wording: %q", out)
}
if strings.Contains(out, "Claim it and start working.") {
t.Errorf("stdout still contains stale claim wording: %q", out)
}
}

func TestHookInjectAlwaysExitsZero(t *testing.T) {
Expand Down
6 changes: 3 additions & 3 deletions cmd/gc/prompts/loop-mail.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ You are a coding agent that runs in a loop, checking for work and messages.
2. If you have unread messages, read each one: `gc mail read <id>`
- If the message asks a question, reply: `gc mail send <from> "<your answer>"`
- If the message gives you information, incorporate it into your work
3. Check your claim: `gc agent claimed $GC_AGENT`
4. If a bead is already claimed by you, execute it and go to step 7
3. Check your claim: `gc hook $GC_AGENT`
4. If a bead is already assigned to you, execute it and go to step 7
5. If your hook is empty, check for available work: `bd ready`
6. If a bead is available, claim it: `gc agent claim $GC_AGENT <id>`
6. If a bead is available, route it to yourself: `gc sling $GC_AGENT <id>`
7. Execute the work described in the bead's title
8. When done, close it: `bd close <id>`
9. Go to step 1
12 changes: 6 additions & 6 deletions cmd/gc/prompts/loop.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,24 @@
You are a worker agent in a Gas City workspace. You drain the backlog —
executing tasks one at a time, each with a clean focus.

## GUPP — If you find work claimed by you, YOU RUN IT.
## GUPP — If you find work assigned to you, YOU RUN IT.

No confirmation, no waiting. The hook having work IS the assignment.

## Your tools

- `gc agent claimed $GC_AGENT` — check what's claimed by you
- `gc hook $GC_AGENT` — check what's assigned to you
- `bd ready` — see available work items
- `gc agent claim $GC_AGENT <id>` — claim a work item
- `gc sling $GC_AGENT <id>` — route a work item to yourself
- `bd show <id>` — see details of a work item
- `bd close <id>` — mark work as done

## How to work

1. Check your claim: `gc agent claimed $GC_AGENT`
2. If a bead is already claimed by you, execute it and go to step 5
1. Check your claim: `gc hook $GC_AGENT`
2. If a bead is already assigned to you, execute it and go to step 5
3. If your hook is empty, check for available work: `bd ready`
4. If a bead is available, claim it: `gc agent claim $GC_AGENT <id>`
4. If a bead is available, route it to yourself: `gc sling $GC_AGENT <id>`
5. Execute the work described in the bead's title
6. When done, close it: `bd close <id>`
7. Go to step 1
Expand Down
8 changes: 4 additions & 4 deletions cmd/gc/prompts/one-shot.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@
You are a worker agent in a Gas City workspace. You execute a single task
and stop.

## GUPP — If you find work claimed by you, YOU RUN IT.
## GUPP — If you find work assigned to you, YOU RUN IT.

No confirmation, no waiting. The hook having work IS the assignment.

## Your tools

- `gc agent claimed $GC_AGENT` — check what's claimed by you
- `gc hook $GC_AGENT` — check what's assigned to you
- `bd show <id>` — see details of a work item
- `bd close <id>` — mark work as done

## How to work

1. Check your claim: `gc agent claimed $GC_AGENT`
2. If a bead is claimed by you, execute the work described in its title
1. Check your claim: `gc hook $GC_AGENT`
2. If a bead is assigned to you, execute the work described in its title
3. When done, close it: `bd close <id>`
4. You're done. Wait for further instructions.

Expand Down
8 changes: 4 additions & 4 deletions cmd/gc/prompts/scoped-worker.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@
You are a worker agent in a Gas City workspace.
Your working directory is $GC_DIR — all your work happens there.

## GUPP — If you find work claimed by you, YOU RUN IT.
## GUPP — If you find work assigned to you, YOU RUN IT.

No confirmation, no waiting. The hook having work IS the assignment.

## Your tools

- `gc agent claimed $GC_AGENT` — check what's claimed by you
- `gc hook $GC_AGENT` — check what's assigned to you
- `bd show <id>` — see details of a work item
- `bd close <id>` — mark work as done

## How to work

1. Check your claim: `gc agent claimed $GC_AGENT`
2. If a bead is claimed by you, execute the work described in its title
1. Check your claim: `gc hook $GC_AGENT`
2. If a bead is assigned to you, execute the work described in its title
3. All file operations happen in your directory: $GC_DIR
4. When done, close it: `bd close <id>`
5. Check your claim again for more work
Expand Down
8 changes: 4 additions & 4 deletions cmd/gc/prompts/worker.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@

You are a worker agent in a Gas City workspace.

## GUPP — If you find work claimed by you, YOU RUN IT.
## GUPP — If you find work assigned to you, YOU RUN IT.

No confirmation, no waiting. The hook having work IS the assignment.

## Your tools

- `gc agent claimed $GC_AGENT` — check what's claimed by you
- `gc hook $GC_AGENT` — check what's assigned to you
- `bd show <id>` — see details of a work item
- `bd close <id>` — mark work as done

## How to work

1. Check your claim: `gc agent claimed $GC_AGENT`
2. If a bead is claimed by you, execute the work described in its title
1. Check your claim: `gc hook $GC_AGENT`
2. If a bead is assigned to you, execute the work described in its title
3. When done, close it: `bd close <id>`
4. Check your claim again for more work

Expand Down