specfuse-loop 0.2.0__tar.gz → 0.3.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.
- {specfuse_loop-0.2.0/specfuse_loop.egg-info → specfuse_loop-0.3.1}/PKG-INFO +23 -18
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/README.md +22 -17
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/pyproject.toml +8 -4
- specfuse_loop-0.3.1/specfuse/loop/data/LEARNINGS.template.md +69 -0
- specfuse_loop-0.3.1/specfuse/loop/data/VERSION +1 -0
- specfuse_loop-0.3.1/specfuse/loop/data/docs/concepts/architecture-addendum-gates-and-iterative-planning.md +97 -0
- specfuse_loop-0.3.1/specfuse/loop/data/docs/concepts/ralph-lineage.md +66 -0
- specfuse_loop-0.3.1/specfuse/loop/data/docs/getting-started.md +221 -0
- specfuse_loop-0.3.1/specfuse/loop/data/docs/methodology.md +322 -0
- specfuse_loop-0.3.1/specfuse/loop/data/docs/skills.md +132 -0
- specfuse_loop-0.3.1/specfuse/loop/data/gitignore.snippet +11 -0
- specfuse_loop-0.3.1/specfuse/loop/data/roadmap.template.md +37 -0
- specfuse_loop-0.3.1/specfuse/loop/data/rules/correlation-ids.md +197 -0
- specfuse_loop-0.3.1/specfuse/loop/data/rules/never-touch.md +81 -0
- specfuse_loop-0.3.1/specfuse/loop/data/rules/result-contract.md +113 -0
- specfuse_loop-0.3.1/specfuse/loop/data/rules/security-boundaries.md +102 -0
- specfuse_loop-0.3.1/specfuse/loop/data/templates/GATE.template.md +43 -0
- specfuse_loop-0.3.1/specfuse/loop/data/templates/PLAN.template.md +74 -0
- specfuse_loop-0.3.1/specfuse/loop/data/templates/WU.template.md +103 -0
- specfuse_loop-0.3.1/specfuse/loop/data/verification.yml.example +53 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/specfuse/loop/lint_plan.py +26 -1
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/specfuse/loop/loop.py +478 -33
- specfuse_loop-0.3.1/specfuse/loop/scaffold.py +630 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1/specfuse_loop.egg-info}/PKG-INFO +23 -18
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/specfuse_loop.egg-info/SOURCES.txt +40 -0
- specfuse_loop-0.3.1/tests/test_arm_gate_edits_uncommitted.py +165 -0
- specfuse_loop-0.3.1/tests/test_autosync.py +200 -0
- specfuse_loop-0.3.1/tests/test_autosync_consent.py +294 -0
- specfuse_loop-0.3.1/tests/test_autosync_firstrun.py +146 -0
- specfuse_loop-0.3.1/tests/test_autosync_plugin.py +245 -0
- specfuse_loop-0.3.1/tests/test_bookkeeping_commit_crash_run.py +121 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_closing_deliverable_guard.py +55 -0
- specfuse_loop-0.3.1/tests/test_doctor.py +269 -0
- specfuse_loop-0.3.1/tests/test_init_integration.py +389 -0
- specfuse_loop-0.3.1/tests/test_leak_findings_redaction.py +109 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_leak_scan.py +9 -0
- specfuse_loop-0.3.1/tests/test_lint_bare_produces_path.py +153 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_loop_orchestration.py +3 -3
- specfuse_loop-0.3.1/tests/test_migrate_legacy.py +268 -0
- specfuse_loop-0.3.1/tests/test_scaffold_data_in_sync.py +104 -0
- specfuse_loop-0.3.1/tests/test_scaffold_docs.py +101 -0
- specfuse_loop-0.3.1/tests/test_scaffold_init.py +133 -0
- specfuse_loop-0.3.1/tests/test_scaffold_manifest.py +144 -0
- specfuse_loop-0.3.1/tests/test_scaffold_resources.py +71 -0
- specfuse_loop-0.3.1/tests/test_scaffold_seed_sanity.py +80 -0
- specfuse_loop-0.3.1/tests/test_scaffold_upgrade.py +184 -0
- specfuse_loop-0.3.1/tests/test_scaffold_wiring.py +325 -0
- specfuse_loop-0.3.1/tests/test_untracked_feature_folder.py +153 -0
- specfuse_loop-0.3.1/tests/test_upgrade_integration.py +391 -0
- specfuse_loop-0.3.1/tests/test_version_consistency.py +120 -0
- specfuse_loop-0.3.1/tests/test_version_skew.py +115 -0
- specfuse_loop-0.2.0/tests/test_version_skew.py +0 -95
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/LICENSE +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/NOTICE +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/setup.cfg +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/specfuse/loop/__init__.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/specfuse/loop/_miniyaml.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/specfuse/loop/adopt_feature.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/specfuse/loop/gate_eval.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/specfuse/loop/gh_backend.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/specfuse/loop/gh_features.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/specfuse/loop/validate_event.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/specfuse_loop.egg-info/dependency_links.txt +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/specfuse_loop.egg-info/entry_points.txt +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/specfuse_loop.egg-info/requires.txt +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/specfuse_loop.egg-info/top_level.txt +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_adopt_feature.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_attempt_outcome_emission.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_backend.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_bookkeeping_commit_hook_crash.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_cost_tracking.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_deliverable_presence_gate.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_driver_integration.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_driver_lock.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_duration_tracking.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_empty_files_escalation.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_ensure_feature_branch.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_extra_gates.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_force_full_close.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_gate_eval.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_gate_eval_calibration.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_gate_eval_intermediate_wiring.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_gate_eval_terminal_wiring.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_gh_backend.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_gh_features.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_git_env_isolation.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_hashed_denylist.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_hashed_denylist_ci.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_leak_scan_content.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_legacy_4wu_terminal_flips.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_lifecycle_integration.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_lint_close_intermediate.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_lint_close_wu.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_lint_correlation_id.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_lint_correlation_id_close_intermediate.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_lint_oracle_env.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_lint_plan_errors.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_lint_plan_next_draft.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_lint_produces_driver_helper.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_lint_sections.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_lint_task_graph_yaml_selection.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_loop_auto_archive.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_loop_caveman_preamble.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_loop_close_intermediate.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_loop_defaults_by_type.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_loop_effort.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_loop_failure_note_cap.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_loop_files_changed_guard.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_loop_gate_budget.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_loop_model_alias.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_loop_post_pass_invariant.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_loop_reset_preserving_events.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_loop_smoke_runner.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_loop_unsandboxed.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_loop_zero_token_guard.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_miniyaml_equivalence.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_miniyaml_negative.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_planned_cost_lint.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_produces_field.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_result_block.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_roadmap_add_skill.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_roadmap_archive_skill.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_roadmap_row_parser.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_squash_commit_hook_crash.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_template_closing_shapes.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_terminal_flip_ownership.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_terminal_flips.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_validate_event.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_verdict_coupling.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.1}/tests/test_verify_empty_gate_set.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: specfuse-loop
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Local-first executor for the Specfuse Plan + Work Unit gate-cycle methodology.
|
|
5
5
|
Author: Specfuse contributors
|
|
6
6
|
License: Apache-2.0
|
|
@@ -77,8 +77,8 @@ the planning rigor Ralph's bare task list lacks.
|
|
|
77
77
|
`.specfuse/scripts/adopt_feature.py <repo> <issue-number>` (or the
|
|
78
78
|
interactive `/adopt-feature` skill) to scaffold a dispatchable feature
|
|
79
79
|
folder from a picked issue.
|
|
80
|
-
- The **driver** (
|
|
81
|
-
work units, dispatches each as a fresh `claude -p` session, runs the unit's
|
|
80
|
+
- The **driver** (`specfuse-loop`, from the pip package) walks the current gate's
|
|
81
|
+
ready work units, dispatches each as a fresh `claude -p` session, runs the unit's
|
|
82
82
|
verification itself as the exit oracle, and commits one squashed,
|
|
83
83
|
trailer-carrying commit per unit. A failed gate is retried with a fresh
|
|
84
84
|
session carrying the failure evidence, up to three attempts, then escalated.
|
|
@@ -101,29 +101,34 @@ In a target single-repo project:
|
|
|
101
101
|
cloning to enable the pre-push hook (runs `scripts/smoke-test.sh` — same
|
|
102
102
|
checks CI runs — before each `git push`). Bypass with `git push --no-verify`.
|
|
103
103
|
|
|
104
|
-
The driver installs from PyPI and the skills
|
|
104
|
+
The driver installs from PyPI and the skills ship as a Claude Code plugin:
|
|
105
105
|
|
|
106
106
|
```bash
|
|
107
|
-
|
|
108
|
-
#
|
|
107
|
+
pipx install specfuse # umbrella CLI; pulls specfuse-loop>=0.3.0
|
|
108
|
+
# gives you: specfuse, specfuse-loop, specfuse-lint (or: python3 -m pip install specfuse, in a venv)
|
|
109
|
+
|
|
110
|
+
# in Claude Code, enable the skills plugin (one-time):
|
|
109
111
|
# /plugin marketplace add specfuse/specfuse
|
|
110
112
|
# /plugin install specfuse@specfuse
|
|
111
113
|
|
|
112
|
-
# scaffold
|
|
113
|
-
./init.sh /path/to/your-project # legacy installer (v1.0; removed in v1.1)
|
|
114
|
+
specfuse init /path/to/your-project # scaffold .specfuse/ + wire .claude/ (--dry-run previews)
|
|
114
115
|
|
|
115
116
|
cd /path/to/your-project
|
|
116
117
|
$EDITOR .specfuse/verification.yml # match the `code` gates to your stack
|
|
117
|
-
# author your first feature
|
|
118
|
-
specfuse-loop --dry-run #
|
|
119
|
-
specfuse-loop
|
|
118
|
+
# author your first feature (in Claude Code: /draft-feature)
|
|
119
|
+
specfuse-loop --dry-run # show the gate walk, no dispatch
|
|
120
|
+
specfuse-loop # the real run
|
|
120
121
|
```
|
|
121
122
|
|
|
122
|
-
> **Distribution
|
|
123
|
-
> `
|
|
124
|
-
>
|
|
125
|
-
>
|
|
126
|
-
>
|
|
123
|
+
> **Distribution.** Code ships via pip — `specfuse` (umbrella CLI: `init` /
|
|
124
|
+
> `upgrade`) pulls `specfuse-loop` (the driver); Claude assets ship via the
|
|
125
|
+
> [`specfuse/specfuse`](https://github.com/specfuse/specfuse) plugin marketplace.
|
|
126
|
+
> `specfuse init` lays down `.specfuse/` and wires `.claude/`; `specfuse upgrade`
|
|
127
|
+
> overlays a newer scaffold and pip-upgrades both packages. Every `specfuse-loop`
|
|
128
|
+
> run self-provisions (version-syncs `.specfuse/` from the installed package), so
|
|
129
|
+
> an upgrade reaches existing projects on their next run. (`./init.sh` is a
|
|
130
|
+
> deprecated v1.0 shim that delegates to `specfuse init`/`upgrade`; slated for
|
|
131
|
+
> removal.)
|
|
127
132
|
|
|
128
133
|
> **One driver per working tree.** The driver holds an exclusive advisory lock on
|
|
129
134
|
> `.specfuse/.loop.lock` for the duration of a run; a second driver targeting the
|
|
@@ -145,7 +150,7 @@ python .specfuse/scripts/loop.py --dry-run
|
|
|
145
150
|
```
|
|
146
151
|
specfuse-loop/
|
|
147
152
|
├── LICENSE NOTICE CONTRIBUTING.md README.md .gitignore
|
|
148
|
-
├── init.sh
|
|
153
|
+
├── init.sh deprecated v1.0 shim → delegates to `specfuse init`/`upgrade`
|
|
149
154
|
├── docs/
|
|
150
155
|
│ ├── getting-started.md narrated first-feature + operator walkthrough
|
|
151
156
|
│ ├── methodology.md the gate-cycle contract (shared with the orchestrator)
|
|
@@ -164,7 +169,7 @@ specfuse-loop/
|
|
|
164
169
|
└── features/FEAT-2026-0001-health-endpoint/ (the worked example)
|
|
165
170
|
```
|
|
166
171
|
|
|
167
|
-
`init
|
|
172
|
+
`specfuse init` also ships the durable docs — `methodology.md`, `skills.md`, and
|
|
168
173
|
`concepts/` — into a target's `.specfuse/docs/`, so an initialized repo is
|
|
169
174
|
self-documenting without this checkout.
|
|
170
175
|
|
|
@@ -48,8 +48,8 @@ the planning rigor Ralph's bare task list lacks.
|
|
|
48
48
|
`.specfuse/scripts/adopt_feature.py <repo> <issue-number>` (or the
|
|
49
49
|
interactive `/adopt-feature` skill) to scaffold a dispatchable feature
|
|
50
50
|
folder from a picked issue.
|
|
51
|
-
- The **driver** (
|
|
52
|
-
work units, dispatches each as a fresh `claude -p` session, runs the unit's
|
|
51
|
+
- The **driver** (`specfuse-loop`, from the pip package) walks the current gate's
|
|
52
|
+
ready work units, dispatches each as a fresh `claude -p` session, runs the unit's
|
|
53
53
|
verification itself as the exit oracle, and commits one squashed,
|
|
54
54
|
trailer-carrying commit per unit. A failed gate is retried with a fresh
|
|
55
55
|
session carrying the failure evidence, up to three attempts, then escalated.
|
|
@@ -72,29 +72,34 @@ In a target single-repo project:
|
|
|
72
72
|
cloning to enable the pre-push hook (runs `scripts/smoke-test.sh` — same
|
|
73
73
|
checks CI runs — before each `git push`). Bypass with `git push --no-verify`.
|
|
74
74
|
|
|
75
|
-
The driver installs from PyPI and the skills
|
|
75
|
+
The driver installs from PyPI and the skills ship as a Claude Code plugin:
|
|
76
76
|
|
|
77
77
|
```bash
|
|
78
|
-
|
|
79
|
-
#
|
|
78
|
+
pipx install specfuse # umbrella CLI; pulls specfuse-loop>=0.3.0
|
|
79
|
+
# gives you: specfuse, specfuse-loop, specfuse-lint (or: python3 -m pip install specfuse, in a venv)
|
|
80
|
+
|
|
81
|
+
# in Claude Code, enable the skills plugin (one-time):
|
|
80
82
|
# /plugin marketplace add specfuse/specfuse
|
|
81
83
|
# /plugin install specfuse@specfuse
|
|
82
84
|
|
|
83
|
-
# scaffold
|
|
84
|
-
./init.sh /path/to/your-project # legacy installer (v1.0; removed in v1.1)
|
|
85
|
+
specfuse init /path/to/your-project # scaffold .specfuse/ + wire .claude/ (--dry-run previews)
|
|
85
86
|
|
|
86
87
|
cd /path/to/your-project
|
|
87
88
|
$EDITOR .specfuse/verification.yml # match the `code` gates to your stack
|
|
88
|
-
# author your first feature
|
|
89
|
-
specfuse-loop --dry-run #
|
|
90
|
-
specfuse-loop
|
|
89
|
+
# author your first feature (in Claude Code: /draft-feature)
|
|
90
|
+
specfuse-loop --dry-run # show the gate walk, no dispatch
|
|
91
|
+
specfuse-loop # the real run
|
|
91
92
|
```
|
|
92
93
|
|
|
93
|
-
> **Distribution
|
|
94
|
-
> `
|
|
95
|
-
>
|
|
96
|
-
>
|
|
97
|
-
>
|
|
94
|
+
> **Distribution.** Code ships via pip — `specfuse` (umbrella CLI: `init` /
|
|
95
|
+
> `upgrade`) pulls `specfuse-loop` (the driver); Claude assets ship via the
|
|
96
|
+
> [`specfuse/specfuse`](https://github.com/specfuse/specfuse) plugin marketplace.
|
|
97
|
+
> `specfuse init` lays down `.specfuse/` and wires `.claude/`; `specfuse upgrade`
|
|
98
|
+
> overlays a newer scaffold and pip-upgrades both packages. Every `specfuse-loop`
|
|
99
|
+
> run self-provisions (version-syncs `.specfuse/` from the installed package), so
|
|
100
|
+
> an upgrade reaches existing projects on their next run. (`./init.sh` is a
|
|
101
|
+
> deprecated v1.0 shim that delegates to `specfuse init`/`upgrade`; slated for
|
|
102
|
+
> removal.)
|
|
98
103
|
|
|
99
104
|
> **One driver per working tree.** The driver holds an exclusive advisory lock on
|
|
100
105
|
> `.specfuse/.loop.lock` for the duration of a run; a second driver targeting the
|
|
@@ -116,7 +121,7 @@ python .specfuse/scripts/loop.py --dry-run
|
|
|
116
121
|
```
|
|
117
122
|
specfuse-loop/
|
|
118
123
|
├── LICENSE NOTICE CONTRIBUTING.md README.md .gitignore
|
|
119
|
-
├── init.sh
|
|
124
|
+
├── init.sh deprecated v1.0 shim → delegates to `specfuse init`/`upgrade`
|
|
120
125
|
├── docs/
|
|
121
126
|
│ ├── getting-started.md narrated first-feature + operator walkthrough
|
|
122
127
|
│ ├── methodology.md the gate-cycle contract (shared with the orchestrator)
|
|
@@ -135,7 +140,7 @@ specfuse-loop/
|
|
|
135
140
|
└── features/FEAT-2026-0001-health-endpoint/ (the worked example)
|
|
136
141
|
```
|
|
137
142
|
|
|
138
|
-
`init
|
|
143
|
+
`specfuse init` also ships the durable docs — `methodology.md`, `skills.md`, and
|
|
139
144
|
`concepts/` — into a target's `.specfuse/docs/`, so an initialized repo is
|
|
140
145
|
self-documenting without this checkout.
|
|
141
146
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "specfuse-loop"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.1"
|
|
8
8
|
description = "Local-first executor for the Specfuse Plan + Work Unit gate-cycle methodology."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -56,8 +56,12 @@ specfuse-lint = "specfuse.loop.lint_plan:main"
|
|
|
56
56
|
|
|
57
57
|
[tool.setuptools.packages.find]
|
|
58
58
|
# Ship ONLY the specfuse namespace package. Without this scope, setuptools'
|
|
59
|
-
# auto-discovery sweeps tests/, docs/, and scripts/ into the wheel.
|
|
60
|
-
# data (templates, rules, features) lives in the consumer's `.specfuse/`, not in
|
|
61
|
-
# the wheel, so the package is pure code.
|
|
59
|
+
# auto-discovery sweeps tests/, docs/, and scripts/ into the wheel.
|
|
62
60
|
include = ["specfuse*"]
|
|
63
61
|
namespaces = true
|
|
62
|
+
|
|
63
|
+
[tool.setuptools.package-data]
|
|
64
|
+
# Include the scaffold seed so `specfuse init` / `specfuse upgrade` can copy
|
|
65
|
+
# templates, rules, and other seed files out of the wheel without needing the
|
|
66
|
+
# loop source repo on disk.
|
|
67
|
+
"specfuse.loop" = ["data/**/*", "data/*"]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# LEARNINGS
|
|
2
|
+
|
|
3
|
+
Durable, reusable rules distilled from every gate's retrospective. The `lessons` work
|
|
4
|
+
unit appends here; planning reads here before detailing any new feature. This is the
|
|
5
|
+
feedback loop that makes each plan better than the last.
|
|
6
|
+
|
|
7
|
+
Append only. Phrase each entry as a rule that would change how a FUTURE work unit is
|
|
8
|
+
written or executed, not a one-off observation. De-duplicate against what is here.
|
|
9
|
+
Feature-specific observations stay in that feature's `RETROSPECTIVE.md` and are not
|
|
10
|
+
promoted here.
|
|
11
|
+
|
|
12
|
+
## Format
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
- [FEAT-YYYY-NNNN/G1] Implementation WUs must name the module a new route/handler
|
|
16
|
+
lives in; "add it to the router" cost a blocked attempt when no router existed yet.
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Entries
|
|
20
|
+
|
|
21
|
+
<!-- lessons work units append below this line -->
|
|
22
|
+
<!--
|
|
23
|
+
The entries below are GENERIC methodology lessons that ship with the scaffold —
|
|
24
|
+
they are about how to write and run work units, not about any one project. Your
|
|
25
|
+
project's own `lessons` work units append project-specific rules beneath them.
|
|
26
|
+
-->
|
|
27
|
+
|
|
28
|
+
- [meta/first-live-use] Scope a feature's acceptance criteria to the feature's
|
|
29
|
+
own footprint — its slug, the paths it creates or edits, the symbols it
|
|
30
|
+
introduces. Acceptance criteria that grep or scan the WHOLE repo will trip
|
|
31
|
+
on pre-existing, unrelated state and (correctly) cause the agent to emit
|
|
32
|
+
`status: blocked` even when the WU's own work is fine. Example failure mode:
|
|
33
|
+
a "no TODO comments anywhere in the tree" check that fires on legacy code
|
|
34
|
+
the WU never touches. Rule: bound checks to the feature's path prefixes
|
|
35
|
+
(e.g. `src/<slug>/**`) or to files the WU declares in `generated_surfaces` /
|
|
36
|
+
`files_changed`; repo-wide invariants belong in a separate hygiene WU or in
|
|
37
|
+
the repo's `code` gate set, not in a per-feature acceptance criterion.
|
|
38
|
+
|
|
39
|
+
- [meta/first-live-use] Name what the WU is expected to PRODUCE, not only what
|
|
40
|
+
it must NOT touch. The "Do not touch" section bounds the WU on one side;
|
|
41
|
+
without an equally-explicit "produces" list, an agent can helpfully write
|
|
42
|
+
files that should belong to a later WU (e.g. docs that were T92's job
|
|
43
|
+
showing up in T01's commit) without the verification gates objecting. Rule:
|
|
44
|
+
in addition to the "Do not touch" list, the WU's Acceptance criteria should
|
|
45
|
+
name the specific files/sections the WU is expected to author. A reviewer
|
|
46
|
+
reading the diff should be able to point at every changed file and find it
|
|
47
|
+
in either the WU's produces-list or the gate's verification output.
|
|
48
|
+
|
|
49
|
+
- [meta/first-live-use] The "hygiene WU" pattern — when a substantive WU
|
|
50
|
+
discovers a pre-existing bug in a path its "Do not touch" rule forbids
|
|
51
|
+
(typical case: shared module, infrastructure config, dependency version),
|
|
52
|
+
the right move is to insert a narrow hygiene WU EARLIER in the gate (or as
|
|
53
|
+
a precursor gate) that fixes only that issue. Not: loosen the blocked WU's
|
|
54
|
+
scope to permit the cross-cutting fix (muddies its boundary). Not: fix it
|
|
55
|
+
manually out-of-loop and pretend the gate ran clean (silent drift between
|
|
56
|
+
the methodology's history and git's). The hygiene WU should have a single,
|
|
57
|
+
obvious acceptance criterion and pass on its own verification; the original
|
|
58
|
+
blocked WU then runs after, unmodified.
|
|
59
|
+
|
|
60
|
+
- [meta/loop-driver-bugs] Driver bookkeeping (frontmatter status flips,
|
|
61
|
+
events.jsonl appends, per-attempt notes) must be committed if it should
|
|
62
|
+
survive across WUs — uncommitted writes are wiped by the inter-attempt
|
|
63
|
+
`git reset --hard`. Agent-work commits (per-WU squash) are separate from
|
|
64
|
+
bookkeeping commits (`chore(loop): ...`). When authoring WUs whose
|
|
65
|
+
verification commands themselves write to disk, remember the agent's
|
|
66
|
+
working tree is reset between failed attempts — scratch files written
|
|
67
|
+
during a failed attempt won't persist into the next attempt's prompt unless
|
|
68
|
+
the agent explicitly buffers them in the prompt-facing failure note the
|
|
69
|
+
driver hands to the next attempt.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.3.1
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Architecture Addendum — Gates and the iterative planning cycle (Model B)
|
|
2
|
+
|
|
3
|
+
> **Status: adopted (2026-06).** Gate placement is resolved as **Model B — gates live in the
|
|
4
|
+
> loop, per component, NOT in the orchestrator PM.** The gate cycle was proven on a real
|
|
5
|
+
> multi-gate feature (loop `FEAT-2026-0003`: plan-next drafted real, armable next gates across
|
|
6
|
+
> three cycles). This addendum records that decision and what it means for the orchestrator.
|
|
7
|
+
>
|
|
8
|
+
> **This supersedes the earlier Model-A proposal** (an earlier revision of this file that
|
|
9
|
+
> proposed folding gate identification / `plan-next` / per-gate `plan_review` into the PM agent as
|
|
10
|
+
> a `v1.7.0` behavioral change). That fold-in was **not adopted**; the PM does not gain gate
|
|
11
|
+
> machinery. See the orchestrator repo's `docs/gate-placement-proposal.md` (Model A vs B, decision
|
|
12
|
+
> criteria) and `docs/naming-convention.md` for the canonical contracts.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 1. The decision
|
|
17
|
+
|
|
18
|
+
The orchestrator coordinates one level above a single-repo goal. An **initiative**
|
|
19
|
+
(`INIT-YYYY-NNNN`) is decomposed by the PM into a **`feature_graph`** of **features**
|
|
20
|
+
(`INIT-YYYY-NNNN/FNN`), each a single-repo goal dispatched to one component. **Each dispatched
|
|
21
|
+
implementation feature == a loop feature**: the receiving component's loop decomposes it into
|
|
22
|
+
**gates** and **work units** and grinds it through its gate cycle.
|
|
23
|
+
|
|
24
|
+
So gates are **internal to the loop**, not orchestrator state. The orchestrator owns
|
|
25
|
+
`initiative → feature` decomposition, cross-repo dependency ordering, and the spec/generated
|
|
26
|
+
interface contracts between features; it does **not** identify gates, run `plan-next`, or hold a
|
|
27
|
+
per-gate `plan_review`. The loop owns all of that, per [`methodology.md`](methodology.md).
|
|
28
|
+
|
|
29
|
+
**Why Model B (summary).** The loop is single-repo + edit-and-commit; codegen freezes the
|
|
30
|
+
cross-repo interface (generated `emit-*`/`on-*` contracts are immutable `_generated/`), so
|
|
31
|
+
component-loops grind hand-code against frozen boundaries and cannot break each other. This
|
|
32
|
+
dissolves the hardest part of Model A (predicting cross-repo gate boundaries inside the PM) and
|
|
33
|
+
keeps the gate cycle built once, in the loop. Full rationale + the rejected Model A:
|
|
34
|
+
`gate-placement-proposal.md`.
|
|
35
|
+
|
|
36
|
+
## 2. What changed in the orchestrator (minimal — no PM gate machinery)
|
|
37
|
+
|
|
38
|
+
The orchestrator change is the **initiative/feature reframe**, already folded into
|
|
39
|
+
`orchestrator-architecture.md` §1A and `naming-convention.md`:
|
|
40
|
+
|
|
41
|
+
- **Vocabulary / IDs:** initiative → feature → gate → work unit; `INIT-YYYY-NNNN/FNN/TNN`
|
|
42
|
+
(legacy/component-local `FEAT-…/TNN`). Root token = origin.
|
|
43
|
+
- **State machines (unchanged in shape):** the "feature state machine" is the **initiative**
|
|
44
|
+
lifecycle; the "task state machine" is the **feature** lifecycle. Gates/WUs do **not** appear in
|
|
45
|
+
the orchestrator's state machines — they are loop-internal.
|
|
46
|
+
- **PM agent (reframed, not gate-extended):** `feature-decomposition` (was task-decomposition)
|
|
47
|
+
produces a `feature_graph`; `issue-drafting` files feature issues labelled by type;
|
|
48
|
+
`plan-review` reviews the `feature_graph`; `dependency-recomputation` (runtime `scripts/poller.py`)
|
|
49
|
+
flips features `pending → ready`. **No** gate identification, `plan-next`, or per-gate
|
|
50
|
+
`plan_review` in the PM — those were the Model-A additions and are dropped.
|
|
51
|
+
- **Dispatch by feature type:** `implementation` → the component-loop (loop GitHub feature-pick on
|
|
52
|
+
`specfuse:feature`); `qa_*` → the QA agent (`specfuse:qa-feature`), a distinct cross-repo role,
|
|
53
|
+
**not** a loop. QA is the exception to uniform-loop dispatch.
|
|
54
|
+
- **Per-gate autonomy / arming** lives in the loop (not the PM): autonomy flows orchestrator →
|
|
55
|
+
loop (`review`/`supervised` stop at each gate for a human arm; `auto` self-arms safe gates under
|
|
56
|
+
the methodology §9 conjunction). The merge gate stays human until the QA loop is trusted.
|
|
57
|
+
|
|
58
|
+
The orchestrator's earlier "no gates" behavior is therefore not changed by *adding* gates to it —
|
|
59
|
+
gates were placed in the loop instead.
|
|
60
|
+
|
|
61
|
+
## 3. What the loop owns (the gate layer)
|
|
62
|
+
|
|
63
|
+
Per [`methodology.md`](methodology.md): the gate cycle (plan → execute → close → review&arm), the
|
|
64
|
+
four-type closing sequence (`retrospective → lessons → docs → plan-next`), `plan-next` drafting
|
|
65
|
+
the next gate (never arming it), `LEARNINGS.md`, and per-gate autonomy. These are **loop-internal**
|
|
66
|
+
to each dispatched feature; the orchestrator sees only the feature's overall state (via issue
|
|
67
|
+
labels) and its completion (PR merge → merge-watcher → `state:done`).
|
|
68
|
+
|
|
69
|
+
## 4. Reconciliation with the orchestrator (the surface-specific seams)
|
|
70
|
+
|
|
71
|
+
Per the collaboration charter §2 / methodology §10, only these differ between surfaces:
|
|
72
|
+
|
|
73
|
+
| Concern | Loop (single-repo) | Orchestrator (multi-repo) |
|
|
74
|
+
|----------------|------------------------------------------|---------------------------------------------------|
|
|
75
|
+
| State backend | WU/GATE/PLAN frontmatter, git-tracked | GitHub issue labels + the initiative registry |
|
|
76
|
+
| Dispatch | driver shells out (`claude -p`) | poller routes by type → loop / QA agent |
|
|
77
|
+
| Branch / merge | one branch, squash per WU | branch + PR per **feature**, merge watcher |
|
|
78
|
+
| Report-back | RESULT block | `task_completed` event (+ `state:*` labels) via the loop's `GitHubBackend` |
|
|
79
|
+
|
|
80
|
+
The loop's `loop.py` is the reference for the orchestrator's poller (its dispatch/verify/retry/
|
|
81
|
+
gate-stop semantics decompose across the poller, PM dependency-recomputation, and the merge
|
|
82
|
+
watcher); the orchestrator does not import `loop.py`.
|
|
83
|
+
|
|
84
|
+
## 5. Status of the old Model-A sections
|
|
85
|
+
|
|
86
|
+
The prior revision's §A.2–§A.11 (feature-state `in_progress → plan_review` oscillation, gate
|
|
87
|
+
skeleton in the PM, `plan-next` as a PM skill, per-gate `plan_review`, the auto-arm conjunction in
|
|
88
|
+
the PM, PM `v1.7.0`, the `gates`/`task.gate` frontmatter fields) described **Model A and are not
|
|
89
|
+
implemented.** The `feature-frontmatter.schema.json` `gates` array is not used by the orchestrator
|
|
90
|
+
(gates are loop-internal). If a future need arises to surface gate state at the orchestrator level,
|
|
91
|
+
re-open this addendum deliberately.
|
|
92
|
+
|
|
93
|
+
## 6. Remaining (gated)
|
|
94
|
+
|
|
95
|
+
- `specfuse/methodology` extraction — once the gate-cycle contracts stop changing run-to-run
|
|
96
|
+
(charter §4; two contract fixes landed during the FEAT-2026-0003 dogfood — let them soak).
|
|
97
|
+
- Loop kit → `stable` in the orchestrator distribution manifest (same soak gate).
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Why the loop exists — lineage and positioning
|
|
2
|
+
|
|
3
|
+
## The Ralph lineage
|
|
4
|
+
|
|
5
|
+
The loop descends from the "Ralph" technique: in its purest form, a bash loop
|
|
6
|
+
that feeds a prompt to a coding agent repeatedly until the work is done, with a
|
|
7
|
+
fresh context each iteration and durable state kept in files (git history, a
|
|
8
|
+
progress file, a task list) rather than in the context window. Its insight is
|
|
9
|
+
that for large work, *stubbornness plus fresh context* beats a single clever
|
|
10
|
+
pass — the loop is the hero, not the model.
|
|
11
|
+
|
|
12
|
+
Ralph's known weakness is the thinness of its task list: a bare list of TODOs
|
|
13
|
+
gives an agent nothing to enforce patterns against, so it drifts. The ecosystem's
|
|
14
|
+
answer to "that's too coarse for serious work" has been to make the units of work
|
|
15
|
+
granular and self-contained enough that ephemeral workers can pick them up,
|
|
16
|
+
execute, and hand off — orchestration of many such workers ("Gas Town"-style).
|
|
17
|
+
|
|
18
|
+
The Specfuse Loop is that idea with the planning rigor added back in. It keeps
|
|
19
|
+
Ralph's fresh-context-per-iteration property but moves it to **work-unit
|
|
20
|
+
granularity**, and it replaces the thin task list with the **Plan + Work Unit**
|
|
21
|
+
pattern: crisp work units with hard "do not touch" boundaries, explicit
|
|
22
|
+
acceptance criteria, and machine-checkable verification gates. The up-front
|
|
23
|
+
planning investment is precisely what earns the right to let execution run
|
|
24
|
+
unattended — the richer the unit, the longer the loop can safely run before a
|
|
25
|
+
human checkpoint.
|
|
26
|
+
|
|
27
|
+
Two things distinguish it from vanilla Ralph:
|
|
28
|
+
|
|
29
|
+
- **Verification is the exit oracle, not the agent's say-so.** The driver re-runs
|
|
30
|
+
the unit's gates and they decide done — eliminating Ralph's classic
|
|
31
|
+
premature-"done" failure.
|
|
32
|
+
- **Gates are human checkpoints by design.** The loop runs unattended *within* a
|
|
33
|
+
gate and stops *at* it. Reflection, a cross-feature learnings rollup, and
|
|
34
|
+
drafting the next batch happen systematically as the gate's closing sequence,
|
|
35
|
+
not when someone remembers to ask.
|
|
36
|
+
|
|
37
|
+
## Where it sits in Specfuse
|
|
38
|
+
|
|
39
|
+
Specfuse is a methodology and an organization, not a single tool. Three
|
|
40
|
+
independently-adoptable projects live under it:
|
|
41
|
+
|
|
42
|
+
- **`specfuse/codegen`** turns OpenAPI / AsyncAPI / Arazzo specifications into
|
|
43
|
+
deterministic source code — the boilerplate no one should hand-write and no
|
|
44
|
+
agent should hallucinate.
|
|
45
|
+
- **`specfuse/loop`** (this project) executes the Plan + Work Unit pattern in a
|
|
46
|
+
single repository, with no specification required and no agent-coordination
|
|
47
|
+
overhead. The lightweight surface.
|
|
48
|
+
- **`specfuse/orchestrator`** coordinates specialized agents across many
|
|
49
|
+
component repositories from validated specifications — the heavyweight surface
|
|
50
|
+
for multi-repo, spec-first feature delivery.
|
|
51
|
+
|
|
52
|
+
The loop and the orchestrator are two execution surfaces of **one** methodology
|
|
53
|
+
(see [`methodology.md`](methodology.md)); they share the gate cycle, the
|
|
54
|
+
work-unit contract, the correlation-ID scheme, and the verification discipline.
|
|
55
|
+
The loop is the right home for work that lives in one repo or has no formal
|
|
56
|
+
spec; the orchestrator is the right home when the work genuinely spans repos and
|
|
57
|
+
is driven by specifications that `codegen` can turn into a stable foundation.
|
|
58
|
+
|
|
59
|
+
## What it is not
|
|
60
|
+
|
|
61
|
+
- Not a general-purpose AI coding platform. It does one shape of work:
|
|
62
|
+
plan-driven, gated, fresh-context execution in a single repo.
|
|
63
|
+
- Not a replacement for human judgment. Every gate is a human checkpoint; the
|
|
64
|
+
loop keeps agents *inside* a loop, it does not remove the loop.
|
|
65
|
+
- Not a hosted service. It runs on your machine, against your repo, under your
|
|
66
|
+
accounts.
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# Getting started
|
|
2
|
+
|
|
3
|
+
This walks you from an empty project to a feature delivered by the loop, then
|
|
4
|
+
shows what to do when a run halts. It assumes you've read the one-minute pitch in
|
|
5
|
+
the [README](../README.md); for the full contracts see
|
|
6
|
+
[`methodology.md`](methodology.md) and for the interactive operations see
|
|
7
|
+
[`skills.md`](skills.md).
|
|
8
|
+
|
|
9
|
+
The driver is pure-stdlib Python; it installs from PyPI, and the interactive
|
|
10
|
+
skills ship as a Claude Code plugin. You install the tooling once, then scaffold
|
|
11
|
+
each project you want to drive with one command.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. Install the tooling and scaffold your project
|
|
16
|
+
|
|
17
|
+
Install the umbrella package — it pulls the driver (`specfuse-loop>=0.3.0`) as a
|
|
18
|
+
dependency and puts the `specfuse`, `specfuse-loop`, and `specfuse-lint` commands
|
|
19
|
+
on your PATH. It's a command-line app, so **pipx** is the recommended installer
|
|
20
|
+
(isolated environment, no `--break-system-packages` on PEP 668 / externally-
|
|
21
|
+
managed Pythons):
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pipx install specfuse # recommended
|
|
25
|
+
# or, inside a virtualenv you control:
|
|
26
|
+
python3 -m pip install specfuse
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
> On Debian/Ubuntu/macOS-Homebrew Pythons a bare `pip install` into the system
|
|
30
|
+
> interpreter is blocked (`externally-managed-environment`). Use `pipx` (or a
|
|
31
|
+
> venv) — that's what puts `specfuse-loop` / `specfuse-lint` on PATH for the gate
|
|
32
|
+
> commands to find.
|
|
33
|
+
|
|
34
|
+
Enable the skills plugin in Claude Code (one-time):
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
/plugin marketplace add specfuse/specfuse
|
|
38
|
+
/plugin install specfuse@specfuse
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Then scaffold the repo you want to drive:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
specfuse init /path/to/your-project # add --dry-run to preview, writes nothing
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This writes `.specfuse/` (templates, rules, the durable docs, `verification.yml`,
|
|
48
|
+
and an empty `features/`) into your project and merge-safely wires `.claude/`
|
|
49
|
+
(`CLAUDE.md`, `settings.json` enabling the `specfuse@specfuse` plugin) plus a
|
|
50
|
+
`.gitignore` snippet. It refuses if `.specfuse/` already exists — use
|
|
51
|
+
`specfuse upgrade /path/to/your-project` to overlay a newer scaffold in place
|
|
52
|
+
without touching your authored files. (The skills come from the plugin, not from
|
|
53
|
+
files copied into your repo.)
|
|
54
|
+
|
|
55
|
+
> **Don't gitignore `.specfuse/`.** The loop's durable state lives there and must
|
|
56
|
+
> be committed for the loop to work.
|
|
57
|
+
|
|
58
|
+
> **Self-provisioning.** Every `specfuse-loop` run first version-syncs `.specfuse/`
|
|
59
|
+
> from the installed package (missing → scaffold, older → overlay, equal → no-op,
|
|
60
|
+
> never downgrades). So `pip install -U specfuse` followed by a run keeps the
|
|
61
|
+
> scaffold current — `specfuse upgrade` is the explicit equivalent. Disable with
|
|
62
|
+
> `--no-autosync` or `autosync: false` in `.specfuse/config`.
|
|
63
|
+
|
|
64
|
+
## 2. Match verification to your stack
|
|
65
|
+
|
|
66
|
+
`specfuse init` seeds `.specfuse/verification.yml`. Open it and make the `code`
|
|
67
|
+
gate set run *your* project's checks:
|
|
68
|
+
|
|
69
|
+
```yaml
|
|
70
|
+
code:
|
|
71
|
+
- name: tests
|
|
72
|
+
command: "pytest -q"
|
|
73
|
+
- name: coverage
|
|
74
|
+
command: "coverage report --fail-under=90"
|
|
75
|
+
- name: lint
|
|
76
|
+
command: "ruff check ."
|
|
77
|
+
- name: security
|
|
78
|
+
command: "bandit -r src -ll"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
These commands are the **exit oracle**: the driver re-runs them itself after every
|
|
82
|
+
work unit and *they* decide whether the unit is done — the agent's own self-report
|
|
83
|
+
is advisory only ([methodology §5](methodology.md)). Keep this set in lock-step
|
|
84
|
+
with your GitHub branch protection, or an agent can pass locally and still be
|
|
85
|
+
unmergeable.
|
|
86
|
+
|
|
87
|
+
If your repo already has CI worth deriving from, run **`/derive-verification`** in
|
|
88
|
+
Claude Code instead of editing by hand — it inspects your CI and tooling and
|
|
89
|
+
drafts the file for you.
|
|
90
|
+
|
|
91
|
+
## 3. Author your first feature
|
|
92
|
+
|
|
93
|
+
Two ways to create a feature folder under `.specfuse/features/`:
|
|
94
|
+
|
|
95
|
+
- **Interactively (recommended):** run **`/pick-feature`** to choose from your
|
|
96
|
+
roadmap, then **`/draft-feature`**. Draft-feature asks framing questions, then
|
|
97
|
+
proposes a gate skeleton and gate 1's work units, writing only on your accept.
|
|
98
|
+
- **By hand:** start from the bare templates in `.specfuse/templates/`
|
|
99
|
+
(`PLAN`, `GATE`, `WU`) and fill in a small first feature. (The
|
|
100
|
+
`specfuse/loop` source repo also carries a worked example,
|
|
101
|
+
`FEAT-2026-0001-health-endpoint`, if you want a complete reference to copy
|
|
102
|
+
from.)
|
|
103
|
+
|
|
104
|
+
A feature folder holds:
|
|
105
|
+
|
|
106
|
+
| File | Owns | Who writes it |
|
|
107
|
+
|------|------|---------------|
|
|
108
|
+
| `PLAN.md` | the *shape*: gate order, WU membership, dependency edges, feature status | you / `draft-feature` (gate 1); `plan-next` (later gates) |
|
|
109
|
+
| `GATE-NN.md` | one gate's status and definition of done | you / the planner |
|
|
110
|
+
| `WU-*.md` | a single work unit: frontmatter + the prompt a fresh session receives | you / `draft-feature` / `plan-next` |
|
|
111
|
+
|
|
112
|
+
Then create the branch named in `PLAN.md`'s frontmatter (`branch:`).
|
|
113
|
+
|
|
114
|
+
## 4. Validate before running
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
specfuse-lint .specfuse/features/FEAT-YYYY-NNNN-your-feature
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The linter checks structure: every WU has the five mandatory sections, the closing
|
|
121
|
+
sequence is present and well-formed, dependencies resolve, IDs are well-formed.
|
|
122
|
+
Fix anything it flags before dispatching — it's far cheaper than a failed
|
|
123
|
+
dispatch.
|
|
124
|
+
|
|
125
|
+
## 5. Dry-run, then run
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
specfuse-loop --dry-run # show the gate walked, in dep order, no dispatch
|
|
129
|
+
specfuse-loop # the real thing
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
With no `--feature` flag the driver picks the single `active` feature. For each
|
|
133
|
+
ready work unit it:
|
|
134
|
+
|
|
135
|
+
1. marks the WU `in_progress`,
|
|
136
|
+
2. dispatches a **fresh** `claude -p` session with that unit's model and prompt,
|
|
137
|
+
3. runs the unit's verification **itself** as the exit oracle,
|
|
138
|
+
4. on pass, makes **one squashed commit** carrying the `Feature: FEAT-.../TNN`
|
|
139
|
+
trailer.
|
|
140
|
+
|
|
141
|
+
A failed gate is discarded and re-dispatched to a fresh session carrying the
|
|
142
|
+
failure evidence, up to three attempts, then the unit is escalated to
|
|
143
|
+
`blocked_human` and the gate halts.
|
|
144
|
+
|
|
145
|
+
> **One driver per working tree.** The driver holds an exclusive lock on
|
|
146
|
+
> `.specfuse/.loop.lock`. A second driver on the same checkout exits immediately.
|
|
147
|
+
> To run two features at once, use separate `git worktree` checkouts. `--dry-run`
|
|
148
|
+
> is exempt.
|
|
149
|
+
|
|
150
|
+
## 6. The gate boundary — where you come back in
|
|
151
|
+
|
|
152
|
+
A gate ends with a **closing sequence** that runs automatically: it writes a
|
|
153
|
+
retrospective, promotes durable lessons to `LEARNINGS.md`, reconciles docs, and —
|
|
154
|
+
crucially — **drafts the next gate's work units** (as `draft`) so the next gate is
|
|
155
|
+
waiting for you to review.
|
|
156
|
+
|
|
157
|
+
Two things can happen at the boundary:
|
|
158
|
+
|
|
159
|
+
- **The gate auto-closes.** On a clean, on-plan gate the deterministic predicate
|
|
160
|
+
(`gate_eval.py`) closes it without a reflective session — but `plan-next` still
|
|
161
|
+
drafts the next gate, so the human review step still fires
|
|
162
|
+
([methodology §3](methodology.md)).
|
|
163
|
+
- **The driver halts with `awaiting_review`.** The next gate's WUs are in `draft`
|
|
164
|
+
and the driver will refuse to execute them until you arm them. **Arming is the
|
|
165
|
+
human checkpoint and is deliberately not automated.**
|
|
166
|
+
|
|
167
|
+
Run **`/arm-gate`**. It walks each drafted WU — accept / revise / reject — flips
|
|
168
|
+
the ones you accept to `pending`, marks the finished gate `passed`, and prints the
|
|
169
|
+
resume command. Read the `GATE-NN-REVIEW.md` the planner wrote first: it's
|
|
170
|
+
weighted toward where the planner was *least* certain.
|
|
171
|
+
|
|
172
|
+
Then re-run `specfuse-loop`. Repeat until the terminal gate is `done`.
|
|
173
|
+
|
|
174
|
+
## 7. Wrap up
|
|
175
|
+
|
|
176
|
+
When the terminal gate is `done`, run **`/wrap-feature`**: it pushes the feature
|
|
177
|
+
branch, opens a PR, optionally watches CI, and points at the next pick. Then
|
|
178
|
+
**`/roadmap-archive`** moves the finished feature's detail out of the active
|
|
179
|
+
roadmap.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Operating a running loop
|
|
184
|
+
|
|
185
|
+
The driver runs unattended within a gate, but real runs hit snags. The map:
|
|
186
|
+
|
|
187
|
+
| Symptom | What it means | Do this |
|
|
188
|
+
|---------|---------------|---------|
|
|
189
|
+
| Driver halts, a WU is `blocked_human` | A unit failed three attempts or hit an escalation trigger | Run **`/gate-status`** for a diagnosis (root cause, options, recommended action) |
|
|
190
|
+
| You fixed the blocker (creds, dep, spec) | The WU is still `blocked_human` | Run **`/unblock-wu`** to re-arm it (`blocked_human → pending`, attempts reset), then re-run |
|
|
191
|
+
| Driver exits "could not acquire lock" | Another driver owns this checkout | Find/stop the other driver, or use a separate `git worktree` |
|
|
192
|
+
| A gate is `awaiting_review` | Normal gate boundary | Run **`/arm-gate`** (§6) |
|
|
193
|
+
| The feature isn't worth finishing | — | Run **`/abandon-feature`** — flips every WU/gate/PLAN/roadmap surface cleanly |
|
|
194
|
+
| A WU "passed" but wrote no code | Hollow pass | Tighten the WU's acceptance criteria and verification; see [`authoring-work-units`](skills.md) |
|
|
195
|
+
|
|
196
|
+
**Where the durable state lives** (nothing important is in a context window):
|
|
197
|
+
|
|
198
|
+
- `PLAN.md` / `GATE-NN.md` / `WU-*.md` frontmatter — current status of everything.
|
|
199
|
+
- `events.jsonl` (per feature) — the event log; every dispatch emits an
|
|
200
|
+
`attempt_outcome`.
|
|
201
|
+
- `RETROSPECTIVE.md` — feature-local raw observations from each close.
|
|
202
|
+
- `LEARNINGS.md` (repo root of `.specfuse/`) — cross-feature durable lessons, read
|
|
203
|
+
at planning time so each plan is better than the last. Run
|
|
204
|
+
**`/learnings-suggest`** periodically to mine recurring failures into new
|
|
205
|
+
entries.
|
|
206
|
+
|
|
207
|
+
When in doubt after a halt, start with **`/gate-status`** — it reads all of the
|
|
208
|
+
above and tells you where you stand.
|
|
209
|
+
|
|
210
|
+
## Fixing a bug (not a feature)
|
|
211
|
+
|
|
212
|
+
Bugs don't go through the feature methodology. Run **`/fix-bug`** with the issue
|
|
213
|
+
number or report: it's 1 bug = 1 branch = 1 PR, test-first. It refuses and
|
|
214
|
+
proposes promoting to a feature if the work turns out large or risky.
|
|
215
|
+
|
|
216
|
+
## Next
|
|
217
|
+
|
|
218
|
+
- [`methodology.md`](methodology.md) — the full gate-cycle contract.
|
|
219
|
+
- [`skills.md`](skills.md) — every skill, by lifecycle phase.
|
|
220
|
+
- [`concepts/ralph-lineage.md`](concepts/ralph-lineage.md) — why the loop is
|
|
221
|
+
shaped the way it is.
|