@gr8ful/spf 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +321 -0
  3. package/assets/defaults/spf.config.yaml +141 -0
  4. package/assets/prompts/builder/system.md +13 -0
  5. package/assets/prompts/builder/user.md +34 -0
  6. package/assets/prompts/documenter/system.md +17 -0
  7. package/assets/prompts/documenter/user.md +48 -0
  8. package/assets/prompts/planner/system.md +21 -0
  9. package/assets/prompts/planner/user.md +45 -0
  10. package/assets/prompts/reviewer/system.md +16 -0
  11. package/assets/prompts/reviewer/user.md +44 -0
  12. package/assets/prompts/scout/system.md +20 -0
  13. package/assets/prompts/scout/user.md +34 -0
  14. package/assets/skill/SKILL.md +80 -0
  15. package/assets/skill/cookbooks/authoring_chains.md +193 -0
  16. package/assets/skill/cookbooks/how_to_prompt_for_the_eng.md +109 -0
  17. package/assets/skill/cookbooks/roster.md +197 -0
  18. package/assets/skill/cookbooks/run_adw.md +92 -0
  19. package/assets/skill/cookbooks/spf_overview.md +111 -0
  20. package/assets/skill/references/config.md +188 -0
  21. package/assets/skill/references/handoff.md +162 -0
  22. package/assets/skill/references/observability.md +184 -0
  23. package/dist/chains/adw_build.d.ts +12 -0
  24. package/dist/chains/adw_build.js +27 -0
  25. package/dist/chains/adw_build_review.d.ts +21 -0
  26. package/dist/chains/adw_build_review.js +55 -0
  27. package/dist/chains/adw_build_test.d.ts +21 -0
  28. package/dist/chains/adw_build_test.js +67 -0
  29. package/dist/chains/adw_document.d.ts +23 -0
  30. package/dist/chains/adw_document.js +59 -0
  31. package/dist/chains/adw_plan.d.ts +12 -0
  32. package/dist/chains/adw_plan.js +27 -0
  33. package/dist/chains/adw_plan_build.d.ts +12 -0
  34. package/dist/chains/adw_plan_build.js +30 -0
  35. package/dist/chains/adw_plan_build_test.d.ts +16 -0
  36. package/dist/chains/adw_plan_build_test.js +65 -0
  37. package/dist/chains/adw_plan_build_test_quality.d.ts +18 -0
  38. package/dist/chains/adw_plan_build_test_quality.js +66 -0
  39. package/dist/chains/adw_prompt.d.ts +12 -0
  40. package/dist/chains/adw_prompt.js +25 -0
  41. package/dist/chains/adw_quality.d.ts +12 -0
  42. package/dist/chains/adw_quality.js +32 -0
  43. package/dist/chains/adw_scout.d.ts +12 -0
  44. package/dist/chains/adw_scout.js +27 -0
  45. package/dist/chains/adw_simple_sdlc.d.ts +43 -0
  46. package/dist/chains/adw_simple_sdlc.js +147 -0
  47. package/dist/chains/context.d.ts +19 -0
  48. package/dist/chains/context.js +1 -0
  49. package/dist/chains/index.d.ts +19 -0
  50. package/dist/chains/index.js +116 -0
  51. package/dist/cli/bin.d.ts +15 -0
  52. package/dist/cli/bin.js +24 -0
  53. package/dist/cli/commands/abort.d.ts +1 -0
  54. package/dist/cli/commands/abort.js +42 -0
  55. package/dist/cli/commands/doctor.d.ts +1 -0
  56. package/dist/cli/commands/doctor.js +165 -0
  57. package/dist/cli/commands/eject.d.ts +1 -0
  58. package/dist/cli/commands/eject.js +55 -0
  59. package/dist/cli/commands/events.d.ts +1 -0
  60. package/dist/cli/commands/events.js +49 -0
  61. package/dist/cli/commands/init.d.ts +1 -0
  62. package/dist/cli/commands/init.js +62 -0
  63. package/dist/cli/commands/install-skill.d.ts +1 -0
  64. package/dist/cli/commands/install-skill.js +122 -0
  65. package/dist/cli/commands/list.d.ts +1 -0
  66. package/dist/cli/commands/list.js +13 -0
  67. package/dist/cli/commands/migrate.d.ts +1 -0
  68. package/dist/cli/commands/migrate.js +167 -0
  69. package/dist/cli/commands/phases.d.ts +1 -0
  70. package/dist/cli/commands/phases.js +25 -0
  71. package/dist/cli/commands/run.d.ts +3 -0
  72. package/dist/cli/commands/run.js +27 -0
  73. package/dist/cli/commands/sessions.d.ts +1 -0
  74. package/dist/cli/commands/sessions.js +20 -0
  75. package/dist/cli/commands/trace.d.ts +8 -0
  76. package/dist/cli/commands/trace.js +10 -0
  77. package/dist/cli/commands/ui.d.ts +1 -0
  78. package/dist/cli/commands/ui.js +35 -0
  79. package/dist/cli/commands/version.d.ts +1 -0
  80. package/dist/cli/commands/version.js +8 -0
  81. package/dist/cli/commands/watch.d.ts +8 -0
  82. package/dist/cli/commands/watch.js +210 -0
  83. package/dist/cli/gitignore.d.ts +1 -0
  84. package/dist/cli/gitignore.js +14 -0
  85. package/dist/cli/index.d.ts +1 -0
  86. package/dist/cli/index.js +156 -0
  87. package/dist/core/agent_cc.d.ts +75 -0
  88. package/dist/core/agent_cc.js +322 -0
  89. package/dist/core/agent_flue.d.ts +66 -0
  90. package/dist/core/agent_flue.js +321 -0
  91. package/dist/core/agents.d.ts +64 -0
  92. package/dist/core/agents.js +456 -0
  93. package/dist/core/changes.d.ts +35 -0
  94. package/dist/core/changes.js +98 -0
  95. package/dist/core/console.d.ts +36 -0
  96. package/dist/core/console.js +156 -0
  97. package/dist/core/data_types.d.ts +562 -0
  98. package/dist/core/data_types.js +382 -0
  99. package/dist/core/gates.d.ts +33 -0
  100. package/dist/core/gates.js +144 -0
  101. package/dist/core/git_helper.d.ts +59 -0
  102. package/dist/core/git_helper.js +115 -0
  103. package/dist/core/issues/github_provider.d.ts +52 -0
  104. package/dist/core/issues/github_provider.js +211 -0
  105. package/dist/core/issues/provider.d.ts +90 -0
  106. package/dist/core/issues/provider.js +14 -0
  107. package/dist/core/paths.d.ts +78 -0
  108. package/dist/core/paths.js +108 -0
  109. package/dist/core/permissions.d.ts +78 -0
  110. package/dist/core/permissions.js +187 -0
  111. package/dist/core/prompts.d.ts +4 -0
  112. package/dist/core/prompts.js +17 -0
  113. package/dist/core/quality.d.ts +65 -0
  114. package/dist/core/quality.js +194 -0
  115. package/dist/core/runner.d.ts +72 -0
  116. package/dist/core/runner.js +168 -0
  117. package/dist/core/session.d.ts +16 -0
  118. package/dist/core/session.js +60 -0
  119. package/dist/core/sqlite.d.ts +55 -0
  120. package/dist/core/sqlite.js +106 -0
  121. package/dist/core/tracer.d.ts +56 -0
  122. package/dist/core/tracer.js +246 -0
  123. package/dist/core/utils.d.ts +40 -0
  124. package/dist/core/utils.js +108 -0
  125. package/dist/core/watch.d.ts +44 -0
  126. package/dist/core/watch.js +194 -0
  127. package/dist/test/agent_cc.test.d.ts +1 -0
  128. package/dist/test/agent_cc.test.js +95 -0
  129. package/dist/test/agent_flue.test.d.ts +1 -0
  130. package/dist/test/agent_flue.test.js +83 -0
  131. package/dist/test/data_types.test.d.ts +10 -0
  132. package/dist/test/data_types.test.js +49 -0
  133. package/dist/test/ui_server.test.d.ts +1 -0
  134. package/dist/test/ui_server.test.js +119 -0
  135. package/dist/test/watch.test.d.ts +1 -0
  136. package/dist/test/watch.test.js +227 -0
  137. package/dist/ui/server/app.d.ts +3 -0
  138. package/dist/ui/server/app.js +98 -0
  139. package/dist/ui/server/db.d.ts +82 -0
  140. package/dist/ui/server/db.js +333 -0
  141. package/dist/ui/server/serve.d.ts +13 -0
  142. package/dist/ui/server/serve.js +80 -0
  143. package/dist/ui/server/static.d.ts +5 -0
  144. package/dist/ui/server/static.js +55 -0
  145. package/dist/ui/shared/types.d.ts +263 -0
  146. package/dist/ui/shared/types.js +8 -0
  147. package/package.json +52 -0
  148. package/web/assets/index-C7nF068F.css +1 -0
  149. package/web/assets/index-mzSArcnQ.js +11 -0
  150. package/web/assets/play-latin-400-normal-GKW-4YV7.woff2 +0 -0
  151. package/web/assets/play-latin-700-normal-DyPlLDbb.woff2 +0 -0
  152. package/web/index.html +14 -0
  153. package/web/logo.svg +6 -0
