software-factory 0.0.1__tar.gz
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.
- software_factory-0.0.1/.gitignore +23 -0
- software_factory-0.0.1/CHANGELOG.md +26 -0
- software_factory-0.0.1/LICENSE +21 -0
- software_factory-0.0.1/PKG-INFO +296 -0
- software_factory-0.0.1/README.md +271 -0
- software_factory-0.0.1/pyproject.toml +79 -0
- software_factory-0.0.1/software_factory/SKILL.md +182 -0
- software_factory-0.0.1/software_factory/__init__.py +8 -0
- software_factory-0.0.1/software_factory/__main__.py +1246 -0
- software_factory-0.0.1/software_factory/backend.py +255 -0
- software_factory-0.0.1/software_factory/config.py +205 -0
- software_factory-0.0.1/software_factory/engine.py +241 -0
- software_factory-0.0.1/software_factory/judge.py +299 -0
- software_factory-0.0.1/software_factory/pipeline.py +237 -0
- software_factory-0.0.1/software_factory/runners/claude.yaml +25 -0
- software_factory-0.0.1/software_factory/runners/shell.yaml +10 -0
- software_factory-0.0.1/software_factory/runners/typesafe.yaml +5 -0
- software_factory-0.0.1/software_factory/runners.py +141 -0
- software_factory-0.0.1/software_factory/steps.py +330 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
wheels/
|
|
7
|
+
*.egg-info
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv
|
|
11
|
+
|
|
12
|
+
# Factory state (work items, worktrees, agent logs)
|
|
13
|
+
_FACTORY/
|
|
14
|
+
|
|
15
|
+
# Tool caches
|
|
16
|
+
.mypy_cache/
|
|
17
|
+
.pytest_cache/
|
|
18
|
+
.ruff_cache/
|
|
19
|
+
|
|
20
|
+
# Editor and agent scratch, local to whoever is working here
|
|
21
|
+
.zed/
|
|
22
|
+
_AGENT/
|
|
23
|
+
_FACTORY/
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format is
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project follows
|
|
5
|
+
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [0.0.1rc1] - 2026-09-21
|
|
8
|
+
|
|
9
|
+
First published build, to TestPyPI. The engine works end to end; the interfaces are
|
|
10
|
+
still free to change.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `sf`, one global command for every repo: `init`, `submit`, `run`, `status`, `show`,
|
|
14
|
+
`replay`, `cancel`, `delete`, `prune`, `config`, `runners`, `doctor`.
|
|
15
|
+
- Pipelines as YAML, in a repo's `.sf/pipelines/` or in `~/.sf/pipelines/` for every repo,
|
|
16
|
+
with rework edges, review gates and a pass budget before a request parks for a human.
|
|
17
|
+
- Three built-in runners: `claude` (Claude Code, non-interactive), `shell`, and `typesafe`
|
|
18
|
+
(a typed Jev judgment, opt-in, off without `TYPESAFE_API_KEY`).
|
|
19
|
+
- A git worktree per request, on its own branch, so several run at once without touching
|
|
20
|
+
the tree you are editing.
|
|
21
|
+
- `sf --skill`, the skill that teaches an agent to operate the factory.
|
|
22
|
+
|
|
23
|
+
## [0.0.1] - 2026-09-21
|
|
24
|
+
|
|
25
|
+
First stable release. The same build as 0.0.1rc1 - see that entry for what it
|
|
26
|
+
contains - published to PyPI rather than TestPyPI.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 StratoNext, Inc
|
|
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.
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: software-factory
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: An agent software factory: requests flow through YAML-defined pipelines of agent, command and judge steps.
|
|
5
|
+
Project-URL: Homepage, https://github.com/stratonext/software-factory
|
|
6
|
+
Project-URL: Repository, https://github.com/stratonext/software-factory
|
|
7
|
+
Project-URL: Issues, https://github.com/stratonext/software-factory/issues
|
|
8
|
+
Author-email: Stratonext <info@stratonext.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agents,automation,claude,cli,code-review,pipeline
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Topic :: Software Development
|
|
19
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Requires-Dist: pyyaml>=6
|
|
22
|
+
Requires-Dist: rich>=13
|
|
23
|
+
Requires-Dist: typer>=0.12.1
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
<img src="assets/factory.svg" alt="" width="192" height="192">
|
|
27
|
+
|
|
28
|
+
Maintained by [StratoNext](https://www.stratonext.ai).
|
|
29
|
+
|
|
30
|
+
# Local Software Factory
|
|
31
|
+
|
|
32
|
+
A software factory is a system that turns software requests into finished work through a repeatable, automated process. Instead of handling every request manually, you define a pipeline of stages that moves the work from request to completion, with human review when needed.
|
|
33
|
+
|
|
34
|
+
This project aims to build a simple **local software factory**.
|
|
35
|
+
|
|
36
|
+
You can define multiple pipelines, each made up of multiple stages. A new request enters a pipeline and moves through its stages until it is completed or requires human review. Pipelines are defined in YAML, inspired by GitHub Actions, so the workflow itself can live alongside your code and be version controlled.
|
|
37
|
+
|
|
38
|
+
A pipeline can combine **coding agents and shell commands**. For example, one stage might ask a coding agent to implement a change, another might run tests, and a later stage might ask another agent to review the result.
|
|
39
|
+
|
|
40
|
+
Multiple requests can run through pipelines in parallel. By default, each request gets its own Git worktree, keeping work isolated so different requests do not interfere with each other.
|
|
41
|
+
|
|
42
|
+
The project is designed to run locally and reuse the coding agents you already have installed. Instead of requiring a separate API integration or metered API usage, each stage can use the agent CLI and subscription you already have.
|
|
43
|
+
|
|
44
|
+
The goal is simple: **bring the basic ideas of a software factory to your local machine, with pipelines defined as code and coding agents as workers.**
|
|
45
|
+
|
|
46
|
+

|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
`sf` is one global command for all your repos, like `docker`. Install it once:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
uv tool install software-factory # or: pipx install software-factory
|
|
54
|
+
pip install software-factory # or into a virtualenv you manage yourself
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Then create the installation — `~/.sf`, a starter `config.yaml` and the directories the
|
|
58
|
+
factory uses:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
sf init
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Stages that use a coding agent run that agent's CLI, so it has to be installed and
|
|
65
|
+
authenticated once. For the built-in `claude` runner:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
claude setup-token # needs a Claude subscription
|
|
69
|
+
export CLAUDE_CODE_OAUTH_TOKEN=... # the token it printed
|
|
70
|
+
sf doctor # checks git, the CLI, the token, the paths
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Quickstart
|
|
74
|
+
|
|
75
|
+
The first thing to do is to write a pipeline. Put it in `~/.sf/pipelines/` and every repo on the machine can use it:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
mkdir -p ~/.sf/pipelines/prompts
|
|
79
|
+
|
|
80
|
+
cat > ~/.sf/pipelines/quick.yaml <<'YAML'
|
|
81
|
+
name: quick
|
|
82
|
+
description: Implement the request with Claude Code, then commit it.
|
|
83
|
+
start: code
|
|
84
|
+
|
|
85
|
+
steps:
|
|
86
|
+
code:
|
|
87
|
+
uses: claude # the agent CLI you already have
|
|
88
|
+
with:
|
|
89
|
+
prompt: prompts/code.md # relative to this file
|
|
90
|
+
next: commit
|
|
91
|
+
|
|
92
|
+
commit:
|
|
93
|
+
uses: shell # an ordinary command, run in the request's worktree
|
|
94
|
+
with:
|
|
95
|
+
run: "git add -A && git diff --cached --quiet || git commit -m 'sf: worked by the factory'"
|
|
96
|
+
on: { pass: done } # `done` is the implicit terminal stage
|
|
97
|
+
YAML
|
|
98
|
+
|
|
99
|
+
cat > ~/.sf/pipelines/prompts/code.md <<'MD'
|
|
100
|
+
Implement the request below in this repository.
|
|
101
|
+
|
|
102
|
+
Make the smallest change that does it. Follow the conventions already in the code, and
|
|
103
|
+
leave the working tree clean - no scratch files, no commented-out code.
|
|
104
|
+
MD
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The request itself is not in the prompt file: the engine appends it, along with anything
|
|
108
|
+
earlier stages produced and any note a human left. You write the instructions, the factory
|
|
109
|
+
fills in the work.
|
|
110
|
+
|
|
111
|
+
Submit **from inside the repo**:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
sf submit "add rate limiting to /upload" --name rate-limit --pipeline quick # -> id 1
|
|
115
|
+
sf run # work everything that is queued
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
And from anywhere, to see what is happening:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
sf status # everything in flight, across every repo
|
|
122
|
+
sf show 1 # the full state of one request
|
|
123
|
+
sf replay 1 # play the run back: every step, its route, its artifacts
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Each request works in its own git worktree under `~/.sf/worktrees/<repo>/<id>/`, so several
|
|
127
|
+
can run at once without stepping on each other or on what you are editing.
|
|
128
|
+
|
|
129
|
+
## Driving the factory with an agent
|
|
130
|
+
|
|
131
|
+
The factory is a CLI, so the thing best placed to operate it is another agent. `sf` ships
|
|
132
|
+
with a skill that teaches one how — every command, the verdicts, how to answer a parked
|
|
133
|
+
request, and the shape of a pipeline file:
|
|
134
|
+
|
|
135
|
+
Load the skill in the agent (`--skill`), then ask it to break the work up and queue it:
|
|
136
|
+
|
|
137
|
+
> Read the roadmap in `docs/plan.md`, split it into requests small enough for one pipeline
|
|
138
|
+
> pass each, and `sf submit` them against `quick` pipeline. Then run the queue and tell me what
|
|
139
|
+
> lands.
|
|
140
|
+
|
|
141
|
+
Ask it to write the process, not just use it:
|
|
142
|
+
|
|
143
|
+
> We keep shipping changes with no tests. Write me a `.sf/pipelines/dev.yaml` that plans,
|
|
144
|
+
> implements, runs `pytest -q`, and sends the work back to the coder when it fails — two
|
|
145
|
+
> rework passes, then park for me.
|
|
146
|
+
|
|
147
|
+
And then leave it to watch: `sf status` is the whole state of the world in one call, so the
|
|
148
|
+
agent can poll until every request is `done`, `failed` or `needs_human`, `sf replay <id>`
|
|
149
|
+
the ones that went wrong, answer a parked one with `sf run <id> --note "..."`, re-enter an
|
|
150
|
+
earlier stage when a plan needs changing, and re-queue what failed — until the queue is
|
|
151
|
+
empty.
|
|
152
|
+
|
|
153
|
+
That is the whole point of the local factory. The agent you are talking to is the foreman,
|
|
154
|
+
not the worker: each request it queues is worked by its own agent, in its own git worktree,
|
|
155
|
+
on its own branch, several at a time, and none of them can touch the tree you are editing.
|
|
156
|
+
One conversation turns into a queue of parallel work you can watch, interrupt, and replay —
|
|
157
|
+
`sf cancel` stops it, and the foreman is never the one grading its own diff, because the
|
|
158
|
+
pipeline decides that with a test run or a [Jev judgment](#judging-with-jev).
|
|
159
|
+
|
|
160
|
+
## Writing a pipeline
|
|
161
|
+
|
|
162
|
+
A pipeline is a YAML file in the repo's own `.sf/pipelines/<name>.yaml`, or in
|
|
163
|
+
`~/.sf/pipelines/` for every repo. The repo's own copy wins, so two repos can both have a
|
|
164
|
+
`dev` pipeline and mean different processes.
|
|
165
|
+
|
|
166
|
+
The Quickstart's `quick` is about as small as one gets. Here is the next step up — implement,
|
|
167
|
+
test, and send the work back to the coder if the tests fail:
|
|
168
|
+
|
|
169
|
+
```yaml
|
|
170
|
+
name: dev
|
|
171
|
+
description: Implement a change, and only keep it if the tests pass.
|
|
172
|
+
start: code # which step a new request enters; defaults to the first
|
|
173
|
+
max_passes: 2 # rework round-trips before a human is asked instead
|
|
174
|
+
|
|
175
|
+
steps:
|
|
176
|
+
code:
|
|
177
|
+
uses: claude # which runner performs this step
|
|
178
|
+
with:
|
|
179
|
+
prompt: prompts/code.md # prompt file, relative to this pipeline
|
|
180
|
+
next: test # unconditional edge
|
|
181
|
+
|
|
182
|
+
test:
|
|
183
|
+
uses: shell
|
|
184
|
+
with:
|
|
185
|
+
run: "pytest -q" # exit 0 = pass, anything else = fail
|
|
186
|
+
on: { pass: done, fail: code } # a backwards edge is rework, and costs one pass
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Submit against it with `sf submit "..." --pipeline dev` — the name is the file's, so
|
|
190
|
+
`dev.yaml` is `--pipeline dev`. `~/.sf/pipelines/` serves every repo; a `.sf/pipelines/` in a
|
|
191
|
+
repo wins over it, which is how one repo keeps a process of its own. [`examples/`](examples/)
|
|
192
|
+
has four more to copy: implement-and-commit, the full reviewed line, a judged secret
|
|
193
|
+
gate, and one that opens the pull request.
|
|
194
|
+
|
|
195
|
+
A step says **who performs it** (`uses:`) and **what to hand them** (`with:`), and needs at
|
|
196
|
+
least one of `next:` or `on:`. `done` is the implicit terminal stage. Three runners ship
|
|
197
|
+
built in:
|
|
198
|
+
|
|
199
|
+
| runner | what it is |
|
|
200
|
+
|---|---|
|
|
201
|
+
| `claude` | Claude Code, non-interactive. `with: {prompt:, effort:, model:}` |
|
|
202
|
+
| `shell` | an ordinary command. `with: {run:}` |
|
|
203
|
+
| `typesafe` | a typed judgment instead of an agent. `with: {questions:}` |
|
|
204
|
+
|
|
205
|
+
`sf runners` lists every runner a step can name, whether its binary is on PATH, and what
|
|
206
|
+
each one supports. Adding one is a YAML file too, so a stage can run a different agent CLI.
|
|
207
|
+
|
|
208
|
+
Everything a pipeline can say — `input:`, `output:`, `review:` gates, judge steps,
|
|
209
|
+
concurrency, costs — is in [`docs/pipelines.md`](docs/pipelines.md), and
|
|
210
|
+
[`docs/pipeline-schema.yaml`](docs/pipeline-schema.yaml) is the annotated schema your editor
|
|
211
|
+
can use for completion.
|
|
212
|
+
|
|
213
|
+
## Judging with Jev
|
|
214
|
+
|
|
215
|
+
`typesafe` is the third built-in runner, and the one that is not an agent. A step that
|
|
216
|
+
`uses: typesafe` sends the diff and the scratch files as **state**, asks the typed questions
|
|
217
|
+
you wrote, and gets typed answers back from [TypeSafe](https://typesafe.ai)'s System One
|
|
218
|
+
model, **Jev** — a probability, a position on an ordered scale, one of a set of choices. The
|
|
219
|
+
pipeline routes on those numbers with thresholds you declare, so the decision is data rather
|
|
220
|
+
than an agent's prose.
|
|
221
|
+
|
|
222
|
+
```yaml
|
|
223
|
+
gate:
|
|
224
|
+
uses: typesafe
|
|
225
|
+
with:
|
|
226
|
+
questions: prompts/review-gate.yaml # the questions and the routing, beside the prompts
|
|
227
|
+
input: [plan.md]
|
|
228
|
+
output: gate.md
|
|
229
|
+
on: { pass: review, fail: code } # a cheap filter before the expensive reviewer
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
```yaml
|
|
233
|
+
# prompts/review-gate.yaml
|
|
234
|
+
state: { diff: "git diff" } # shell commands; stdout becomes a named state field
|
|
235
|
+
questions:
|
|
236
|
+
secret: { type: noul, instructions: "`diff` hardcodes a credential, key, token or password." }
|
|
237
|
+
scope: { type: score, instructions: "How far does `diff` go beyond `request`?",
|
|
238
|
+
criteria: ["exactly the request", "small extras", "large unrelated changes"] }
|
|
239
|
+
route: # first match wins; a rule with no `when` is default
|
|
240
|
+
- { when: secret, above: $secret, verdict: human, notes: "possible hardcoded credential" }
|
|
241
|
+
- { when: scope, above: 1.5, verdict: fail, notes: "goes well beyond the request" }
|
|
242
|
+
- { verdict: pass }
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Why bother, when an agent could be asked the same thing in English: a judgment costs about
|
|
246
|
+
**$0.0002** where a review agent costs **$1–2**, it answers in milliseconds, and it is not
|
|
247
|
+
the worker grading its own work. The notes the factory records carry the number that fired —
|
|
248
|
+
`secret 0.71 > 0.5 - possible hardcoded credential` — so a verdict can be argued with. Use it
|
|
249
|
+
as a gate in front of the expensive stages, not as a replacement for them.
|
|
250
|
+
|
|
251
|
+
`above: $secret` reads `typesafe.thresholds.secret` from `~/.sf/config.yaml`, so a gate that
|
|
252
|
+
turns out to be too eager is one edit for every pipeline that uses it; a rule that writes a
|
|
253
|
+
literal still wins. A `$name` the config does not define parks the request rather than being
|
|
254
|
+
read as zero.
|
|
255
|
+
|
|
256
|
+
The same model can also pick the process for you. `--pipeline auto` and `--effort auto` ask
|
|
257
|
+
Jev which pipeline a request belongs in and how hard the agent should think, in one call
|
|
258
|
+
before anything is queued, and flag a request too vague for anyone to start on:
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
sf submit "the upload endpoint 500s on files over 2MB" --pipeline auto --effort auto
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
All of it is **opt-in and off by default**: it needs `TYPESAFE_API_KEY`, and without one
|
|
265
|
+
nothing here is reached — submit behaves exactly as it always did. A step that *does* name
|
|
266
|
+
this runner and cannot ask — no key, an HTTP error, a rule about a question that was not
|
|
267
|
+
answered — returns `human` and parks the request. The factory never guesses a verdict, and a
|
|
268
|
+
service it could not reach is a decision nobody made.
|
|
269
|
+
|
|
270
|
+
The questions are fixed by the pipeline author, and only the diff and the scratch files
|
|
271
|
+
arrive as state. That separation is deliberate: an agent wrote the diff, so the diff is not
|
|
272
|
+
trusted input, and it must never be able to reach the model as an instruction.
|
|
273
|
+
[`docs/pipelines.md`](docs/pipelines.md) has the whole shape, question types included.
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
## Commands
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
sf init # create ~/.sf, its config.yaml and its directories
|
|
280
|
+
sf submit "..." --name x # queue a request against this repo (--file, --pipeline, --run)
|
|
281
|
+
sf run # work the queue (--repo, <id>..., --detach, --note, --stage)
|
|
282
|
+
sf status # what is in flight, across every repo
|
|
283
|
+
sf show 1 # full state of one request
|
|
284
|
+
sf replay 1 # play a run back (--step, --json)
|
|
285
|
+
sf config # the settings in effect, and where they would be changed
|
|
286
|
+
sf runners # every runner a step can use, and whether it is installed
|
|
287
|
+
sf cancel 1 2 # stop requests now; `sf run <id>` picks one back up
|
|
288
|
+
sf delete 1 2 # drop requests: item, artifacts and worktree
|
|
289
|
+
sf prune # clear finished requests in bulk (--status, --older-than)
|
|
290
|
+
sf doctor # is this installation able to work anything?
|
|
291
|
+
sf --version # what is installed
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Every command prints dense `key: value` for a person and JSON for a program, decided by
|
|
295
|
+
where it is going: a terminal gets the text, a pipe or a redirect gets the JSON, and
|
|
296
|
+
`--json` forces it anywhere.
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
<img src="assets/factory.svg" alt="" width="192" height="192">
|
|
2
|
+
|
|
3
|
+
Maintained by [StratoNext](https://www.stratonext.ai).
|
|
4
|
+
|
|
5
|
+
# Local Software Factory
|
|
6
|
+
|
|
7
|
+
A software factory is a system that turns software requests into finished work through a repeatable, automated process. Instead of handling every request manually, you define a pipeline of stages that moves the work from request to completion, with human review when needed.
|
|
8
|
+
|
|
9
|
+
This project aims to build a simple **local software factory**.
|
|
10
|
+
|
|
11
|
+
You can define multiple pipelines, each made up of multiple stages. A new request enters a pipeline and moves through its stages until it is completed or requires human review. Pipelines are defined in YAML, inspired by GitHub Actions, so the workflow itself can live alongside your code and be version controlled.
|
|
12
|
+
|
|
13
|
+
A pipeline can combine **coding agents and shell commands**. For example, one stage might ask a coding agent to implement a change, another might run tests, and a later stage might ask another agent to review the result.
|
|
14
|
+
|
|
15
|
+
Multiple requests can run through pipelines in parallel. By default, each request gets its own Git worktree, keeping work isolated so different requests do not interfere with each other.
|
|
16
|
+
|
|
17
|
+
The project is designed to run locally and reuse the coding agents you already have installed. Instead of requiring a separate API integration or metered API usage, each stage can use the agent CLI and subscription you already have.
|
|
18
|
+
|
|
19
|
+
The goal is simple: **bring the basic ideas of a software factory to your local machine, with pipelines defined as code and coding agents as workers.**
|
|
20
|
+
|
|
21
|
+

|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
`sf` is one global command for all your repos, like `docker`. Install it once:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
uv tool install software-factory # or: pipx install software-factory
|
|
29
|
+
pip install software-factory # or into a virtualenv you manage yourself
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Then create the installation — `~/.sf`, a starter `config.yaml` and the directories the
|
|
33
|
+
factory uses:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
sf init
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Stages that use a coding agent run that agent's CLI, so it has to be installed and
|
|
40
|
+
authenticated once. For the built-in `claude` runner:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
claude setup-token # needs a Claude subscription
|
|
44
|
+
export CLAUDE_CODE_OAUTH_TOKEN=... # the token it printed
|
|
45
|
+
sf doctor # checks git, the CLI, the token, the paths
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Quickstart
|
|
49
|
+
|
|
50
|
+
The first thing to do is to write a pipeline. Put it in `~/.sf/pipelines/` and every repo on the machine can use it:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
mkdir -p ~/.sf/pipelines/prompts
|
|
54
|
+
|
|
55
|
+
cat > ~/.sf/pipelines/quick.yaml <<'YAML'
|
|
56
|
+
name: quick
|
|
57
|
+
description: Implement the request with Claude Code, then commit it.
|
|
58
|
+
start: code
|
|
59
|
+
|
|
60
|
+
steps:
|
|
61
|
+
code:
|
|
62
|
+
uses: claude # the agent CLI you already have
|
|
63
|
+
with:
|
|
64
|
+
prompt: prompts/code.md # relative to this file
|
|
65
|
+
next: commit
|
|
66
|
+
|
|
67
|
+
commit:
|
|
68
|
+
uses: shell # an ordinary command, run in the request's worktree
|
|
69
|
+
with:
|
|
70
|
+
run: "git add -A && git diff --cached --quiet || git commit -m 'sf: worked by the factory'"
|
|
71
|
+
on: { pass: done } # `done` is the implicit terminal stage
|
|
72
|
+
YAML
|
|
73
|
+
|
|
74
|
+
cat > ~/.sf/pipelines/prompts/code.md <<'MD'
|
|
75
|
+
Implement the request below in this repository.
|
|
76
|
+
|
|
77
|
+
Make the smallest change that does it. Follow the conventions already in the code, and
|
|
78
|
+
leave the working tree clean - no scratch files, no commented-out code.
|
|
79
|
+
MD
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The request itself is not in the prompt file: the engine appends it, along with anything
|
|
83
|
+
earlier stages produced and any note a human left. You write the instructions, the factory
|
|
84
|
+
fills in the work.
|
|
85
|
+
|
|
86
|
+
Submit **from inside the repo**:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
sf submit "add rate limiting to /upload" --name rate-limit --pipeline quick # -> id 1
|
|
90
|
+
sf run # work everything that is queued
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
And from anywhere, to see what is happening:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
sf status # everything in flight, across every repo
|
|
97
|
+
sf show 1 # the full state of one request
|
|
98
|
+
sf replay 1 # play the run back: every step, its route, its artifacts
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Each request works in its own git worktree under `~/.sf/worktrees/<repo>/<id>/`, so several
|
|
102
|
+
can run at once without stepping on each other or on what you are editing.
|
|
103
|
+
|
|
104
|
+
## Driving the factory with an agent
|
|
105
|
+
|
|
106
|
+
The factory is a CLI, so the thing best placed to operate it is another agent. `sf` ships
|
|
107
|
+
with a skill that teaches one how — every command, the verdicts, how to answer a parked
|
|
108
|
+
request, and the shape of a pipeline file:
|
|
109
|
+
|
|
110
|
+
Load the skill in the agent (`--skill`), then ask it to break the work up and queue it:
|
|
111
|
+
|
|
112
|
+
> Read the roadmap in `docs/plan.md`, split it into requests small enough for one pipeline
|
|
113
|
+
> pass each, and `sf submit` them against `quick` pipeline. Then run the queue and tell me what
|
|
114
|
+
> lands.
|
|
115
|
+
|
|
116
|
+
Ask it to write the process, not just use it:
|
|
117
|
+
|
|
118
|
+
> We keep shipping changes with no tests. Write me a `.sf/pipelines/dev.yaml` that plans,
|
|
119
|
+
> implements, runs `pytest -q`, and sends the work back to the coder when it fails — two
|
|
120
|
+
> rework passes, then park for me.
|
|
121
|
+
|
|
122
|
+
And then leave it to watch: `sf status` is the whole state of the world in one call, so the
|
|
123
|
+
agent can poll until every request is `done`, `failed` or `needs_human`, `sf replay <id>`
|
|
124
|
+
the ones that went wrong, answer a parked one with `sf run <id> --note "..."`, re-enter an
|
|
125
|
+
earlier stage when a plan needs changing, and re-queue what failed — until the queue is
|
|
126
|
+
empty.
|
|
127
|
+
|
|
128
|
+
That is the whole point of the local factory. The agent you are talking to is the foreman,
|
|
129
|
+
not the worker: each request it queues is worked by its own agent, in its own git worktree,
|
|
130
|
+
on its own branch, several at a time, and none of them can touch the tree you are editing.
|
|
131
|
+
One conversation turns into a queue of parallel work you can watch, interrupt, and replay —
|
|
132
|
+
`sf cancel` stops it, and the foreman is never the one grading its own diff, because the
|
|
133
|
+
pipeline decides that with a test run or a [Jev judgment](#judging-with-jev).
|
|
134
|
+
|
|
135
|
+
## Writing a pipeline
|
|
136
|
+
|
|
137
|
+
A pipeline is a YAML file in the repo's own `.sf/pipelines/<name>.yaml`, or in
|
|
138
|
+
`~/.sf/pipelines/` for every repo. The repo's own copy wins, so two repos can both have a
|
|
139
|
+
`dev` pipeline and mean different processes.
|
|
140
|
+
|
|
141
|
+
The Quickstart's `quick` is about as small as one gets. Here is the next step up — implement,
|
|
142
|
+
test, and send the work back to the coder if the tests fail:
|
|
143
|
+
|
|
144
|
+
```yaml
|
|
145
|
+
name: dev
|
|
146
|
+
description: Implement a change, and only keep it if the tests pass.
|
|
147
|
+
start: code # which step a new request enters; defaults to the first
|
|
148
|
+
max_passes: 2 # rework round-trips before a human is asked instead
|
|
149
|
+
|
|
150
|
+
steps:
|
|
151
|
+
code:
|
|
152
|
+
uses: claude # which runner performs this step
|
|
153
|
+
with:
|
|
154
|
+
prompt: prompts/code.md # prompt file, relative to this pipeline
|
|
155
|
+
next: test # unconditional edge
|
|
156
|
+
|
|
157
|
+
test:
|
|
158
|
+
uses: shell
|
|
159
|
+
with:
|
|
160
|
+
run: "pytest -q" # exit 0 = pass, anything else = fail
|
|
161
|
+
on: { pass: done, fail: code } # a backwards edge is rework, and costs one pass
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Submit against it with `sf submit "..." --pipeline dev` — the name is the file's, so
|
|
165
|
+
`dev.yaml` is `--pipeline dev`. `~/.sf/pipelines/` serves every repo; a `.sf/pipelines/` in a
|
|
166
|
+
repo wins over it, which is how one repo keeps a process of its own. [`examples/`](examples/)
|
|
167
|
+
has four more to copy: implement-and-commit, the full reviewed line, a judged secret
|
|
168
|
+
gate, and one that opens the pull request.
|
|
169
|
+
|
|
170
|
+
A step says **who performs it** (`uses:`) and **what to hand them** (`with:`), and needs at
|
|
171
|
+
least one of `next:` or `on:`. `done` is the implicit terminal stage. Three runners ship
|
|
172
|
+
built in:
|
|
173
|
+
|
|
174
|
+
| runner | what it is |
|
|
175
|
+
|---|---|
|
|
176
|
+
| `claude` | Claude Code, non-interactive. `with: {prompt:, effort:, model:}` |
|
|
177
|
+
| `shell` | an ordinary command. `with: {run:}` |
|
|
178
|
+
| `typesafe` | a typed judgment instead of an agent. `with: {questions:}` |
|
|
179
|
+
|
|
180
|
+
`sf runners` lists every runner a step can name, whether its binary is on PATH, and what
|
|
181
|
+
each one supports. Adding one is a YAML file too, so a stage can run a different agent CLI.
|
|
182
|
+
|
|
183
|
+
Everything a pipeline can say — `input:`, `output:`, `review:` gates, judge steps,
|
|
184
|
+
concurrency, costs — is in [`docs/pipelines.md`](docs/pipelines.md), and
|
|
185
|
+
[`docs/pipeline-schema.yaml`](docs/pipeline-schema.yaml) is the annotated schema your editor
|
|
186
|
+
can use for completion.
|
|
187
|
+
|
|
188
|
+
## Judging with Jev
|
|
189
|
+
|
|
190
|
+
`typesafe` is the third built-in runner, and the one that is not an agent. A step that
|
|
191
|
+
`uses: typesafe` sends the diff and the scratch files as **state**, asks the typed questions
|
|
192
|
+
you wrote, and gets typed answers back from [TypeSafe](https://typesafe.ai)'s System One
|
|
193
|
+
model, **Jev** — a probability, a position on an ordered scale, one of a set of choices. The
|
|
194
|
+
pipeline routes on those numbers with thresholds you declare, so the decision is data rather
|
|
195
|
+
than an agent's prose.
|
|
196
|
+
|
|
197
|
+
```yaml
|
|
198
|
+
gate:
|
|
199
|
+
uses: typesafe
|
|
200
|
+
with:
|
|
201
|
+
questions: prompts/review-gate.yaml # the questions and the routing, beside the prompts
|
|
202
|
+
input: [plan.md]
|
|
203
|
+
output: gate.md
|
|
204
|
+
on: { pass: review, fail: code } # a cheap filter before the expensive reviewer
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
```yaml
|
|
208
|
+
# prompts/review-gate.yaml
|
|
209
|
+
state: { diff: "git diff" } # shell commands; stdout becomes a named state field
|
|
210
|
+
questions:
|
|
211
|
+
secret: { type: noul, instructions: "`diff` hardcodes a credential, key, token or password." }
|
|
212
|
+
scope: { type: score, instructions: "How far does `diff` go beyond `request`?",
|
|
213
|
+
criteria: ["exactly the request", "small extras", "large unrelated changes"] }
|
|
214
|
+
route: # first match wins; a rule with no `when` is default
|
|
215
|
+
- { when: secret, above: $secret, verdict: human, notes: "possible hardcoded credential" }
|
|
216
|
+
- { when: scope, above: 1.5, verdict: fail, notes: "goes well beyond the request" }
|
|
217
|
+
- { verdict: pass }
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Why bother, when an agent could be asked the same thing in English: a judgment costs about
|
|
221
|
+
**$0.0002** where a review agent costs **$1–2**, it answers in milliseconds, and it is not
|
|
222
|
+
the worker grading its own work. The notes the factory records carry the number that fired —
|
|
223
|
+
`secret 0.71 > 0.5 - possible hardcoded credential` — so a verdict can be argued with. Use it
|
|
224
|
+
as a gate in front of the expensive stages, not as a replacement for them.
|
|
225
|
+
|
|
226
|
+
`above: $secret` reads `typesafe.thresholds.secret` from `~/.sf/config.yaml`, so a gate that
|
|
227
|
+
turns out to be too eager is one edit for every pipeline that uses it; a rule that writes a
|
|
228
|
+
literal still wins. A `$name` the config does not define parks the request rather than being
|
|
229
|
+
read as zero.
|
|
230
|
+
|
|
231
|
+
The same model can also pick the process for you. `--pipeline auto` and `--effort auto` ask
|
|
232
|
+
Jev which pipeline a request belongs in and how hard the agent should think, in one call
|
|
233
|
+
before anything is queued, and flag a request too vague for anyone to start on:
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
sf submit "the upload endpoint 500s on files over 2MB" --pipeline auto --effort auto
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
All of it is **opt-in and off by default**: it needs `TYPESAFE_API_KEY`, and without one
|
|
240
|
+
nothing here is reached — submit behaves exactly as it always did. A step that *does* name
|
|
241
|
+
this runner and cannot ask — no key, an HTTP error, a rule about a question that was not
|
|
242
|
+
answered — returns `human` and parks the request. The factory never guesses a verdict, and a
|
|
243
|
+
service it could not reach is a decision nobody made.
|
|
244
|
+
|
|
245
|
+
The questions are fixed by the pipeline author, and only the diff and the scratch files
|
|
246
|
+
arrive as state. That separation is deliberate: an agent wrote the diff, so the diff is not
|
|
247
|
+
trusted input, and it must never be able to reach the model as an instruction.
|
|
248
|
+
[`docs/pipelines.md`](docs/pipelines.md) has the whole shape, question types included.
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
## Commands
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
sf init # create ~/.sf, its config.yaml and its directories
|
|
255
|
+
sf submit "..." --name x # queue a request against this repo (--file, --pipeline, --run)
|
|
256
|
+
sf run # work the queue (--repo, <id>..., --detach, --note, --stage)
|
|
257
|
+
sf status # what is in flight, across every repo
|
|
258
|
+
sf show 1 # full state of one request
|
|
259
|
+
sf replay 1 # play a run back (--step, --json)
|
|
260
|
+
sf config # the settings in effect, and where they would be changed
|
|
261
|
+
sf runners # every runner a step can use, and whether it is installed
|
|
262
|
+
sf cancel 1 2 # stop requests now; `sf run <id>` picks one back up
|
|
263
|
+
sf delete 1 2 # drop requests: item, artifacts and worktree
|
|
264
|
+
sf prune # clear finished requests in bulk (--status, --older-than)
|
|
265
|
+
sf doctor # is this installation able to work anything?
|
|
266
|
+
sf --version # what is installed
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Every command prints dense `key: value` for a person and JSON for a program, decided by
|
|
270
|
+
where it is going: a terminal gets the text, a pipe or a redirect gets the JSON, and
|
|
271
|
+
`--json` forces it anywhere.
|