minifactory 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.
- minifactory-0.0.1/LICENSE +21 -0
- minifactory-0.0.1/PKG-INFO +249 -0
- minifactory-0.0.1/README.md +219 -0
- minifactory-0.0.1/pyproject.toml +62 -0
- minifactory-0.0.1/setup.cfg +4 -0
- minifactory-0.0.1/src/minifactory/__init__.py +1 -0
- minifactory-0.0.1/src/minifactory/__main__.py +3 -0
- minifactory-0.0.1/src/minifactory/catalog.py +130 -0
- minifactory-0.0.1/src/minifactory/cli.py +380 -0
- minifactory-0.0.1/src/minifactory/core/__init__.py +0 -0
- minifactory-0.0.1/src/minifactory/core/context.py +43 -0
- minifactory-0.0.1/src/minifactory/core/engine.py +167 -0
- minifactory-0.0.1/src/minifactory/core/errors.py +18 -0
- minifactory-0.0.1/src/minifactory/core/events.py +55 -0
- minifactory-0.0.1/src/minifactory/core/expr.py +109 -0
- minifactory-0.0.1/src/minifactory/core/models.py +76 -0
- minifactory-0.0.1/src/minifactory/core/ports.py +48 -0
- minifactory-0.0.1/src/minifactory/core/registry.py +47 -0
- minifactory-0.0.1/src/minifactory/core/workflow.py +130 -0
- minifactory-0.0.1/src/minifactory/demo.py +72 -0
- minifactory-0.0.1/src/minifactory/plugins/__init__.py +0 -0
- minifactory-0.0.1/src/minifactory/plugins/builtin/__init__.py +18 -0
- minifactory-0.0.1/src/minifactory/plugins/builtin/fake_flutter.py +112 -0
- minifactory-0.0.1/src/minifactory/plugins/builtin/flutter_tool.py +45 -0
- minifactory-0.0.1/src/minifactory/plugins/builtin/intakes.py +118 -0
- minifactory-0.0.1/src/minifactory/plugins/builtin/providers_bedrock.py +77 -0
- minifactory-0.0.1/src/minifactory/plugins/builtin/providers_github.py +102 -0
- minifactory-0.0.1/src/minifactory/plugins/builtin/providers_llm.py +106 -0
- minifactory-0.0.1/src/minifactory/plugins/builtin/providers_misc.py +85 -0
- minifactory-0.0.1/src/minifactory/plugins/builtin/providers_scm.py +125 -0
- minifactory-0.0.1/src/minifactory/plugins/builtin/routers.py +102 -0
- minifactory-0.0.1/src/minifactory/plugins/builtin/steps_agent.py +212 -0
- minifactory-0.0.1/src/minifactory/plugins/builtin/steps_build.py +61 -0
- minifactory-0.0.1/src/minifactory/plugins/builtin/steps_existing.py +128 -0
- minifactory-0.0.1/src/minifactory/plugins/builtin/steps_flutter.py +123 -0
- minifactory-0.0.1/src/minifactory/plugins/builtin/steps_scm.py +92 -0
- minifactory-0.0.1/src/minifactory/plugins/builtin/steps_workflows.py +87 -0
- minifactory-0.0.1/src/minifactory/plugins/builtin/util.py +191 -0
- minifactory-0.0.1/src/minifactory/py.typed +0 -0
- minifactory-0.0.1/src/minifactory/review.py +48 -0
- minifactory-0.0.1/src/minifactory/router.py +85 -0
- minifactory-0.0.1/src/minifactory/templates/create-workflow/workflow.yaml +72 -0
- minifactory-0.0.1/src/minifactory/templates/fix-bug/assets/tasks/README.md +9 -0
- minifactory-0.0.1/src/minifactory/templates/fix-bug/workflow.yaml +117 -0
- minifactory-0.0.1/src/minifactory/templates/flutter-app-factory/assets/fixtures/flutter/bad_spec/plan.json +1 -0
- minifactory-0.0.1/src/minifactory/templates/flutter-app-factory/assets/fixtures/flutter/counter_needs_repair/implement/counter.txt +42 -0
- minifactory-0.0.1/src/minifactory/templates/flutter-app-factory/assets/fixtures/flutter/counter_needs_repair/plan.json +3 -0
- minifactory-0.0.1/src/minifactory/templates/flutter-app-factory/assets/fixtures/flutter/counter_needs_repair/repair/1.txt +42 -0
- minifactory-0.0.1/src/minifactory/templates/flutter-app-factory/assets/fixtures/flutter/notes_low_coverage/implement/screen.txt +25 -0
- minifactory-0.0.1/src/minifactory/templates/flutter-app-factory/assets/fixtures/flutter/notes_low_coverage/implement/storage.txt +8 -0
- minifactory-0.0.1/src/minifactory/templates/flutter-app-factory/assets/fixtures/flutter/notes_low_coverage/plan.json +4 -0
- minifactory-0.0.1/src/minifactory/templates/flutter-app-factory/assets/fixtures/flutter/todo_app/implement/list_screen.txt +35 -0
- minifactory-0.0.1/src/minifactory/templates/flutter-app-factory/assets/fixtures/flutter/todo_app/implement/model.txt +20 -0
- minifactory-0.0.1/src/minifactory/templates/flutter-app-factory/assets/fixtures/flutter/todo_app/plan.json +4 -0
- minifactory-0.0.1/src/minifactory/templates/flutter-app-factory/assets/specs/bad_spec.yaml +3 -0
- minifactory-0.0.1/src/minifactory/templates/flutter-app-factory/assets/specs/counter_needs_repair.yaml +4 -0
- minifactory-0.0.1/src/minifactory/templates/flutter-app-factory/assets/specs/notes_low_coverage.yaml +5 -0
- minifactory-0.0.1/src/minifactory/templates/flutter-app-factory/assets/specs/todo_app.yaml +5 -0
- minifactory-0.0.1/src/minifactory/templates/flutter-app-factory/workflow.yaml +80 -0
- minifactory-0.0.1/src/minifactory/templates/flutter-app-factory-bedrock/assets/specs/todo_app.yaml +5 -0
- minifactory-0.0.1/src/minifactory/templates/flutter-app-factory-bedrock/workflow.yaml +97 -0
- minifactory-0.0.1/src/minifactory/templates/java-migration-demo/workflow.yaml +86 -0
- minifactory-0.0.1/src/minifactory/templates.py +57 -0
- minifactory-0.0.1/src/minifactory/ui.py +190 -0
- minifactory-0.0.1/src/minifactory/workspace.py +107 -0
- minifactory-0.0.1/src/minifactory.egg-info/PKG-INFO +249 -0
- minifactory-0.0.1/src/minifactory.egg-info/SOURCES.txt +82 -0
- minifactory-0.0.1/src/minifactory.egg-info/dependency_links.txt +1 -0
- minifactory-0.0.1/src/minifactory.egg-info/entry_points.txt +2 -0
- minifactory-0.0.1/src/minifactory.egg-info/requires.txt +13 -0
- minifactory-0.0.1/src/minifactory.egg-info/top_level.txt +1 -0
- minifactory-0.0.1/tests/test_bedrock.py +63 -0
- minifactory-0.0.1/tests/test_create_workflow.py +206 -0
- minifactory-0.0.1/tests/test_demo.py +78 -0
- minifactory-0.0.1/tests/test_engine.py +126 -0
- minifactory-0.0.1/tests/test_existing.py +231 -0
- minifactory-0.0.1/tests/test_expr.py +33 -0
- minifactory-0.0.1/tests/test_flutter.py +170 -0
- minifactory-0.0.1/tests/test_output.py +175 -0
- minifactory-0.0.1/tests/test_patches.py +70 -0
- minifactory-0.0.1/tests/test_router.py +259 -0
- minifactory-0.0.1/tests/test_ui.py +45 -0
- minifactory-0.0.1/tests/test_workflow.py +55 -0
- minifactory-0.0.1/tests/test_workspace.py +195 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Juan Campuzano
|
|
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,249 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: minifactory
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A tiny, pluggable software factory: declarative workflows made of plugin steps
|
|
5
|
+
Author: Juan Campuzano
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/juan-campuzano/minifactory
|
|
8
|
+
Project-URL: Issues, https://github.com/juan-campuzano/minifactory/issues
|
|
9
|
+
Keywords: workflow,agents,llm,automation,software-factory
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: pydantic>=2
|
|
19
|
+
Requires-Dist: pyyaml>=6
|
|
20
|
+
Requires-Dist: rich>=13
|
|
21
|
+
Requires-Dist: certifi
|
|
22
|
+
Provides-Extra: aws
|
|
23
|
+
Requires-Dist: boto3; extra == "aws"
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest; extra == "dev"
|
|
26
|
+
Requires-Dist: ruff; extra == "dev"
|
|
27
|
+
Requires-Dist: mypy; extra == "dev"
|
|
28
|
+
Requires-Dist: types-PyYAML; extra == "dev"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
<p align="center">
|
|
32
|
+
<picture>
|
|
33
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/juan-campuzano/minifactory/main/assets/brand/png/minifactory-horizontal-dark.png">
|
|
34
|
+
<img src="https://raw.githubusercontent.com/juan-campuzano/minifactory/main/assets/brand/png/minifactory-horizontal-light.png" alt="minifactory" width="420">
|
|
35
|
+
</picture>
|
|
36
|
+
</p>
|
|
37
|
+
|
|
38
|
+
A tiny, pluggable **software factory**. You describe what you need, a **workflow** does the work, and every
|
|
39
|
+
step an AI agent takes is fenced in by deterministic checks, budgets and a human-reviewable result.
|
|
40
|
+
|
|
41
|
+
- **Declarative workflows**: YAML made of plugin steps, validated before anything runs.
|
|
42
|
+
- **Agents on a leash**: the LLM is a step, never the boss. Its output is untrusted, bounded by budgets and
|
|
43
|
+
followed by real gates (a compiler, a test run, a diff-size limit).
|
|
44
|
+
- **Everything environment-specific sits behind ports** (git host, LLM, secrets, job source, executor), so
|
|
45
|
+
swapping GitHub for another host or Bedrock for another model is a one-line change.
|
|
46
|
+
- **Ask in plain language**: `minifactory ask "..."` picks the right workflow, drafts its input, and asks before it runs.
|
|
47
|
+
|
|
48
|
+
> Status: **alpha**. The engine, the CLI and three example workflows work and are tested; see
|
|
49
|
+
> [Limitations](#limitations) for what has and has not been verified.
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
┌──────────────── core (src/minifactory/core) ────────────────┐
|
|
53
|
+
│ engine · workflow validation · registry · events · expr │ knows nothing about
|
|
54
|
+
└───────────────────────────┬─────────────────────────────┘ languages, git hosts or LLMs
|
|
55
|
+
ports: scm · llm · secrets · source · executor · router · intake
|
|
56
|
+
│
|
|
57
|
+
┌───────────── plugins (src/minifactory/plugins/builtin) ─────┐
|
|
58
|
+
│ github · local-git · bedrock · openai · jev router │
|
|
59
|
+
│ shell.run · agent.edit · flutter.* · scm.open_pr · ... │
|
|
60
|
+
└─────────────────────────────────────────────────────────┘
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The rule that keeps it from becoming spaghetti: **the core never imports a plugin**; plugins register themselves.
|
|
64
|
+
|
|
65
|
+
## Install
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
git clone https://github.com/juan-campuzano/minifactory && cd minifactory
|
|
69
|
+
python -m venv .venv && source .venv/bin/activate
|
|
70
|
+
pip install -e ".[dev]" # add ",aws" for the Amazon Bedrock LLM (installs boto3)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Python 3.11+. Workflows live in a **workspace**, not in the package (see below).
|
|
74
|
+
|
|
75
|
+
## Quick start (offline, no keys, no SDK)
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
minifactory init my-factory && cd my-factory # a workspace: minifactory.yaml + workflows/
|
|
79
|
+
minifactory workflows list # templates that ship with the package
|
|
80
|
+
minifactory workflows copy flutter-app-factory # copies the workflow and its sample inputs
|
|
81
|
+
minifactory validate flutter-app-factory # fail-fast checks, runs nothing
|
|
82
|
+
minifactory run flutter-app-factory # runs 4 sample jobs with canned LLM replies
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
This example factory turns an app spec into a project: plan, implement, build/test gates, an automatic repair
|
|
86
|
+
loop, a non-blocking coverage check, then delivery. The offline run uses a *simulated* `flutter` and canned LLM
|
|
87
|
+
replies, so what it produces is not a real project. It exists to show every path the engine can take:
|
|
88
|
+
|
|
89
|
+
| sample job | what it exercises |
|
|
90
|
+
|---|---|
|
|
91
|
+
| `todo_app` | happy path |
|
|
92
|
+
| `counter_needs_repair` | the first attempt breaks the build; the repair loop fixes it |
|
|
93
|
+
| `notes_low_coverage` | coverage below the threshold never blocks delivery, it annotates it |
|
|
94
|
+
| `bad_spec` | the plan is not valid JSON, so the job fails early and nothing is built |
|
|
95
|
+
|
|
96
|
+
Each job leaves `state.json`, `result.json` and its working copy in `.minifactory/runs/<workflow>/<job>/`;
|
|
97
|
+
`events.jsonl` records every step and LLM call (tokens and cost when the provider reports them).
|
|
98
|
+
`minifactory run ... --resume` restarts from the first step that did not finish `ok`.
|
|
99
|
+
|
|
100
|
+
## Workspaces
|
|
101
|
+
|
|
102
|
+
Code ships in the package; your content lives in a workspace, found like git (nearest `minifactory.yaml` going up from
|
|
103
|
+
the current directory, else `~/.config/minifactory`):
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
my-factory/
|
|
107
|
+
minifactory.yaml optional settings: runs_dir, output_dir, router, threshold, plugins
|
|
108
|
+
workflows/ your workflows, the ones `minifactory ask` can choose from
|
|
109
|
+
... anything your workflows reference (specs/, fixtures/...), relative to this folder
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Relative paths inside a workflow resolve from the workspace root, wherever you run `minifactory` inside it.
|
|
113
|
+
Command-line flags override `minifactory.yaml`, which overrides the defaults.
|
|
114
|
+
|
|
115
|
+
## Example workflows (templates)
|
|
116
|
+
|
|
117
|
+
Copy one into your workspace with `minifactory workflows copy NAME`; only copied workflows are routable by `ask`.
|
|
118
|
+
|
|
119
|
+
| template | what it does | needs |
|
|
120
|
+
|---|---|---|
|
|
121
|
+
| `flutter-app-factory` | the offline demo above | nothing |
|
|
122
|
+
| `flutter-app-factory-bedrock` | creates a **new** Flutter app from a one-line request, sized to the request ("minimal" means few features) | AWS credentials (Bedrock), Flutter SDK |
|
|
123
|
+
| `fix-bug` | fixes a bug in an **existing** GitHub repo: reproduces it with a failing test, fixes it minimally, opens a PR | `gh auth login`, AWS credentials, Flutter SDK |
|
|
124
|
+
| `create-workflow` | drafts a **new workflow** from a plain-language goal, using only the steps and plugins that exist (see below) | AWS credentials (Bedrock) |
|
|
125
|
+
| `java-migration-demo` | a batch transform over toy repos: deterministic branch vs LLM branch, baseline gate, repair loop | nothing |
|
|
126
|
+
|
|
127
|
+
## Describe what you need: `minifactory ask`
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
export TYPESAFE_API_KEY=... # for the default router (Jev); `--router keyword` works offline
|
|
131
|
+
minifactory ask "a simple app to track my expenses" --dry-run # picks a workflow, drafts its input, runs nothing
|
|
132
|
+
minifactory ask "a simple app to track my expenses" # same, then asks before running
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
1. A **router** plugin chooses among the workflows that declare a `route:` block, and says how confident it is.
|
|
136
|
+
2. Policy lives in code, not in the model: with `--yes` it only acts when the router is confident, the request is
|
|
137
|
+
actionable and a workflow fits; otherwise it exits without doing anything.
|
|
138
|
+
3. The workflow's **intake** plugin drafts its input from your text (the text is treated as data, the result is
|
|
139
|
+
validated), and you confirm before anything runs.
|
|
140
|
+
4. **Where results go** is your choice for new projects: `--output local` (a plain git folder) or
|
|
141
|
+
`--output github` (a new private repo plus a pull request; `--public` to change that).
|
|
142
|
+
|
|
143
|
+
## Fix a bug in an existing repo
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
minifactory workflows copy fix-bug
|
|
147
|
+
minifactory ask "the counter goes up by 2 instead of 1 in acme/todo_app" --dry-run
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`fix-bug` clones the repo with `gh`, requires the repo to analyse cleanly, selects the relevant files, writes a test
|
|
151
|
+
that reproduces the bug (and stops without changing anything if that test does not fail), fixes it with an
|
|
152
|
+
exact-match patch, re-runs the checks, enforces a size limit on the change, and opens a pull request.
|
|
153
|
+
It never touches the default branch.
|
|
154
|
+
|
|
155
|
+
## Let the factory write workflows
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
minifactory workflows copy create-workflow
|
|
159
|
+
minifactory ask "create a workflow that clones a GitHub repo, runs dart format and opens a pull request"
|
|
160
|
+
minifactory workflows list # the result is listed under "drafts"
|
|
161
|
+
minifactory workflows enable dart-format-pr # shows what it would do, asks, then activates it
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The agent reads a catalog generated from the plugins installed *right now* (`minifactory plugins --schema` prints the same
|
|
165
|
+
text), writes `draft.yaml`, and must pass the same validator as any workflow; errors go back to it through the usual repair
|
|
166
|
+
loop. If the goal needs something that does not exist it declines with `MISSING: <what>` instead of inventing a step.
|
|
167
|
+
The result is only ever a **draft**: it is not routable by `ask` and never runs until you enable it, and `enable` first lists
|
|
168
|
+
every shell command, repository write and paid LLM call it contains.
|
|
169
|
+
|
|
170
|
+
## Design principles
|
|
171
|
+
|
|
172
|
+
- **Untrusted agent output.** Edits are parsed strictly: a `PATCH` must match exactly once or nothing is written;
|
|
173
|
+
paths cannot escape the repo; plans are validated with pydantic; the Bedrock provider rejects a reply cut off by the token limit.
|
|
174
|
+
- **Bounded loops.** Repair attempts, unproductive attempts, features per plan and context size all have budgets.
|
|
175
|
+
- **Deterministic gates after every agent step.** `on_fail: discard` stops a job without changing anything;
|
|
176
|
+
`blocking: false` lets enrichment steps annotate a result without stopping it.
|
|
177
|
+
- **Generated workflows are drafts.** A person reviews and enables them; nothing an LLM wrote runs on its own.
|
|
178
|
+
- **Proposals, not pushes.** Changes to existing repos arrive as a pull request on a new branch.
|
|
179
|
+
- **No silent fakes.** The simulated `flutter` is opt-in (`tool: fake` or `FACTORY_FLUTTER=fake`); by default the
|
|
180
|
+
real SDK is required and a missing one is an error.
|
|
181
|
+
|
|
182
|
+
## The workflow format
|
|
183
|
+
|
|
184
|
+
Deliberately small: sequence, `when`, `on_fail: fail|discard|continue`, `blocking: false`, and `${...}` templating
|
|
185
|
+
over `job`, `steps`, `vars` and `env`. No loops, no calls, no arithmetic: if you need that, write a step in Python.
|
|
186
|
+
Everything checkable is checked at load time: unknown plugins, typos in parameters, missing providers, bad `when`
|
|
187
|
+
syntax, references to steps that run later.
|
|
188
|
+
|
|
189
|
+
## Write a plugin
|
|
190
|
+
|
|
191
|
+
No core changes needed. A step is a class with typed parameters:
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
# my_plugin.py
|
|
195
|
+
from minifactory.core.context import Step, StrictParams
|
|
196
|
+
from minifactory.core.models import StepResult
|
|
197
|
+
from minifactory.core.registry import register
|
|
198
|
+
|
|
199
|
+
class Params(StrictParams): # unknown keys in the YAML are errors
|
|
200
|
+
message: str
|
|
201
|
+
|
|
202
|
+
@register("step", "demo.hello")
|
|
203
|
+
class Hello(Step):
|
|
204
|
+
Params = Params
|
|
205
|
+
def run(self, ctx, p):
|
|
206
|
+
return StepResult.ok(f"hello {ctx.job.name}: {p.message}")
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
minifactory run my-workflow --plugin my_plugin
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Installed packages can instead expose the `minifactory.plugins` entry point and are discovered automatically.
|
|
214
|
+
Plugin kinds: `step`, `source`, `executor`, `scm`, `llm`, `secrets`, `router`, `intake`. Built in:
|
|
215
|
+
|
|
216
|
+
| kind | built in |
|
|
217
|
+
|---|---|
|
|
218
|
+
| `scm` | `github` (via the `gh` CLI), `local-git`, `local-dir` |
|
|
219
|
+
| `llm` | `bedrock`, `openai` (any OpenAI-compatible endpoint), `fixture`, `scripted` |
|
|
220
|
+
| `router` | `jev` (TypeSafe), `keyword` |
|
|
221
|
+
| `intake` | `app-spec`, `repo-task` |
|
|
222
|
+
| `source` | `app-specs`, `repo-tasks`, `catalog`, `static` |
|
|
223
|
+
| `executor` | `local`, `threads` |
|
|
224
|
+
| `secrets` | `env` |
|
|
225
|
+
|
|
226
|
+
See [`docs/plugins.md`](docs/plugins.md) for each kind's interface, and [`AGENTS.md`](AGENTS.md) if you are an AI
|
|
227
|
+
coding agent (or work with one) on this repo.
|
|
228
|
+
|
|
229
|
+
## Limitations
|
|
230
|
+
|
|
231
|
+
- **Alpha.** Only Python 3.11 and 3.13 are tested in CI.
|
|
232
|
+
- The example workflows target Flutter/Dart. Other stacks need their own steps (or `shell.run`).
|
|
233
|
+
- The real-model runs were small: a one-line app, and a seeded one-line bug in a small repo. Larger apps, where
|
|
234
|
+
features must agree with each other, are not solved: expect failures there.
|
|
235
|
+
- The `github` provider has been exercised against a fake `gh` in tests and has created repos in manual runs; the
|
|
236
|
+
existing-repo flow has only been run against a local git remote, not against a real GitHub repository.
|
|
237
|
+
- No built-in retry or timeout policy at the engine level, and no persistent state store beyond local JSON files.
|
|
238
|
+
|
|
239
|
+
## Development
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
pip install -e ".[dev]"
|
|
243
|
+
ruff check src tests examples
|
|
244
|
+
pytest -q # no network, credentials or Flutter SDK needed
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## License
|
|
248
|
+
|
|
249
|
+
MIT, see [`LICENSE`](LICENSE).
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/juan-campuzano/minifactory/main/assets/brand/png/minifactory-horizontal-dark.png">
|
|
4
|
+
<img src="https://raw.githubusercontent.com/juan-campuzano/minifactory/main/assets/brand/png/minifactory-horizontal-light.png" alt="minifactory" width="420">
|
|
5
|
+
</picture>
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
A tiny, pluggable **software factory**. You describe what you need, a **workflow** does the work, and every
|
|
9
|
+
step an AI agent takes is fenced in by deterministic checks, budgets and a human-reviewable result.
|
|
10
|
+
|
|
11
|
+
- **Declarative workflows**: YAML made of plugin steps, validated before anything runs.
|
|
12
|
+
- **Agents on a leash**: the LLM is a step, never the boss. Its output is untrusted, bounded by budgets and
|
|
13
|
+
followed by real gates (a compiler, a test run, a diff-size limit).
|
|
14
|
+
- **Everything environment-specific sits behind ports** (git host, LLM, secrets, job source, executor), so
|
|
15
|
+
swapping GitHub for another host or Bedrock for another model is a one-line change.
|
|
16
|
+
- **Ask in plain language**: `minifactory ask "..."` picks the right workflow, drafts its input, and asks before it runs.
|
|
17
|
+
|
|
18
|
+
> Status: **alpha**. The engine, the CLI and three example workflows work and are tested; see
|
|
19
|
+
> [Limitations](#limitations) for what has and has not been verified.
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
┌──────────────── core (src/minifactory/core) ────────────────┐
|
|
23
|
+
│ engine · workflow validation · registry · events · expr │ knows nothing about
|
|
24
|
+
└───────────────────────────┬─────────────────────────────┘ languages, git hosts or LLMs
|
|
25
|
+
ports: scm · llm · secrets · source · executor · router · intake
|
|
26
|
+
│
|
|
27
|
+
┌───────────── plugins (src/minifactory/plugins/builtin) ─────┐
|
|
28
|
+
│ github · local-git · bedrock · openai · jev router │
|
|
29
|
+
│ shell.run · agent.edit · flutter.* · scm.open_pr · ... │
|
|
30
|
+
└─────────────────────────────────────────────────────────┘
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The rule that keeps it from becoming spaghetti: **the core never imports a plugin**; plugins register themselves.
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
git clone https://github.com/juan-campuzano/minifactory && cd minifactory
|
|
39
|
+
python -m venv .venv && source .venv/bin/activate
|
|
40
|
+
pip install -e ".[dev]" # add ",aws" for the Amazon Bedrock LLM (installs boto3)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Python 3.11+. Workflows live in a **workspace**, not in the package (see below).
|
|
44
|
+
|
|
45
|
+
## Quick start (offline, no keys, no SDK)
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
minifactory init my-factory && cd my-factory # a workspace: minifactory.yaml + workflows/
|
|
49
|
+
minifactory workflows list # templates that ship with the package
|
|
50
|
+
minifactory workflows copy flutter-app-factory # copies the workflow and its sample inputs
|
|
51
|
+
minifactory validate flutter-app-factory # fail-fast checks, runs nothing
|
|
52
|
+
minifactory run flutter-app-factory # runs 4 sample jobs with canned LLM replies
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This example factory turns an app spec into a project: plan, implement, build/test gates, an automatic repair
|
|
56
|
+
loop, a non-blocking coverage check, then delivery. The offline run uses a *simulated* `flutter` and canned LLM
|
|
57
|
+
replies, so what it produces is not a real project. It exists to show every path the engine can take:
|
|
58
|
+
|
|
59
|
+
| sample job | what it exercises |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `todo_app` | happy path |
|
|
62
|
+
| `counter_needs_repair` | the first attempt breaks the build; the repair loop fixes it |
|
|
63
|
+
| `notes_low_coverage` | coverage below the threshold never blocks delivery, it annotates it |
|
|
64
|
+
| `bad_spec` | the plan is not valid JSON, so the job fails early and nothing is built |
|
|
65
|
+
|
|
66
|
+
Each job leaves `state.json`, `result.json` and its working copy in `.minifactory/runs/<workflow>/<job>/`;
|
|
67
|
+
`events.jsonl` records every step and LLM call (tokens and cost when the provider reports them).
|
|
68
|
+
`minifactory run ... --resume` restarts from the first step that did not finish `ok`.
|
|
69
|
+
|
|
70
|
+
## Workspaces
|
|
71
|
+
|
|
72
|
+
Code ships in the package; your content lives in a workspace, found like git (nearest `minifactory.yaml` going up from
|
|
73
|
+
the current directory, else `~/.config/minifactory`):
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
my-factory/
|
|
77
|
+
minifactory.yaml optional settings: runs_dir, output_dir, router, threshold, plugins
|
|
78
|
+
workflows/ your workflows, the ones `minifactory ask` can choose from
|
|
79
|
+
... anything your workflows reference (specs/, fixtures/...), relative to this folder
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Relative paths inside a workflow resolve from the workspace root, wherever you run `minifactory` inside it.
|
|
83
|
+
Command-line flags override `minifactory.yaml`, which overrides the defaults.
|
|
84
|
+
|
|
85
|
+
## Example workflows (templates)
|
|
86
|
+
|
|
87
|
+
Copy one into your workspace with `minifactory workflows copy NAME`; only copied workflows are routable by `ask`.
|
|
88
|
+
|
|
89
|
+
| template | what it does | needs |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| `flutter-app-factory` | the offline demo above | nothing |
|
|
92
|
+
| `flutter-app-factory-bedrock` | creates a **new** Flutter app from a one-line request, sized to the request ("minimal" means few features) | AWS credentials (Bedrock), Flutter SDK |
|
|
93
|
+
| `fix-bug` | fixes a bug in an **existing** GitHub repo: reproduces it with a failing test, fixes it minimally, opens a PR | `gh auth login`, AWS credentials, Flutter SDK |
|
|
94
|
+
| `create-workflow` | drafts a **new workflow** from a plain-language goal, using only the steps and plugins that exist (see below) | AWS credentials (Bedrock) |
|
|
95
|
+
| `java-migration-demo` | a batch transform over toy repos: deterministic branch vs LLM branch, baseline gate, repair loop | nothing |
|
|
96
|
+
|
|
97
|
+
## Describe what you need: `minifactory ask`
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
export TYPESAFE_API_KEY=... # for the default router (Jev); `--router keyword` works offline
|
|
101
|
+
minifactory ask "a simple app to track my expenses" --dry-run # picks a workflow, drafts its input, runs nothing
|
|
102
|
+
minifactory ask "a simple app to track my expenses" # same, then asks before running
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
1. A **router** plugin chooses among the workflows that declare a `route:` block, and says how confident it is.
|
|
106
|
+
2. Policy lives in code, not in the model: with `--yes` it only acts when the router is confident, the request is
|
|
107
|
+
actionable and a workflow fits; otherwise it exits without doing anything.
|
|
108
|
+
3. The workflow's **intake** plugin drafts its input from your text (the text is treated as data, the result is
|
|
109
|
+
validated), and you confirm before anything runs.
|
|
110
|
+
4. **Where results go** is your choice for new projects: `--output local` (a plain git folder) or
|
|
111
|
+
`--output github` (a new private repo plus a pull request; `--public` to change that).
|
|
112
|
+
|
|
113
|
+
## Fix a bug in an existing repo
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
minifactory workflows copy fix-bug
|
|
117
|
+
minifactory ask "the counter goes up by 2 instead of 1 in acme/todo_app" --dry-run
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`fix-bug` clones the repo with `gh`, requires the repo to analyse cleanly, selects the relevant files, writes a test
|
|
121
|
+
that reproduces the bug (and stops without changing anything if that test does not fail), fixes it with an
|
|
122
|
+
exact-match patch, re-runs the checks, enforces a size limit on the change, and opens a pull request.
|
|
123
|
+
It never touches the default branch.
|
|
124
|
+
|
|
125
|
+
## Let the factory write workflows
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
minifactory workflows copy create-workflow
|
|
129
|
+
minifactory ask "create a workflow that clones a GitHub repo, runs dart format and opens a pull request"
|
|
130
|
+
minifactory workflows list # the result is listed under "drafts"
|
|
131
|
+
minifactory workflows enable dart-format-pr # shows what it would do, asks, then activates it
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The agent reads a catalog generated from the plugins installed *right now* (`minifactory plugins --schema` prints the same
|
|
135
|
+
text), writes `draft.yaml`, and must pass the same validator as any workflow; errors go back to it through the usual repair
|
|
136
|
+
loop. If the goal needs something that does not exist it declines with `MISSING: <what>` instead of inventing a step.
|
|
137
|
+
The result is only ever a **draft**: it is not routable by `ask` and never runs until you enable it, and `enable` first lists
|
|
138
|
+
every shell command, repository write and paid LLM call it contains.
|
|
139
|
+
|
|
140
|
+
## Design principles
|
|
141
|
+
|
|
142
|
+
- **Untrusted agent output.** Edits are parsed strictly: a `PATCH` must match exactly once or nothing is written;
|
|
143
|
+
paths cannot escape the repo; plans are validated with pydantic; the Bedrock provider rejects a reply cut off by the token limit.
|
|
144
|
+
- **Bounded loops.** Repair attempts, unproductive attempts, features per plan and context size all have budgets.
|
|
145
|
+
- **Deterministic gates after every agent step.** `on_fail: discard` stops a job without changing anything;
|
|
146
|
+
`blocking: false` lets enrichment steps annotate a result without stopping it.
|
|
147
|
+
- **Generated workflows are drafts.** A person reviews and enables them; nothing an LLM wrote runs on its own.
|
|
148
|
+
- **Proposals, not pushes.** Changes to existing repos arrive as a pull request on a new branch.
|
|
149
|
+
- **No silent fakes.** The simulated `flutter` is opt-in (`tool: fake` or `FACTORY_FLUTTER=fake`); by default the
|
|
150
|
+
real SDK is required and a missing one is an error.
|
|
151
|
+
|
|
152
|
+
## The workflow format
|
|
153
|
+
|
|
154
|
+
Deliberately small: sequence, `when`, `on_fail: fail|discard|continue`, `blocking: false`, and `${...}` templating
|
|
155
|
+
over `job`, `steps`, `vars` and `env`. No loops, no calls, no arithmetic: if you need that, write a step in Python.
|
|
156
|
+
Everything checkable is checked at load time: unknown plugins, typos in parameters, missing providers, bad `when`
|
|
157
|
+
syntax, references to steps that run later.
|
|
158
|
+
|
|
159
|
+
## Write a plugin
|
|
160
|
+
|
|
161
|
+
No core changes needed. A step is a class with typed parameters:
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
# my_plugin.py
|
|
165
|
+
from minifactory.core.context import Step, StrictParams
|
|
166
|
+
from minifactory.core.models import StepResult
|
|
167
|
+
from minifactory.core.registry import register
|
|
168
|
+
|
|
169
|
+
class Params(StrictParams): # unknown keys in the YAML are errors
|
|
170
|
+
message: str
|
|
171
|
+
|
|
172
|
+
@register("step", "demo.hello")
|
|
173
|
+
class Hello(Step):
|
|
174
|
+
Params = Params
|
|
175
|
+
def run(self, ctx, p):
|
|
176
|
+
return StepResult.ok(f"hello {ctx.job.name}: {p.message}")
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
minifactory run my-workflow --plugin my_plugin
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Installed packages can instead expose the `minifactory.plugins` entry point and are discovered automatically.
|
|
184
|
+
Plugin kinds: `step`, `source`, `executor`, `scm`, `llm`, `secrets`, `router`, `intake`. Built in:
|
|
185
|
+
|
|
186
|
+
| kind | built in |
|
|
187
|
+
|---|---|
|
|
188
|
+
| `scm` | `github` (via the `gh` CLI), `local-git`, `local-dir` |
|
|
189
|
+
| `llm` | `bedrock`, `openai` (any OpenAI-compatible endpoint), `fixture`, `scripted` |
|
|
190
|
+
| `router` | `jev` (TypeSafe), `keyword` |
|
|
191
|
+
| `intake` | `app-spec`, `repo-task` |
|
|
192
|
+
| `source` | `app-specs`, `repo-tasks`, `catalog`, `static` |
|
|
193
|
+
| `executor` | `local`, `threads` |
|
|
194
|
+
| `secrets` | `env` |
|
|
195
|
+
|
|
196
|
+
See [`docs/plugins.md`](docs/plugins.md) for each kind's interface, and [`AGENTS.md`](AGENTS.md) if you are an AI
|
|
197
|
+
coding agent (or work with one) on this repo.
|
|
198
|
+
|
|
199
|
+
## Limitations
|
|
200
|
+
|
|
201
|
+
- **Alpha.** Only Python 3.11 and 3.13 are tested in CI.
|
|
202
|
+
- The example workflows target Flutter/Dart. Other stacks need their own steps (or `shell.run`).
|
|
203
|
+
- The real-model runs were small: a one-line app, and a seeded one-line bug in a small repo. Larger apps, where
|
|
204
|
+
features must agree with each other, are not solved: expect failures there.
|
|
205
|
+
- The `github` provider has been exercised against a fake `gh` in tests and has created repos in manual runs; the
|
|
206
|
+
existing-repo flow has only been run against a local git remote, not against a real GitHub repository.
|
|
207
|
+
- No built-in retry or timeout policy at the engine level, and no persistent state store beyond local JSON files.
|
|
208
|
+
|
|
209
|
+
## Development
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
pip install -e ".[dev]"
|
|
213
|
+
ruff check src tests examples
|
|
214
|
+
pytest -q # no network, credentials or Flutter SDK needed
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## License
|
|
218
|
+
|
|
219
|
+
MIT, see [`LICENSE`](LICENSE).
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "minifactory"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
description = "A tiny, pluggable software factory: declarative workflows made of plugin steps"
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
authors = [{ name = "Juan Campuzano" }]
|
|
13
|
+
keywords = ["workflow", "agents", "llm", "automation", "software-factory"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.13",
|
|
20
|
+
]
|
|
21
|
+
license-files = ["LICENSE"]
|
|
22
|
+
dependencies = ["pydantic>=2", "pyyaml>=6", "rich>=13", "certifi"]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://github.com/juan-campuzano/minifactory"
|
|
26
|
+
Issues = "https://github.com/juan-campuzano/minifactory/issues"
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
aws = ["boto3"]
|
|
30
|
+
dev = ["pytest", "ruff", "mypy", "types-PyYAML"]
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
minifactory = "minifactory.cli:main"
|
|
34
|
+
|
|
35
|
+
# Third-party packages add themselves like this (no change to the core):
|
|
36
|
+
# [project.entry-points."minifactory.plugins"]
|
|
37
|
+
# my_plugin = "my_package.factory_plugin"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.packages.find]
|
|
40
|
+
where = ["src"]
|
|
41
|
+
|
|
42
|
+
[tool.setuptools.package-data]
|
|
43
|
+
minifactory = ["py.typed", "templates/**/*"]
|
|
44
|
+
|
|
45
|
+
[tool.pytest.ini_options]
|
|
46
|
+
testpaths = ["tests"]
|
|
47
|
+
pythonpath = ["src"]
|
|
48
|
+
|
|
49
|
+
[tool.ruff]
|
|
50
|
+
line-length = 120
|
|
51
|
+
target-version = "py311"
|
|
52
|
+
|
|
53
|
+
[tool.ruff.lint]
|
|
54
|
+
# UP035/UP037/B905 are ignored only because the pre-existing core predates them.
|
|
55
|
+
select = ["E", "F", "I", "B", "UP", "SIM"]
|
|
56
|
+
ignore = ["UP035", "UP037", "B905"]
|
|
57
|
+
|
|
58
|
+
[tool.mypy]
|
|
59
|
+
python_version = "3.11"
|
|
60
|
+
strict = true
|
|
61
|
+
files = ["src"]
|
|
62
|
+
plugins = ["pydantic.mypy"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.1"
|