@@ -0,0 +1,197 @@
1
+ # Roster
2
+
3
+ Add or retune agents in `spf.config.yaml`. There's nothing to generate — `spf
4
+ init` seeds a starter `.spf/spf.config.yaml` with a couple of commented
5
+ examples; everything else is inherited from the packaged defaults until you
6
+ override it. `spf doctor` always shows what's actually in effect and where it
7
+ came from.
8
+
9
+ ## The rule
10
+
11
+ **One agent, one prompt, one purpose.** A roster entry defines who an agent
12
+ *is*: its model, thinking level, and exactly one system prompt plus one user
13
+ prompt. How it's *used* — the output type, a per-call prompt override —
14
+ lives at the chain call site, never here. See `authoring_chains.md`.
15
+
16
+ ## Retune model or thinking
17
+
18
+ ```yaml
19
+ agents:
20
+ - name: builder
21
+ model: anthropic/claude-sonnet-4-6 # ALWAYS provider/model-id
22
+ thinking: high # was medium
23
+ ```
24
+
25
+ Write the model as `provider/model-id`, never a bare id — SPF has no live
26
+ model catalog to check against (that was `pi --list-models`; Flue has no
27
+ public equivalent), so an unqualified or malformed pattern is only caught by
28
+ shape at `agents.validate()` time, and a genuinely wrong id surfaces at the
29
+ first real dispatch. Thinking levels: `off | minimal | low | medium | high |
30
+ xhigh | max` — inert (no error, no effect) on a model that isn't a reasoning
31
+ model.
32
+
33
+ **This is Flue's model vocabulary, not every backend's.** An agent with
34
+ `coding_agent: claude_code` (see below) writes `model:` in Claude Code's own
35
+ vocabulary instead — a bare alias (`sonnet`, `opus`) or a full model name —
36
+ never `provider/model-id`. `agents.validate()` knows which shape check
37
+ applies from `coding_agent`, so mixing the two conventions in the same
38
+ roster is fine as long as each agent's own `model:` matches its own
39
+ backend.
40
+
41
+ **A model change means a fresh session.** `agent_map.json` records the
42
+ model each agent's Flue conversation was created with. When a joined run
43
+ (`--adw-id`) finds the config's model no longer matches, that agent starts a
44
+ **new** conversation rather than resuming — never a bad resume. Thinking
45
+ changes don't invalidate a session; model changes do.
46
+
47
+ ## Recolor an agent's lane
48
+
49
+ ```yaml
50
+ agents:
51
+ - name: builder
52
+ color: "#22d3ee" # hex; the starter roster ships violet/cyan/amber/red/magenta
53
+ ```
54
+
55
+ Cosmetic, safe mid-project — rides the `agent_start` event and the
56
+ `agent_sessions` row, so the UI picks it up on the next run without
57
+ touching past sessions. Omit it for the UI's fallback palette.
58
+
59
+ ## Coding agent backends
60
+
61
+ `coding_agent: flue` (the default) or `coding_agent: claude_code` — set per
62
+ agent or in `defaults`. Both read the same roster shape (`tools:`,
63
+ `writes:`, `thinking:`), but each backend interprets a couple of fields in
64
+ its own vocabulary:
65
+
66
+ ```yaml
67
+ agents:
68
+ - name: builder
69
+ coding_agent: claude_code
70
+ model: sonnet # Claude Code's own alias/name, NOT provider/model-id
71
+ thinking: high
72
+ tools: [read, edit, bash] # same canonical names either way — see "Retune tools" below
73
+ ```
74
+
75
+ `claude_code` shells out to your own installed `claude` CLI (`spf doctor`
76
+ checks it's on `PATH`) — it needs no separate npm install, since SPF never
77
+ depends on it directly. A missing `ANTHROPIC_API_KEY` is informational, not
78
+ a hard failure: Claude Code also supports its own `claude login` flow.
79
+
80
+ **Pointing a `claude_code` agent at Ollama** — local or cloud — needs no
81
+ config at all, just environment variables set before you run `spf` (Claude
82
+ Code's CLI reads them itself):
83
+
84
+ ```bash
85
+ # local Ollama
86
+ export ANTHROPIC_BASE_URL=http://localhost:11434
87
+ export ANTHROPIC_AUTH_TOKEN=ollama # any non-empty value; Ollama doesn't check it locally
88
+
89
+ spf build "..." --config .spf/spf.config.yaml # any chain naming a claude_code agent
90
+ ```
91
+
92
+ For Ollama's cloud offering, point `ANTHROPIC_BASE_URL` at that instead and
93
+ set `ANTHROPIC_AUTH_TOKEN` to a real Ollama Cloud API key. This is exactly
94
+ the same environment-variable pass-through every agent already gets — no
95
+ SPF-specific plumbing, no `provider:` config section to write.
96
+
97
+ ## Retune tools
98
+
99
+ Known tool names: `read`, `bash`, `edit`, `write`, `grep`, `glob` (`find` is
100
+ accepted as an alias for `glob`). These are canonical across BOTH backends —
101
+ Flue maps them to its own lowercase tool functions, Claude Code maps them to
102
+ its own capitalized names (`Read`, `Bash`, ...); a roster entry never has to
103
+ say which. `ls` is a recognized name with **no built-in on either backend**
104
+ — bash/glob cover it, so listing it is harmless but it never mounts as its
105
+ own tool.
106
+
107
+ ```yaml
108
+ defaults:
109
+ tools: [read, bash, edit, write, grep, glob]
110
+
111
+ agents:
112
+ - name: reviewer
113
+ tools: [read, grep, glob, bash, write] # explicit list wins over defaults
114
+ ```
115
+
116
+ **Resolution:** the agent's own list wins → else it inherits `defaults.tools`
117
+ → else unset, meaning every built-in tool. An empty list is not "all tools";
118
+ it's a tool-less agent, and it will stall. `agents.validate()` rejects an
119
+ unknown tool name at startup, before anything spawns.
120
+
121
+ Narrow by role, not by reflex:
122
+
123
+ - Any agent producing a `context_handoff/` artifact needs **`write`** — the
124
+ session runtime under `data_dir` is always writable regardless of
125
+ `writes:` (below), but the agent still needs the `write` tool itself to
126
+ use that grant.
127
+ - Withhold `edit`/`write` only where the restriction *is* the guarantee — a
128
+ reviewer's contract is "change nothing," so dropping `edit` makes that
129
+ structural rather than merely prompted. (`writes: []`, below, is what
130
+ actually enforces it either way.)
131
+ - Recon agents get the full read surface (`read`, `grep`, `glob`) — cheaper
132
+ and more legible in the trace than the equivalent `bash` calls.
133
+
134
+ `harness_engineering` has **no analogue on any current backend** and must
135
+ stay empty — `agents.validate()` fails loudly if any entry is non-empty.
136
+ (Flue's equivalent surface — extra tools, subagents — is added via
137
+ `useTool()`/`useSandbox()` inside `src/core/agent_flue.ts` itself; Claude
138
+ Code's is an MCP server or plugin passed to `agent_cc.ts`. Either way it's
139
+ an engine change, not a config one; see `authoring_chains.md`.)
140
+
141
+ ## Add a new agent
142
+
143
+ Three steps, all required — skipping any one fails `agents.validate()` at
144
+ startup, before anything spawns:
145
+
146
+ 1. **Prompts.** Create `<name>/system.md` (Purpose + Instructions — static
147
+ identity only) and `<name>/user.md` (one h3 per incoming datum —
148
+ `{{prompt}}`, `{{previous_envelope}}`, `{{context_handoff_dir}}` — then
149
+ the task, then a `## Report` section showing the exact output JSON).
150
+ Copy an existing pair as the shape. If you're overriding in `.spf/`, put
151
+ them under `.spf/prompt_engineering/<name>/`.
152
+ 2. **Config entry.** Name, purpose, prompt refs, plus anything that differs
153
+ from `defaults`.
154
+ 3. **An output type.** Pick an existing envelope type or add one — see
155
+ `authoring_chains.md`. The user prompt's `## Report` section must show
156
+ exactly that JSON shape.
157
+
158
+ Then name the agent in a chain's `REQUIRED_AGENTS` and call it.
159
+
160
+ ## Write permissions — `writes` and `protected_files`
161
+
162
+ `tools` cannot express a safety boundary: `bash` runs anything (including
163
+ `git checkout`, which discards uncommitted work) and `write` reaches any
164
+ path, not only the one report file an agent was granted it for.
165
+ `core/permissions.ts` enforces the real boundary, after the fact, by
166
+ fingerprinting the working tree's change-set before an agent runs and
167
+ comparing it after. A path that was modified beforehand and is clean
168
+ afterward has been **reverted** — a reversion counts as a modification.
169
+ That's what catches the `git checkout` case.
170
+
171
+ ```yaml
172
+ defaults:
173
+ protected_files: [.spf/, spf.config.yaml] # the machinery that grades an agent's work
174
+
175
+ agents:
176
+ - name: builder # no `writes` key -> unrestricted, minus protected_files
177
+ - name: scout
178
+ writes: [] # no repo writes; findings still land in context_handoff/
179
+ - name: planner
180
+ writes: [specs/]
181
+ - name: documenter
182
+ writes: [app_docs/, docs/, "**/*.md", "*.md"]
183
+ ```
184
+
185
+ A breach is **not** a gate violation — a gate is for work an agent can be
186
+ asked to redo, but a write already happened. Instead: everything the agent
187
+ introduced outside its allowlist is rolled back (tracked files via `git
188
+ checkout --`, untracked files by deletion), anything that was *already*
189
+ dirty before the agent ran is left untouched (the operator's own
190
+ uncommitted work is not this module's to discard), and the phase fails
191
+ naming every path and what happened to it.
192
+
193
+ **The session runtime under `data_dir` is always writable, for every
194
+ agent**, regardless of `writes:`. `writes: []` means read-only with respect
195
+ to the repo, not unable to write its own report.
196
+
197
+ Full field-by-field spec: `references/config.md`.
@@ -0,0 +1,92 @@
1
+ # Run a Chain
2
+
3
+ Run a workflow and report on it. **You run and observe — you never step
4
+ into the process or do the work yourself.**
5
+
6
+ ## Step 0 — translate the request
7
+
8
+ **Read [how_to_prompt_for_the_eng.md](how_to_prompt_for_the_eng.md) before
9
+ you launch anything.** The prompt you pass is read by every agent in the
10
+ chain, so it gets written deliberately. This cookbook starts once you have
11
+ that prompt.
12
+
13
+ ## Launch
14
+
15
+ `spf list` shows every chain this install knows, its phases, and what it
16
+ requires — the names are shape, not a fixed menu; read the actual output.
17
+
18
+ ```bash
19
+ spf <chain> "<prompt>" # e.g. spf plan-build-test "add a /health endpoint"
20
+ spf <chain> requests/health.md # prompt can be a file path instead of inline text
21
+ spf <chain> "implement the plan" --adw-id a1b2c3d4 # join an existing session
22
+ spf <chain> "..." --config path/to/other.config.yaml # a non-default roster
23
+ spf <chain> "..." --cwd /path/to/other-repo # run against a different repo
24
+ ```
25
+
26
+ Launch in the background so you can poll while it works. `spf` prints the
27
+ `adw_id` on startup — capture it, everything else keys off it.
28
+
29
+ ### Naming a roster
30
+
31
+ The chain says *what runs*; the config says *who runs it*. If the engineer
32
+ references a roster, a config file, or a model tier, pass `--config` — never
33
+ fall through to the default silently. Two things bite:
34
+
35
+ - **Never swap rosters on your own.** A different roster is a different
36
+ cost and a different result. If the default's model looks wrong, say so
37
+ and let the engineer choose.
38
+ - **Switching rosters mid-session breaks resumption.** `agent_map.json`
39
+ records the model each agent's session was created with; a joined run
40
+ whose config now names a different model starts that agent **fresh**
41
+ instead of resuming — deliberate (a bad resume is worse), but it means
42
+ "plan on one roster, build on another" costs the builder its accumulated
43
+ context. Say so when you report it.
44
+
45
+ `--adw-id` is optional on every chain. Given one, the run joins that session
46
+ if it exists, or creates it pinned to exactly that id — same session
47
+ directory, same `context_handoff/`, envelopes appended, each agent resumes
48
+ its own Flue conversation via `agent_map.json`. That's how you chain runs:
49
+ plan under one id, then build under the same id.
50
+
51
+ ## Observe
52
+
53
+ ```bash
54
+ spf sessions [--limit N] # recent runs
55
+ spf phases <adw_id> # phase-by-phase status for one run
56
+ spf events <adw_id> [--follow] # the trace, live-tailable
57
+ spf abort <adw_id> # signal a stuck run's process to stop
58
+ ```
59
+
60
+ `spf events --follow` polls the same rowid cursor the visualizer does — safe
61
+ to leave running. `spf phases` marks each phase ✓/✗/… — remember **every
62
+ phase defaults to fail**, so `✗` may mean it never completed, and `…` means
63
+ still running, not stuck.
64
+
65
+ For a visual view of the same data: `spf ui` — opens a browser, renders
66
+ sessions as cards and runs as swim lanes, phases and tool calls drill in.
67
+
68
+ ## When a run is stuck
69
+
70
+ A hung agent produces no events at all, so the trace goes quiet rather than
71
+ red.
72
+
73
+ ```bash
74
+ spf phases <adw_id> # which phase is still "running"
75
+ spf abort <adw_id> # stop it
76
+ ```
77
+
78
+ `spf abort` sends `SIGTERM` to the OS process running the chain (there's no
79
+ separate per-agent handle to target from a different CLI invocation — Flue
80
+ runs in-process, so stopping the run means stopping that process). A killed
81
+ run finalizes its own trace: it lands on `fail` with its process rows
82
+ closed, never left claiming `running` forever.
83
+
84
+ ## Report
85
+
86
+ Tell the engineer, in order: which chain and which roster you launched (name
87
+ the config whenever it wasn't the default), which phase is running now or
88
+ which failed, phase statuses in sequence, and for a failure the gate
89
+ violations or the error verbatim, from `spf phases`/`spf events`. Don't dress
90
+ up a partial run as a success.
91
+
92
+ Full trace schema and what each column means: `references/observability.md`.
@@ -0,0 +1,111 @@
1
+ # SPF Overview
2
+
3
+ The map, not the procedure — read this once to know how the pieces fit
4
+ before touching any of the other cookbooks.
5
+
6
+ ## What SPF is
7
+
8
+ SPF runs **ADWs**: TypeScript chains that own sequencing, retries, and
9
+ acceptance, calling bounded agent phases and deterministic code phases in
10
+ between. "Agent proposes, code disposes" — an agent's response is a claim,
11
+ and code (a gate, a quality check, a permissions check) verifies it before
12
+ the chain moves on.
13
+
14
+ SPF is a global CLI (`npm i -g @gr8ful/spf`), not a template stamped into
15
+ the target repo. It ships:
16
+
17
+ - a packaged **default roster** (`spf.config.yaml`) and **default prompts**,
18
+ usable with zero setup;
19
+ - an optional per-repo **`.spf/` override directory** (`spf init` seeds it) —
20
+ merged on top of the packaged defaults, field by field;
21
+ - an optional per-repo **`.spf/data/`** runtime home for the sqlite trace and
22
+ session files, created on first run, always gitignored.
23
+
24
+ There is no `adws/` tree to stamp, no `bun install`. The default
25
+ coding-agent backend is **Flue** (`@flue/runtime`), an in-process agent
26
+ harness with no subprocess and no separate CLI to have on `PATH`.
27
+ `coding_agent: claude_code` is a second backend — it shells out to your own
28
+ installed `claude` CLI instead, for repos that want to run agents on
29
+ Claude Code (including pointed at a local or cloud Ollama server). See
30
+ `roster.md`.
31
+
32
+ ## Layout, once you run something
33
+
34
+ ```
35
+ <repo>/
36
+ .spf/ # optional — only if you ran `spf init`
37
+ spf.config.yaml # your overrides, merged over the built-in defaults
38
+ .spf/data/ # runtime — always gitignored
39
+ spf.db # the trace: sessions, phases, events, envelopes, gates
40
+ flue.db # Flue's own conversation store (session resumption)
41
+ sessions/<adw_id>/
42
+ agent_map.json # agent name -> its Flue conversation id + model
43
+ context_handoff/ # the ONE place agents hand files to each other
44
+ <agent_name>/
45
+ prompts/ # exact system.md + user.md sent, saved before the call
46
+ envelope.json # the final parsed, validated response
47
+ ```
48
+
49
+ `spf doctor` prints exactly what resolved and from where — config paths,
50
+ prompt paths, the trace db path — so "is my override even taking effect" is
51
+ always a one-command answer.
52
+
53
+ ## The phase model
54
+
55
+ Three phase kinds, all run through the one primitive, `run.phase(params, fn)`:
56
+
57
+ - **`engineer`** — captures human input; no agent, no gate.
58
+ - **`agent`** — `ph.call({output_type, prompt, gates})`: sends a prompt,
59
+ gets back a typed envelope, verifies it against every declared gate.
60
+ A parse failure or gate violation triggers a bounded same-session
61
+ correction, never a cold restart.
62
+ - **`code`** — deterministic work: running a quality suite, capturing a git
63
+ diff, committing. If you can write down the command, it's `code`, not an
64
+ agent that has to rediscover it every run.
65
+
66
+ Every phase defaults to `fail`; only a clean exit earns `success`. The chain
67
+ calls `run.finish(accepted, reason)` exactly once at the end — separate from
68
+ per-phase success, because a red test suite that *ran correctly* is still a
69
+ successful phase; whether the whole run is *accepted* is a different
70
+ question.
71
+
72
+ ## Envelopes
73
+
74
+ Every agent call declares a concrete output type — an envelope schema built
75
+ with Valibot's `envelopeType()`. The agent's final JSON is parsed against
76
+ exactly that type; there are no untyped handoffs. The schema, the JSON shown
77
+ in that agent's `user.md` `## Report` section, and the `output_type:` named
78
+ at the call site are the same fact in three places — the "synced triad."
79
+ Full contract: `references/handoff.md`.
80
+
81
+ ## Running a chain
82
+
83
+ ```bash
84
+ spf list # every chain, its phases, what it needs
85
+ spf <chain-name> "<prompt or path/to/prompt.md>" [--config <path>] [--adw-id <id>] [--cwd <dir>]
86
+ spf doctor # confirm the roster/config/quality all resolve first
87
+ spf sessions # recent runs
88
+ spf phases <adw_id> # one run's phase-by-phase status
89
+ spf events <adw_id> --follow # the live trace
90
+ spf ui # the visualizer, browser-based
91
+ ```
92
+
93
+ `spf <chain-name> ...` and `spf run <chain-name> ...` are identical — the bare
94
+ form exists because naming the chain *is* choosing what runs.
95
+
96
+ ## When you've finished reading this
97
+
98
+ Startup discipline still applies: run `spf list`, don't volunteer more state
99
+ than the engineer asked for, and read `how_to_prompt_for_the_eng.md` before
100
+ launching anything.
101
+
102
+ ## Where to go next
103
+
104
+ | Need | Go to |
105
+ |---|---|
106
+ | Launch a chain, watch it, report on it | `cookbooks/run_adw.md` (after `how_to_prompt_for_the_eng.md`) |
107
+ | Retune the roster, add an agent | `cookbooks/roster.md` |
108
+ | Add or extend a chain | `cookbooks/authoring_chains.md` |
109
+ | Envelope/gate/session contract | `references/handoff.md` |
110
+ | Trace schema, spend vs. context | `references/observability.md` |
111
+ | Full config field reference | `references/config.md` |
@@ -0,0 +1,188 @@
1
+ # Config Reference
2
+
3
+ The full `spf.config.yaml` spec: every field, how defaults merge, and how
4
+ model / thinking / tools map onto each coding-agent backend (Flue by
5
+ default, or Claude Code). The schema is Valibot-enforced —
6
+ `agents.validate()` refuses a bad config before anything spawns — so this is
7
+ semantics and gotchas, not a shape you have to memorize by hand; `spf doctor`
8
+ always shows the resolved, merged result for the repo you're in.
9
+
10
+ ## Resolution order
11
+
12
+ 1. The packaged built-in default (`assets/defaults/spf.config.yaml` inside
13
+ the installed CLI).
14
+ 2. `.spf/spf.config.yaml` in the target repo, if present — merged on top,
15
+ field by field (`defaults`/`observability`/`quality` merge key-by-key;
16
+ `agents` merges by `name`: a matching name patches that entry, a new name
17
+ appends).
18
+ 3. An explicit `--config <path>` replaces both — standalone, no built-in
19
+ underneath it.
20
+
21
+ `spf init` seeds step 2 with a commented starter; omitting it entirely means
22
+ running off pure built-ins, which is a fully supported, valid state.
23
+
24
+ ## Shape
25
+
26
+ ```yaml
27
+ defaults:
28
+ coding_agent: flue # the only implemented value
29
+ model: google/gemini-3.6-flash # ALWAYS provider/model-id
30
+ thinking: medium
31
+ tools: [read, bash, edit, write, grep, glob]
32
+ protected_files: [.spf/, spf.config.yaml]
33
+ data_dir: .spf/data
34
+
35
+ observability:
36
+ db: .spf/data/spf.db
37
+ poll_ms: 500
38
+
39
+ quality:
40
+ checks:
41
+ - {name: test, operation: build, argv: ["npm", "test"], timeout_seconds: 600}
42
+ suites:
43
+ test: [test]
44
+ all: [test]
45
+
46
+ agents:
47
+ - name: planner
48
+ model: google/gemini-3.6-flash
49
+ thinking: high
50
+ color: "#a78bfa"
51
+ purpose: Turn a request into a plan the builder can implement without asking questions.
52
+ prompt_engineering:
53
+ system: planner/system.md
54
+ user: planner/user.md
55
+ writes: [specs/]
56
+ tools: [read, grep, glob, bash, write]
57
+ ```
58
+
59
+ ## Fields
60
+
61
+ ### `defaults`
62
+
63
+ | Field | Type | Meaning |
64
+ |---|---|---|
65
+ | `coding_agent` | `"flue"` \| `"claude_code"` | Which backend runs the agent. Default `flue`. `claude_code` shells out to your own installed `claude` CLI — `spf doctor` checks it's on `PATH`. |
66
+ | `model` | string | Vocabulary depends on `coding_agent`: Flue wants `provider/model-id`; Claude Code wants its own bare alias/full name (`sonnet`, `claude-sonnet-5`, ...). Default `google/gemini-3.6-flash`. |
67
+ | `thinking` | enum | `off\|minimal\|low\|medium\|high\|xhigh\|max`. Default `medium`. On a `claude_code` agent this maps to `--effort` (`off`/`minimal` both floor to Claude Code's own minimum — it has no true "disabled" level for a headless run). |
68
+ | `color` | hex string | Lane color fallback for agents that don't set their own. |
69
+ | `harness_engineering` | string[] | **Must stay `[]`** — no analogue on any current backend; a non-empty entry fails validate(). |
70
+ | `tools` | string[] \| null | Roster-wide allowlist. Unset/null = every built-in tool usable. |
71
+ | `protected_files` | string[] | Paths no agent may touch unless named in its own `writes`. Default `[".spf/", "spf.config.yaml"]`. |
72
+ | `data_dir` | path | Runtime home, repo-relative. Default `.spf/data`. |
73
+
74
+ ### `observability`
75
+
76
+ | Field | Type | Meaning |
77
+ |---|---|---|
78
+ | `db` | path | The trace sqlite db. Default `.spf/data/spf.db`. |
79
+ | `poll_ms` | int | UI live-poll cadence. Default `500`. |
80
+
81
+ ### `quality`
82
+
83
+ `checks[]`: `{name, area: "frontend"|"backend", operation: "lint"|"typecheck"|"build", argv: string[], timeout_seconds}`.
84
+ `suites`: `{suite_name: [check_name, ...]}`. No packaged default suite exists
85
+ on purpose — a chain that needs one and finds it unconfigured fails loudly
86
+ at `agents.validate()` time, not with a placeholder green. Adding a check
87
+ whose result is really a test run, not a lint/typecheck/build, still needs
88
+ one of those three `operation` values — it only affects how the trace/UI
89
+ labels the check, never what actually runs — `build` is the closest fit.
90
+
91
+ ```yaml
92
+ quality:
93
+ checks:
94
+ - { name: typecheck, operation: typecheck, argv: ["npm", "run", "typecheck"], timeout_seconds: 60 }
95
+ - { name: build, operation: build, argv: ["npm", "run", "build"], timeout_seconds: 300 }
96
+ - { name: test, operation: build, argv: ["npm", "test"], timeout_seconds: 300 }
97
+ suites:
98
+ test: [test] # what build-test/plan-build-test's fix loop runs
99
+ all: [typecheck, build, test] # what plan-build-test-quality/quality run
100
+ ```
101
+
102
+ A full worked example, including the `defaults.coding_agent`/`watch:`
103
+ sections: `docs/examples/node-typescript.spf.config.yaml` in the spf
104
+ repo (or wherever this skill's own package is installed from).
105
+
106
+ ### `agents[]`
107
+
108
+ | Field | Required | Meaning |
109
+ |---|---|---|
110
+ | `name` | yes | The identifier chains use. Chains name agents, never models. |
111
+ | `prompt_engineering.system` / `.user` | yes | Paths to the two prompt files. Resolved against the repo root, then `.spf/`, then `.spf/prompt_engineering/`, then the packaged assets — first hit wins; a total miss throws listing every path tried. |
112
+ | `purpose` | no | One sentence; should match the system prompt's stated purpose. |
113
+ | `coding_agent`, `model`, `thinking`, `color`, `harness_engineering` | no | Override the matching `defaults` key. |
114
+ | `tools` | no | Allowlist. Omitting it means all tools usable. A capability list, not a boundary — see `writes`. |
115
+ | `writes` | no | What this agent may modify **in the repo**, enforced after every call. `undefined`/`null` = unrestricted (still barred from `protected_files`); `[]` = no repo writes; a list = only those paths (trailing `/` = directory prefix, `*` = one path segment, `**` = crosses segments, anything else = exact path). |
116
+
117
+ Output types are deliberately absent from config: an entry defines who an
118
+ agent *is*; the call site defines how it's *used*.
119
+
120
+ ## Model resolution
121
+
122
+ **For `coding_agent: flue` (the default):** always write `model` as
123
+ `provider/model-id`. There is no live catalog to validate against (Flue has
124
+ no public model-registry API) — `agents.ts` checks only the static shape at
125
+ `agents.validate()` time; a genuinely wrong provider or id surfaces at the
126
+ first real dispatch instead. Provider credentials come from the
127
+ environment, matching the provider you named (`GEMINI_API_KEY`/
128
+ `GOOGLE_API_KEY` for `google/...`, `ANTHROPIC_API_KEY` for `anthropic/...`,
129
+ etc.) — `spf doctor` checks the common ones are set.
130
+
131
+ **For `coding_agent: claude_code`:** write `model` in Claude Code's own
132
+ vocabulary — a bare alias (`sonnet`, `opus`) or a full model name — never
133
+ `provider/model-id`. `agents.validate()` only checks it's non-empty; a
134
+ genuinely wrong name surfaces at the first real dispatch, same as Flue.
135
+ Credentials come from whatever the `claude` CLI itself is authenticated
136
+ with (`ANTHROPIC_API_KEY`, or its own `claude login` flow) — `spf doctor`
137
+ treats a missing key as informational for this backend, not a failure.
138
+
139
+ The resolved model is recorded per agent in `agent_map.json`, mirrored in
140
+ the `agent_sessions` table. **Changing an agent's model invalidates its
141
+ session** — a joined run starts that agent fresh instead of resuming.
142
+
143
+ ## Tools
144
+
145
+ | Tool | Purpose |
146
+ |---|---|
147
+ | `read` | read file contents |
148
+ | `bash` | execute bash commands |
149
+ | `edit` | find/replace edits |
150
+ | `write` | create/overwrite files |
151
+ | `grep` | search file contents |
152
+ | `glob` (alias: `find`) | find files by pattern |
153
+ | `ls` | recognized name, **no built-in on either backend** — harmless to list, never mounts |
154
+
155
+ These names are canonical across backends — a roster entry never says
156
+ which; each backend module (`agent_flue.ts`, `agent_cc.ts`) maps them to
157
+ its own tool vocabulary (Flue's lowercase functions, Claude Code's
158
+ capitalized `Read`/`Bash`/...).
159
+
160
+ **Resolution order:** an agent's own `tools` wins → else `defaults.tools` →
161
+ else unset (all tools usable). An empty list is a tool-less agent, and it
162
+ will stall — not "all tools."
163
+
164
+ ## Write permissions — `writes` and `protected_files`
165
+
166
+ Full mechanism and rationale: `roster.md`'s "Write permissions" section.
167
+ Short version: `permissions.ts` fingerprints the working tree's diff before
168
+ and after an agent runs; anything outside its allowlist is rolled back
169
+ (reverting a previously-clean file counts as a change; a file that was
170
+ already dirty is left alone) and the phase fails, naming every path. This is
171
+ not a gate — it's checked in code, not by asking the model to redo work.
172
+
173
+ ## Harness engineering — no longer applicable
174
+
175
+ Kept as a field for config-shape stability, but it must be empty. Each
176
+ backend's own equivalent (Flue: extra tools/subagents via `useTool()`/
177
+ `useSandbox()`; Claude Code: an MCP server or plugin) is an engine-level
178
+ change made inside that backend's own module, not config — see
179
+ `authoring_chains.md`.
180
+
181
+ ## Pointing `claude_code` at Ollama
182
+
183
+ No config section for this — it's an environment-variable recipe, since
184
+ `agent_cc.ts` passes the operator's environment straight through to the
185
+ `claude` subprocess, exactly like every other env var. Set
186
+ `ANTHROPIC_BASE_URL`/`ANTHROPIC_AUTH_TOKEN` (local or cloud Ollama) before
187
+ running `spf`; see `roster.md`'s "Coding agent backends" section for the
188
+ exact commands.