fix(trail): treat onboarding defaults as working baselines · Entire

fix(trail): treat onboarding defaults as working baselines

287ef9a→main·

Soph·3w ago·5 files·+105 added/-27 removed

Onboarding writes the default runner set, then tune tailors in place. The embedded defaults are working minimal prompts — each carries the output contract its adapter expects — so a runner left un-tailored is still functional and committable, not a broken placeholder.

ensureRunnersPresent reports which runners it created; applyTuneWithAgent tracks which were tailored and reports any created-but-untailored ones neutrally ("kept as working defaults … re-run to tailor"), rather than warning against committing them. The --print path likewise notes created defaults are functional as-is. A test asserts every embedded default contains its adapter's output contract, so the defaults stay genuinely working.

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com

Sessions

8a860ba0e646View transcript

[?
Refine Risk Review Prompt TemplateClaude Code·Opus 4.8[1m]·4 steps](/content/gh/entireio/cli/session/d70d405d-5421-4b68-beab-2e3bcaaf25a7#timeline-8a860ba0e646/index.html)

Changes

5

53 unmodified lines

return nil
}

// untailoredRunners returns the created runner IDs that tuning did NOT tailor
// (still generic defaults), sorted. These were scaffolded by onboarding but
// left unchanged — skipped, omitted by the model, or returned verbatim — so
// they must not be presented as repo-tailored.
func untailoredRunners(createdIDs []string, tailored map[string]bool) []string {
    var out []string
    for _, id := range createdIDs {
        if !tailored[normalizeRunnerID(id)] {
            out = append(out, id)
        }
    }
    sort.Strings(out)
    return out
}

