@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 IndyDevDan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,321 @@
1
+ # Super Portable Factory
2
+
3
+ > **Repeatable agents-plus-code workflows, as a global CLI you run against any repo.**
4
+ > Deterministic TypeScript owns the graph. Coding agents are bounded nodes inside it.
5
+
6
+ A software factory does one thing: it gives you more leverage on your prompt. How much leverage depends entirely on what you invest in it. At the low end you chain two agents together and hope. At the high end you build a system of agents plus code that runs without you, and does the job about as well as you would.
7
+
8
+ Everyone can get an agent to write code once. Almost nobody gets the same result twice. This fixes that by moving the control plane out of the prompt and into TypeScript. A chain script owns sequencing, retries, and acceptance. Agents work inside named phases. Typed JSON envelopes carry context across the seams. Every event streams into SQLite while it is still happening. **Agent proposes, code disposes.**
9
+
10
+ ---
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm i -g @gr8ful/spf
16
+ ```
17
+
18
+ No Bun, no separate coding-agent binary to install first, no template stamped into your repo. `spf` ships with a packaged default agent roster and default prompts — it runs against any repo with zero setup, and only writes files into that repo if you explicitly ask it to (`spf init`, `spf install-skill`).
19
+
20
+ ```bash
21
+ cd your-repo
22
+ spf doctor # confirm everything resolves — paths, roster, provider keys
23
+ spf scout "describe this repo" # a real, read-only run, no setup required
24
+ ```
25
+
26
+ `spf doctor` is the answer to "why did nothing happen": it resolves and prints every path, validates the whole roster and every quality suite, and checks the things that fail silently otherwise — a missing provider key, a quality check whose binary isn't on `PATH`, a stale `protected_files` pattern.
27
+
28
+ ### Customizing a repo
29
+
30
+ ```bash
31
+ spf init # seed .spf/spf.config.yaml — override only what you want to change
32
+ spf list # every chain this install knows, its phases, what it needs
33
+ ```
34
+
35
+ `spf init` writes a small starter `.spf/spf.config.yaml`, commented, that merges on top of the packaged built-ins field by field — override one model, one prompt, one quality check, and everything else stays inherited. Nothing here needs to exist for `spf` to run; it's how you make one repo's roster diverge from the defaults.
36
+
37
+ ### Local development
38
+
39
+ Working from a clone instead of the published package:
40
+
41
+ ```bash
42
+ git clone git@github.com:seesharpguy/super-portable-software-factory.git
43
+ cd super-portable-software-factory
44
+ npm install
45
+ npm run build # builds both the CLI (dist/) and the trace visualizer SPA (web/)
46
+ npm link # makes `spf` resolve globally to THIS checkout
47
+ ```
48
+
49
+ `npm link` beats a real `npm i -g` here: it symlinks the global `spf` bin into this checkout, so every later `npm run build` takes effect immediately, no re-linking. Run it from any repo you want to test against.
50
+
51
+ ```bash
52
+ npm run typecheck # tsc --noEmit
53
+ npm test # node --test against dist/**/*.test.js (runs build:cli first)
54
+ npm run size-check # tarball size gate — what `npm pack` would actually publish
55
+ ```
56
+
57
+ For a one-off check without touching global npm state at all, run the built CLI directly: `node dist/cli/bin.js doctor --cwd /path/to/repo`. When you're done with the linked version, `npm unlink -g @gr8ful/spf` removes it.
58
+
59
+ ---
60
+
61
+ ## Why this exists
62
+
63
+ Hand a capable model your whole SDLC and you get a machine with no seams. There is no phase boundary, so you cannot say which step failed. There is no acceptance criterion you can name, so "done" means "the agent stopped talking." A retry is a cold start that throws away everything the agent just learned. The only trace is a transcript you have to read like a novel. Run it twice, get two different systems.
64
+
65
+ The fix is not a better prompt. The fix is deciding, deliberately, that **code owns sequencing, retries, and acceptance, and the agent owns only the work inside one bounded phase**. Everything else falls out of that one line. Phases become the unit of the trace. Envelopes become the only way context crosses a seam. Gates become the definition of done. A correction becomes cheaper than a restart, because the session is still alive.
66
+
67
+ ### Agents are great. You do not always need one.
68
+
69
+ This is the part most engineers are going to skip, and pay for later.
70
+
71
+ Code costs nothing. It runs at the speed of light. You can change it in a second. And you actually own it, which is not true of any model you are renting by the token.
72
+
73
+ So when the invocation is already known, write it down. `npm test` is not a judgement call. Neither is `tsc --noEmit`. An agent rediscovering your test runner burns a context window to learn what a subprocess already knows, and it charges you for the privilege every single run. Worse, it puts a passing test suite into a context window, which buys you nothing at all.
74
+
75
+ Agents are for the parts that need reading and deciding. Everything else is a `kind: "code"` phase — SPF's own `quality:` config section names the commands, and a chain runs them as code, not as an agent's rediscovery project. When code fails, the failure comes back to the builder as an envelope, through the same door an agent's report would have used. The repair loop is identical. You just stopped paying an agent to do arithmetic.
76
+
77
+ The bill for skipping this is not only tokens. It is cost, speed, and consistency, and you pay it on run one hundred and run one thousand, not on run one.
78
+
79
+ > *Same models. Same prompts. The difference is who owns the loop.*
80
+
81
+ ---
82
+
83
+ ## The agent roster
84
+
85
+ `spf.config.yaml` answers one question per entry: who is this agent. One agent, one prompt, one purpose. `spf init` seeds `.spf/spf.config.yaml`, merged on top of the packaged defaults — an entry only states what differs.
86
+
87
+ ```yaml
88
+ defaults:
89
+ coding_agent: flue # the engine agents run on
90
+ model: google/gemini-3.6-flash # provider/model-id — a bare id can match several providers
91
+ thinking: medium # off | minimal | low | medium | high | xhigh | max
92
+ protected_files: [.spf/, spf.config.yaml] # no agent may edit the machinery that grades it
93
+ data_dir: .spf/data
94
+
95
+ agents:
96
+ - name: planner
97
+ model: fireworks/accounts/fireworks/models/kimi-k3
98
+ thinking: high
99
+ color: "#a78bfa" # this agent's lane color in the trace UI
100
+ purpose: Turn a request into a plan the builder can implement without asking questions.
101
+ prompt_engineering:
102
+ system: planner/system.md
103
+ user: planner/user.md
104
+ writes: [specs/] # the plan is all it may leave in the repo
105
+ ```
106
+
107
+ Five starter agents ship in the box: `planner`, `builder`, `scout` (read-only recon), `reviewer`, and `documenter`. There is no tester, because running a suite is a known command and therefore code, not an agent's job.
108
+
109
+ Every agent gets its own model, thinking level, prompts, and tools. Give the planner a frontier model and the builder a cheap fast one. Give the reviewer no ability to write code at all.
110
+
111
+ **`tools` is a capability list. `writes` is the boundary.** They are not the same thing: `bash` runs anything, including `git checkout`, and `write` reaches any path. So "this agent changes nothing" is enforced in code, after every call, by comparing the repo before and after. Unauthorized changes are rolled back and the phase fails. A read-only agent is read-only with respect to your repo, never unable to write its own report.
112
+
113
+ Config defines who an agent **is**. The chain call site defines how it is **used**. That split is what lets one agent serve many different calls. **Chains name agents, never models.**
114
+
115
+ ### A second backend: Claude Code
116
+
117
+ Set `coding_agent: claude_code` on any agent (or in `defaults`) to run it on your own installed [Claude Code](https://claude.com/product/claude-code) CLI instead of Flue — `spf doctor` checks it's on `PATH`. Model names follow Claude Code's own vocabulary (a bare alias like `sonnet`, not `provider/model-id`); everything else — `tools`, `writes`, `thinking` — stays the same shape. Pointing a `claude_code` agent at a local or cloud [Ollama](https://ollama.com) server needs no config at all — just `ANTHROPIC_BASE_URL`/`ANTHROPIC_AUTH_TOKEN` set before you run `spf`, since Claude Code's CLI reads those itself.
118
+
119
+ ---
120
+
121
+ ## Phases: three lanes, one primitive
122
+
123
+ Every run is a sequence of phases, and every phase is the same async scope no matter who owns it.
124
+
125
+ ```ts
126
+ const REQUIRED_AGENTS = ["planner", "builder"]; // names, never models
127
+
128
+ const cfg = agents.loadConfig(configPaths);
129
+ agents.validate(cfg, REQUIRED_AGENTS, REQUIRED_SUITES, cwd); // a missing agent fails before anything spawns
130
+ const run = session.ensure(cfg, adwId, cwd); // pin-or-create the session
131
+
132
+ const plan = await run.phase(
133
+ makePhaseParams({ name: "plan", kind: "agent", owner: "planner", description: "Turn the request into a plan" }),
134
+ (ph) => ph.call(makeAgentCall({ output_type: PlanOutput, prompt, gates: [gates.artifactsExist] })),
135
+ );
136
+
137
+ await run.phase(
138
+ makePhaseParams({ name: "commit", kind: "code", owner: "git", description: "Commit the working tree" }),
139
+ async () => run.git.commitAll(build.commit_message || `spf: ${prompt.slice(0, 72)}`),
140
+ );
141
+
142
+ return run.finish();
143
+ ```
144
+
145
+ Three kinds, three swim lanes. **engineer** is the human lane. **agent** is `ph.call(...)`: prompt in, typed envelope out, gates verified. **code** is a deterministic step that stands on its own — a commit, a quality suite — and is never buried inside an agent phase, so the trace shows exactly when code ran and when an agent was working.
146
+
147
+ **Success must be earned.** Every phase defaults to `fail`. A clean exit flips it, and an agent phase also needs its envelope to parse and every gate to come back green. `run.finish(accepted, reason)` adds the second question, because phases passing is not the same as the run being acceptable: a test phase that ran a red suite did its job perfectly. One call settles the exit code, the session status, and the banner together, so they cannot disagree.
148
+
149
+ ---
150
+
151
+ ## Envelopes and gates
152
+
153
+ An agent has exactly two output channels: reference files written into `context_handoff/`, and a final valid-JSON response validated against the output type the call declared. Code persists that response as `envelope.json`, records it, and injects it into the next agent's prompt. Context transfers in code, not in conversation.
154
+
155
+ ```ts
156
+ const BuildOutput = envelopeType("BuildOutput", {
157
+ changed_files: v.optional(v.array(v.string()), () => []),
158
+ commit_message: v.optional(v.string(), ""), // consumed by the git commit phase
159
+ });
160
+ ```
161
+
162
+ Determinism is wired into every step. Agents must return a specific structure, every time. If it does not validate, they get asked again until it does.
163
+
164
+ Gates verify claims, never predictions. Nobody knows which files an agent will touch before it finishes, so gates run **after** the fact against the envelope's own declarations: `artifactsExist`, `filesNonEmpty`, `jsonParses`, `diffMatchesClaims`, `testsPass(...)`. A gate is a function with the signature `gate(envelope, run) -> GateReport`, one `check(item, ok, note)` per thing it examined, so a green gate tells you *what* it verified.
165
+
166
+ When validation fails or a gate returns violations, **nothing restarts**. The harness re-prompts the same session with a correction naming exactly what was wrong, and the context window stays intact. A cold restart throws away everything the agent learned. A correction costs one message.
167
+
168
+ The output contract lives in three places and they are one thing: the type in `data_types.ts`, the JSON example in that agent's `user.md` `## Report` section, and `output_type:` at the call site. **Change one, change all three in the same edit** — the synced triad.
169
+
170
+ ---
171
+
172
+ ## The trace
173
+
174
+ One data path, no exceptions: **agents write to SQLite, readers poll SQLite.** Every event lands in `.spf/data/spf.db` while the agent is still working, not batched at the end — tool calls are visible mid-run.
175
+
176
+ ```bash
177
+ spf sessions # recent runs
178
+ spf phases <adw_id> # phase-by-phase status for one run
179
+ spf events <adw_id> --follow # the live trace, tailable
180
+ spf ui # a browser-based visualizer over the same db
181
+ ```
182
+
183
+ That's the entire transport: a rowid-cursor poll query, run on demand. No push, no WebSocket, no ingest endpoint — live view and full history are the same query at different cadence.
184
+
185
+ Files stay the raw record (`envelope.json`, `agent_map.json`, Flue's own conversation store). The db is the queryable mirror. Losing it loses nothing you cannot rebuild.
186
+
187
+ ---
188
+
189
+ ## The chains
190
+
191
+ ```bash
192
+ spf list # every chain, its phases, what it needs
193
+ spf <chain> "<prompt or path/to/prompt.md>" [--config <path>] [--adw-id a1b2c3d4] [--cwd <dir>]
194
+ ```
195
+
196
+ | Chain | Phases | Reach for it when |
197
+ |---|---|---|
198
+ | `prompt` | engineer → \<agent\> | one agent, one prompt, `--agent NAME` picks who |
199
+ | `scout` | engineer → scout | read-only recon, nothing changes |
200
+ | `plan` | engineer → planner | you want the spec before any code |
201
+ | `build` | engineer → builder | the plan already exists |
202
+ | `quality` | engineer → code(quality) | lint, typecheck, build, no agents at all |
203
+ | `plan-build` | planner, builder, git(commit) | small, well-understood work |
204
+ | `build-test` | builder, code(test), bounded fix loop | there is a suite to satisfy |
205
+ | `build-review` | builder, reviewer, bounded revise loop | "is this what was asked for" matters more than "does it run" |
206
+ | `plan-build-test` | plan, build, code(test), git(commit) | the standard chain |
207
+ | `plan-build-test-quality` | same, plus lint/typecheck/build gates | the repo has quality commands worth enforcing |
208
+ | `document` | code(git diff), documenter | write up what just shipped |
209
+ | `simple-sdlc` | plan, build, test, review, document | the work is real and its shape is not obvious |
210
+
211
+ `--adw-id` is optional everywhere. Omit it and a fresh id is minted and printed. Supply it and the run joins that session — same session directory, each agent **resumes its existing context window** instead of starting cold. That's how you chain runs:
212
+
213
+ ```bash
214
+ spf plan "add a /health endpoint" # prints adw_id a1b2c3d4
215
+ spf build-test "implement the plan" --adw-id a1b2c3d4
216
+ ```
217
+
218
+ ---
219
+
220
+ ## `spf watch`
221
+
222
+ Polls a GitHub repo for issues labeled `<prefix>:ready`, runs a configured chain against each in its own git worktree, opens a PR, and tracks it through to merged or blocked — driving the same chains above rather than reimplementing an SDLC. Labels are the whole state machine: `ready → working → review → done`/`blocked`.
223
+
224
+ ```yaml
225
+ # .spf/spf.config.yaml
226
+ watch:
227
+ repo: owner/name
228
+ label_prefix: spf # polls issues labeled spf:ready
229
+ chain: plan-build-test # any registered chain
230
+ base_branch: main
231
+ poll_ms: 60000
232
+ concurrency: 2
233
+ ```
234
+
235
+ ```bash
236
+ export GITHUB_TOKEN=... # classic PAT — see "GITHUB_TOKEN scope" below; spf doctor checks it's set
237
+ spf watch init # idempotently create/update the 5 labels below — run this first
238
+ spf watch # foreground daemon; Ctrl-C drains in-flight claims first
239
+ spf watch --once # one poll tick, then exit — good for cron
240
+ spf watch --dry-run # log intended claims/transitions, mutate nothing
241
+ ```
242
+
243
+ `spf watch init` seeds `<prefix>:ready`/`working`/`review`/`done`/`blocked` with a color and description each — safe to re-run any time (creates what's missing, corrects any that drifted, leaves the rest alone).
244
+
245
+ No GitHub App, no webhook — it's a plain REST poll, same philosophy as the trace db's own polling contract. See [`docs/examples/`](docs/examples/) for full worked configs, and `spf install-skill`'s installed skill (`roster.md`, `references/config.md`) for the field-by-field reference.
246
+
247
+ ### `GITHUB_TOKEN` scope
248
+
249
+ A **classic** PAT (fine-grained tokens use different permission names — not covered here), scoped to the minimum that covers every call `spf watch`/`spf watch init` makes: creating/editing labels, reading and labeling issues, posting comments, opening PRs, and reading PR/check-run status.
250
+
251
+ | Target repo | Scope | Covers |
252
+ |---|---|---|
253
+ | Private | `repo` | Everything above, full read/write |
254
+ | Public only | `public_repo` | The same, restricted to public repos |
255
+
256
+ **Do not grant the `project` scope.** It's a separate, unrelated permission for GitHub Projects (classic/org/user boards) — `spf watch` doesn't touch Projects at all (deliberately out of scope for v1; see the label-based state machine above), so granting it would just be more access than this tool ever uses.
257
+
258
+ There's no dedicated "issues" or "pull requests" scope on classic PATs — GitHub bundles both into `repo`/`public_repo`, which is why that's the whole table.
259
+
260
+ ## What's in this repo
261
+
262
+ ```
263
+ super-portable-software-factory/
264
+ ├── src/
265
+ │ ├── cli/ # the `spf` command surface
266
+ │ ├── chains/ # the twelve starter chains + the registry
267
+ │ ├── core/ # config, agents, gates, quality, the sqlite trace, paths
268
+ │ └── ui/ # the trace visualizer's server side
269
+ ├── app/ # the trace visualizer SPA (Vue), built once at publish time
270
+ └── assets/
271
+ ├── defaults/ # the packaged default roster
272
+ ├── prompts/ # default system.md + user.md per starter agent
273
+ └── skill/ # an optional Claude Code skill — `spf install-skill` to use it
274
+ ```
275
+
276
+ There's no template stamped into your repo. Everything above ships inside the installed npm package; a repo you run `spf` against only ever gains a `.spf/` directory, and only if you ask for one.
277
+
278
+ ---
279
+
280
+ ## Where it can still fail
281
+
282
+ Honest edges, because knowing them is cheaper than discovering them.
283
+
284
+ | Failure | What actually happens | What to do |
285
+ |---|---|---|
286
+ | A quality-gated chain with no `quality:` configured | Fails loudly at `spf doctor`/validate time, before anything spawns | Add a `quality:` section to `.spf/spf.config.yaml` — there's no packaged default suite on purpose |
287
+ | A bare model pattern | The same model can sit under several providers, so an unqualified pattern is ambiguous | Always write `provider/model-id` |
288
+ | A coding agent hangs silently | No events, no tokens. The trace goes quiet rather than red | `spf phases <adw_id>` shows what's still `running`; `spf abort <adw_id>` stops it |
289
+ | The synced triad drifts | Envelope type, `## Report` example, and `output_type:` disagree, so every call burns correction rounds | Grep the type name and fix all three in one edit |
290
+ | Gates pass, output is bad | Gates check what a predicate can check, not plan quality or code taste | Run the `reviewer`, or read it yourself |
291
+ | An agent edits something it shouldn't | Detected and rolled back after the call, and the phase fails | Expected. Widen that agent's `writes` if the change was legitimate |
292
+ | Commit phase has nothing to commit | Throws if the cwd isn't a git repo or nothing changed | `git init` with one commit first |
293
+
294
+ Also missing on purpose, so you know what to add: chains run on your current branch by default. For real work you want a branch or worktree per run and a merge step at the end.
295
+
296
+ **Is this overkill for a one-off feature?** Yes. Prompt an agent and move on. This earns its keep when the same workflow runs a hundred times, when validation is the only thing standing between you and a bad merge, and when you need the thousandth run to look like the first.
297
+
298
+ ---
299
+
300
+ ## Built to be observed, customized, and reused
301
+
302
+ Nothing here is meant to survive contact with your codebase unchanged. The prompts describe a demo app, not your domain. The roster names the models that were good the week it was written. All of that is supposed to be replaced — `spf init` gives you the seam to do it one field at a time, and `spf eject` gives you an editable copy of the engine itself if you need to go further than config.
303
+
304
+ | Change | Where | Why |
305
+ |---|---|---|
306
+ | Your real commands | `.spf/spf.config.yaml`'s `quality:` | There's no packaged default suite — an unconfigured one fails loudly, on purpose |
307
+ | Your prompts | `.spf/prompt_engineering/{agent}/` | Where your standards live: what a good plan looks like, what a review has to catch |
308
+ | Your roster | `.spf/spf.config.yaml` | Models, thinking levels, tools, and what each agent is allowed to write |
309
+ | Your definition of done | a new gate in `core/gates.ts` (via `spf eject`) | A gate is one function |
310
+
311
+ So take it. Strip the parts you don't need, rename the agents, throw out half the chains, and roll what's left into the factory your project actually needs. The specific chains here matter far less than the shape: code owns the loop, agents own the phases, and every run leaves a trace you can go read.
312
+
313
+ ---
314
+
315
+ ## Provenance
316
+
317
+ Super Portable Factory began as a fork of [disler/super-simple-software-factory](https://github.com/disler/super-simple-software-factory). Its MIT license is preserved unmodified in this repo's [`LICENSE`](LICENSE).
318
+
319
+ ## License
320
+
321
+ MIT, see [`LICENSE`](LICENSE).
@@ -0,0 +1,141 @@
1
+ # spf.config.yaml — the factory's agent roster. One agent, one prompt, one purpose.
2
+ # Runs on Flue (@flue/runtime) — coding_agent: flue is the only implemented value.
3
+ defaults:
4
+ coding_agent: flue
5
+ model: google/gemini-3.6-flash # provider/id — a bare pattern is ambiguous across providers
6
+ thinking: medium # off | minimal | low | medium | high | xhigh | max
7
+ harness_engineering: [] # no Flue analogue — must stay empty, see agents.validate()
8
+ # Roster-wide allowlist; any agent may override with its own list. Known
9
+ # names: read, write, edit, bash, grep, glob, find (alias for glob). "ls" is
10
+ # a known name with no Flue builtin (bash/glob cover it) — harmless to list,
11
+ # it is simply dropped rather than mounted.
12
+ tools:
13
+ - read # read file contents
14
+ - bash # execute bash commands
15
+ - edit # find/replace edits
16
+ - write # create/overwrite files
17
+ - grep # search file contents (pi default: OFF)
18
+ - find # find files by glob (pi default: OFF)
19
+ - ls # list directories (pi default: OFF)
20
+ # Off-limits to every agent that does not name them in its own `writes`.
21
+ # `tools` alone cannot protect these: bash runs `git checkout`, and write
22
+ # reaches any path. An agent must not be able to edit the machinery that
23
+ # decides whether its own work passed. Enforced in core/permissions.ts.
24
+ #
25
+ # `writes:` per agent says what it may change IN THE REPO. It never restricts
26
+ # the session runtime under data_dir — context_handoff/, envelopes, prompts,
27
+ # raw output. Every agent can always write its own report; `writes: []` means
28
+ # read-only with respect to the repo, not mute.
29
+ protected_files:
30
+ - .spf/
31
+ - spf.config.yaml
32
+ data_dir: .spf/data # runtime home: {data_dir}/sessions/{adw_id}/{agent_name}/
33
+
34
+ observability:
35
+ db: .spf/data/spf.db # tracer writes here directly; the UI polls it
36
+ poll_ms: 500 # visualizer live-poll cadence
37
+
38
+ # No default quality checks or suites are shipped — an unconfigured chain
39
+ # that needs one fails loudly at agents.validate() time (before anything
40
+ # spawns) rather than reporting a placeholder green. Add your own, e.g.:
41
+ # quality:
42
+ # checks:
43
+ # - {name: test, operation: build, argv: ["npm", "test"], timeout_seconds: 600}
44
+ # - {name: lint, operation: lint, argv: ["npx", "eslint", "."], timeout_seconds: 120}
45
+ # suites:
46
+ # test: [test]
47
+ # all: [test, lint]
48
+
49
+ agents:
50
+ - name: planner
51
+ model: fireworks/accounts/fireworks/models/kimi-k3
52
+ thinking: high
53
+ color: "#a78bfa" # optional hex — the agent's lane color in the visualizer
54
+ purpose: Turn a request into a plan the builder can implement without asking questions.
55
+ prompt_engineering:
56
+ system: planner/system.md
57
+ user: planner/user.md
58
+ writes: # the plan is the only thing it may leave in the repo
59
+ - specs/
60
+ tools: # full recon + write for plan.md; no edit — the planner never touches repo files
61
+ - read
62
+ - grep
63
+ - find
64
+ - ls
65
+ - bash
66
+ - write
67
+
68
+ - name: builder
69
+ color: "#22d3ee"
70
+ purpose: Implement the plan exactly; report every changed file in the envelope.
71
+ prompt_engineering:
72
+ system: builder/system.md
73
+ user: builder/user.md
74
+ # No `writes` key: unrestricted, and the only agent that is. It still cannot
75
+ # touch defaults.protected_files — the builder does not get to edit its own grader.
76
+ tools: # the only agent that mutates the repo — everything on
77
+ - read
78
+ - grep
79
+ - find
80
+ - ls
81
+ - bash
82
+ - edit
83
+ - write
84
+
85
+ - name: scout
86
+ color: "#fbbf24"
87
+ purpose: Find and report where things live; change nothing.
88
+ prompt_engineering:
89
+ system: scout/system.md
90
+ user: scout/user.md
91
+ writes: [] # read-only, and now actually read-only: its findings
92
+ # go to context_handoff/, which is runtime, not the repo
93
+ tools: # search-heavy recon; write only so scout_findings.md lands without a bash heredoc
94
+ - read
95
+ - grep
96
+ - find
97
+ - ls
98
+ - bash
99
+ - write
100
+
101
+ # No tester agent: running the suite is a known command, so it is a kind="code"
102
+ # phase over core/quality.ts. See SKILL.md hard rule 8.
103
+
104
+ - name: reviewer
105
+ model: openai/gpt-5.6-terra
106
+ thinking: high
107
+ color: "#fb7185"
108
+ purpose: Confirm that what was built is what was asked for; change nothing.
109
+ prompt_engineering:
110
+ system: reviewer/system.md
111
+ user: reviewer/user.md
112
+ writes: [] # a reviewer that cannot fix cannot quietly fix — the
113
+ # claim the tool list only implied, now enforced
114
+ tools: # full read surface; write only for review.md, no edit
115
+ - read
116
+ - grep
117
+ - find
118
+ - ls
119
+ - bash
120
+ - write
121
+
122
+ - name: documenter
123
+ model: openai/gpt-5.6-luna
124
+ color: "#e879f9"
125
+ purpose: Write up the change that was just made, from the diff; document only.
126
+ prompt_engineering:
127
+ system: documenter/system.md
128
+ user: documenter/user.md
129
+ writes: # documentation only — "document only" is a rule now,
130
+ - app_docs/ # not a line in a prompt the model may drift from.
131
+ - docs/ # Markdown anywhere, because docs live next to the
132
+ - "**/*.md" # code they describe as often as in a docs folder.
133
+ - "*.md"
134
+ tools: # reads the diff and the code; writes/edits documentation only
135
+ - read
136
+ - grep
137
+ - find
138
+ - ls
139
+ - bash
140
+ - write
141
+ - edit
@@ -0,0 +1,13 @@
1
+ # Builder Agent
2
+
3
+ ## Purpose
4
+
5
+ Implement the plan (or request) exactly; report every file you changed.
6
+
7
+ ## Instructions
8
+
9
+ - If `previous_envelope` references a plan or test failures, follow them — they are your spec.
10
+ - Make the smallest change that satisfies the request; do not refactor unrelated code.
11
+ - When fixing test failures, address every reported failure.
12
+ - You inherit the operator's shell environment — their PATH, toolchains and credentials are already live. Call tools by bare name (`bun`, `uv`, `pytest`); never hunt for a binary or fall back to an absolute `/usr/bin/*` path.
13
+ - Verify your work compiles/runs before reporting, and judge that by exit status — not by scanning the output for words like `error`.
@@ -0,0 +1,34 @@
1
+ # Build Task
2
+
3
+ ## Variables
4
+
5
+ ### prompt
6
+
7
+ {{prompt}}
8
+
9
+ ### previous_envelope
10
+
11
+ {{previous_envelope}}
12
+
13
+ ### context_handoff_dir
14
+
15
+ {{context_handoff_dir}}
16
+
17
+ ## Task
18
+
19
+ Implement the work described in `prompt`, guided by `previous_envelope` if present, then emit your `Report` JSON.
20
+
21
+ ## Report
22
+
23
+ Respond with ONLY valid JSON matching `BuildOutput` — no prose before or after:
24
+
25
+ ```json
26
+ {
27
+ "status": "success",
28
+ "summary": "<one sentence describing what you built>",
29
+ "changed_files": ["src/server.ts"],
30
+ "artifacts": [],
31
+ "commit_message": "<imperative one-line git subject for the code you changed — this is what the commit of your work will say>",
32
+ "notes_for_next_agent": "<how to verify this work>"
33
+ }
34
+ ```
@@ -0,0 +1,17 @@
1
+ # Documenter Agent
2
+
3
+ ## Purpose
4
+
5
+ Write up the change that was just made, from the diff, for the engineer who arrives next.
6
+
7
+ ## Instructions
8
+
9
+ - `previous_envelope` carries the captured change: `base` (what it was measured against), `changed_files`, `stat`, and `diff_path`. **Read `diff_path`** — the full diff is the source of truth.
10
+ - Everything you write must be traceable to that diff. If the diff does not show it, do not claim it — no speculation about intent, no roadmap, no future work.
11
+ - **Name a file only if it is in `changed_files` or appears in the diff.** Listing a plausible neighbour that was never touched is the easiest way to make an otherwise accurate write-up wrong. Check the list before you write the sentence.
12
+ - Document what the change does, where it lives, and how to use or verify it. It is a write-up for a human, not a commit log and not a replay of the diff.
13
+ - Read the surrounding code when the diff alone does not explain a change; the diff is the scope, not the only thing you may open.
14
+ - Write documentation only. Never modify source code, tests, or config — the builder owns those, and a doc run that edits code is a bug.
15
+ - List `app_docs/` before naming your write-up and pick a name nothing else holds. Two doc runs in one session share an `adw_id`, and an overwritten write-up describes a change that already shipped.
16
+ - Keep it tight. A reader should understand the change in under two minutes.
17
+ - You inherit the operator's shell environment — their PATH, toolchains and credentials are already live. Call tools by bare name (`bun`, `uv`, `git`); never hunt for a binary or fall back to an absolute `/usr/bin/*` path.
@@ -0,0 +1,48 @@
1
+ # Document Task
2
+
3
+ ## Variables
4
+
5
+ ### prompt
6
+
7
+ {{prompt}}
8
+
9
+ ### previous_envelope
10
+
11
+ {{previous_envelope}}
12
+
13
+ ### context_handoff_dir
14
+
15
+ {{context_handoff_dir}}
16
+
17
+ ## Task
18
+
19
+ Document the completed work described by `previous_envelope`, using `prompt` for what was originally asked.
20
+
21
+ 1. Read the full diff at `previous_envelope.diff_path`, plus any changed file that needs context.
22
+ 2. Write the write-up to `<context_handoff_dir>/document.md`. Cover: what changed and why it matters, the files that carry it, and how to use or verify it.
23
+ 3. Copy that file into the repo under `app_docs/`:
24
+ - **List `app_docs/` before you pick the name.** A session that documents more than once reuses its `<adw_id>`, so the obvious name may already be taken.
25
+ - Base name: `app_docs/<adw_id>_<slug>.md`, where `<adw_id>` is the session directory name inside `context_handoff_dir` (`.../sessions/<adw_id>/context_handoff`) and `<slug>` is two to four kebab-case words naming the work.
26
+ - If a file with that name already exists, use `app_docs/<adw_id>_<slug>_v2.md`, then `_v3`, and so on until the name is free. **Never overwrite an existing write-up** — it describes a change that already shipped.
27
+ - **Copy it, do not retype it.** One bash call does the whole step:
28
+ `mkdir -p app_docs && cp "<context_handoff_dir>/document.md" "app_docs/<adw_id>_<slug>.md"`
29
+ Writing the document a second time through `write` re-emits every line you already wrote, which costs the whole write-up again in output tokens and lets the two copies drift.
30
+ 4. Emit your `Report` JSON, declaring BOTH paths in `artifacts`.
31
+
32
+ ## Report
33
+
34
+ Respond with ONLY valid JSON matching `DocumentOutput` — no prose before or after:
35
+
36
+ ```json
37
+ {
38
+ "status": "success",
39
+ "summary": "<one sentence describing what you documented>",
40
+ "document_path": "app_docs/<adw_id>_<slug>.md",
41
+ "documented_files": ["src/server.ts"],
42
+ "artifacts": ["<context_handoff_dir>/document.md", "app_docs/<adw_id>_<slug>.md"],
43
+ "commit_message": "<imperative one-line git subject for committing THIS WRITE-UP, not the change it describes — e.g. 'Document the /health endpoint'>",
44
+ "notes_for_next_agent": "<anything the diff left unexplained>"
45
+ }
46
+ ```
47
+
48
+ `document_path` and the `app_docs/` entry in `artifacts` are the path you ACTUALLY wrote, `_v2` suffix and all. Gates open these files — a name you meant to use fails them.
@@ -0,0 +1,21 @@
1
+ # Planner Agent
2
+
3
+ ## Purpose
4
+
5
+ Turn a request into a plan the builder can implement without asking questions.
6
+
7
+ ## Instructions
8
+
9
+ - Read only what you need to understand the request.
10
+ - Write the full plan to `<context_handoff_dir>/plan.md` for the builder, and keep a copy in the repo under `specs/` (exact paths in your task).
11
+ - List `specs/` before naming that copy and pick a name nothing else holds. Two plans in one session share an `adw_id`, and an overwritten spec is a lost record.
12
+ - Keep the plan concrete: files to touch, changes to make, how to verify.
13
+ - You inherit the operator's shell environment — their PATH, toolchains and credentials are already live. Call tools by bare name (`bun`, `uv`, `pytest`); never hunt for a binary or fall back to an absolute `/usr/bin/*` path.
14
+ - Judge any command you run by its exit status, never by scanning its output for words. `error` or `not found` inside passing output is text, not a failure.
15
+ - Do not implement anything.
16
+
17
+ ## Subagents
18
+
19
+ `subagent_create` / `_continue` / `_list` / `_remove` fan out recon — one per subsystem or open question — when the request spans more than you can read cheaply. Give each a self-contained task; omit `model`.
20
+
21
+ They run in the background. **Wait for every one you spawned to report before writing `plan.md` or your Report JSON.** Skip them when a few reads would do.