// droppedPlaceholders returns the placeholders present in oldTemplate but not in
// newTemplate, sorted. Used to inform the user when a rewrite stops using one.
func droppedPlaceholders(oldTemplate, newTemplate string) []string {

Mcmd/entire/cli/trail_tune_apply.go+15

134 unmodified lines

func TestUntailoredRunners(t *testing.T) {
    t.Parallel()

created := []string{"trail-risk", "trail-drift", "trail-review"}
    tailored := map[string]bool{"risk": true} // normalized IDs (no "trail-" prefix)

got := untailoredRunners(created, tailored)
    want := []string{"trail-drift", "trail-review"}
    if len(got) != len(want) || got[0] != want[0] || got[1] != want[1] {
            t.Errorf("untailoredRunners = %v, want %v", got, want)
    }

// Nothing created → nothing untailored, even with no tailoring recorded.
    if u := untailoredRunners(nil, map[string]bool{}); len(u) != 0 {
        t.Errorf("expected empty, got %v", u)
    }
}

func TestParseTuneOutput(t *testing.T) {
    t.Parallel()

Mcmd/entire/cli/trail_tune_apply_test.go+18

6 unmodified lines

7
8
9
10
11
12
13
78 unmodified lines

92
93
94
94
95
96
97
98
99
8 unmodified lines

108
109
110
111
112
113
114
115
116
117
113
118
119
120
121
122
118
119
123
124
125
126
127
128
129
28 unmodified lines

158
159
160
161
162
163
164
22 unmodified lines

187
188
189
190
191
192
193
185
194
195
196
187
188
189
190
191
192
197
198
199
200
201
202
203
204
195
205
206
207
208
209
210
211
212
213
214
215

6 unmodified lines

"io"
    "os"
    "path/filepath"
    "strings"

"github.com/entireio/cli/cmd/entire/cli/agent"
    "github.com/entireio/cli/cmd/entire/cli/paths"
78 unmodified lines

// Onboarding: a repo with no runners yet gets the default set scaffolded
    // (on confirmation), which tune then tailors below.
    if err := ensureRunnersPresent(w, errW, repoRoot, opts.assumeYes); err != nil {
    created, err := ensureRunnersPresent(w, errW, repoRoot, opts.assumeYes)
    if err != nil {
        return err
    }

if !opts.run {
        fmt.Fprintln(w, prompt)
        if len(created) > 0 {
            fmt.Fprintf(errW, "\nCreated %d working default runner(s) (untracked). They are functional as-is; paste the prompt above into your agent to tailor them to this repo.\n", len(created))
        }
        fmt.Fprintf(errW, "\n%d runner(s) in scope. Paste the prompt above into your agent, or re-run with --run to apply headlessly.\n", len(runners))
        return nil
    }

return applyTuneWithAgent(ctx, w, errW, runners, prompt)
}

// applyTuneWithAgent runs the prompt through the configured summary provider
// (prompt -> text), parses the runner-id -> template map it returns, and
// surgically rewrites each runner file's prompt.template in place.
func applyTuneWithAgent(ctx context.Context, w, errW io.Writer, runners []tuneRunner, prompt string) error {

updated, skipped := 0, 0
    tailored := make(map[string]bool)
    for id, tmpl := range templates {
        r, ok := byID[normalizeRunnerID(id)]
        if !ok {
            continue
        }

if err := os.WriteFile(r.Path, []byte(tmpl), 0o644); err != nil {
            return fmt.Errorf("writing %s: %w", r.Path, err)
        }
        fmt.Fprintf(w, "updated %s\n", filepath.Base(r.Path))
        tailored[normalizeRunnerID(r.ID)] = true
        updated++
    }

if updated > 0 {
    switch {
    case updated > 0:
        fmt.Fprintf(w, "\nUpdated %d runner(s). Review with: git diff .entire/runners\n", updated)
        return nil
    }

// Runners onboarding scaffolded but tuning didn't tailor remain the generic
    // defaults. Those are working minimal prompts (valid output contract), so
    // they're committable as-is — just note which are still generic.
    if untailored := untailoredRunners(createdIDs, tailored); len(untailored) > 0 {
        fmt.Fprintf(errW, "\n%d runner(s) kept as working defaults (generic, not tailored to this repo): %s\n",
            len(untailored), strings.Join(untailored, ", "))
        fmt.Fprintln(errW, "They are functional as-is; re-run `entire trail tune --run` to tailor them.")
    }
    return nil
}

Mcmd/entire/cli/trail_tune_cmd.go+30/-12

5 unmodified lines

6
7
8
9
10
11
12
3 unmodified lines

16
17
18
18
19
20
21
22
19
20
21
22
23
24
25
26
27
26
28
29
30
31
32
31
33
34
35
36
37
36
38
39
40
41
40
42
43
44
43
45
46
47
48
49
48
50
51
52
53
54
53
55
56
57
58
59
60
58
61
62
63
64

5 unmodified lines

"io"
    "os"
    "path/filepath"
    "strings"

"github.com/entireio/cli/cmd/entire/cli/interactive"
    "github.com/entireio/cli/cmd/entire/cli/paths"
3 unmodified lines

// ensureRunnersPresent scaffolds the default runner set when a repo has none
// yet, so `tune` doubles as onboarding. It is a no-op when runners already
// exist, and returns an error when the user declined or creation failed.
// Writing is gated on confirmation (interactive prompt, or the --yes flag for
// non-interactive runs).
func ensureRunnersPresent(w, errW io.Writer, repoRoot string, assumeYes bool) error {

// [...] contents stripped for brevity

for _, f := range defaults {
        dest := filepath.Join(dir, f.Name)
        if err := os.WriteFile(dest, f.Data, 0o644); err != nil { //nolint:gosec // runner configs are repo-committed, world-readable config
            return fmt.Errorf("writing %s: %w", dest, err)
        }
        fmt.Fprintf(w, "created %s\n", filepath.Join(paths.EntireDir, "runners", f.Name))
        created = append(created, strings.TrimSuffix(f.Name, ".json"))
    }
}

func confirmCreateRunners(n int) (bool, error) {

}