specfuse-loop 0.2.0__tar.gz → 0.3.0__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.0}/PKG-INFO +1 -1
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/pyproject.toml +8 -4
- specfuse_loop-0.3.0/specfuse/loop/data/LEARNINGS.template.md +69 -0
- specfuse_loop-0.3.0/specfuse/loop/data/VERSION +1 -0
- specfuse_loop-0.3.0/specfuse/loop/data/docs/concepts/architecture-addendum-gates-and-iterative-planning.md +97 -0
- specfuse_loop-0.3.0/specfuse/loop/data/docs/concepts/ralph-lineage.md +66 -0
- specfuse_loop-0.3.0/specfuse/loop/data/docs/getting-started.md +188 -0
- specfuse_loop-0.3.0/specfuse/loop/data/docs/methodology.md +322 -0
- specfuse_loop-0.3.0/specfuse/loop/data/docs/skills.md +130 -0
- specfuse_loop-0.3.0/specfuse/loop/data/gitignore.snippet +11 -0
- specfuse_loop-0.3.0/specfuse/loop/data/roadmap.template.md +36 -0
- specfuse_loop-0.3.0/specfuse/loop/data/rules/correlation-ids.md +197 -0
- specfuse_loop-0.3.0/specfuse/loop/data/rules/never-touch.md +81 -0
- specfuse_loop-0.3.0/specfuse/loop/data/rules/result-contract.md +113 -0
- specfuse_loop-0.3.0/specfuse/loop/data/rules/security-boundaries.md +102 -0
- specfuse_loop-0.3.0/specfuse/loop/data/templates/GATE.template.md +43 -0
- specfuse_loop-0.3.0/specfuse/loop/data/templates/PLAN.template.md +74 -0
- specfuse_loop-0.3.0/specfuse/loop/data/templates/WU.template.md +103 -0
- specfuse_loop-0.3.0/specfuse/loop/data/verification.yml.example +53 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/specfuse/loop/lint_plan.py +25 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/specfuse/loop/loop.py +477 -32
- specfuse_loop-0.3.0/specfuse/loop/scaffold.py +630 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0/specfuse_loop.egg-info}/PKG-INFO +1 -1
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/specfuse_loop.egg-info/SOURCES.txt +38 -0
- specfuse_loop-0.3.0/tests/test_arm_gate_edits_uncommitted.py +165 -0
- specfuse_loop-0.3.0/tests/test_autosync.py +200 -0
- specfuse_loop-0.3.0/tests/test_autosync_consent.py +294 -0
- specfuse_loop-0.3.0/tests/test_autosync_firstrun.py +146 -0
- specfuse_loop-0.3.0/tests/test_autosync_plugin.py +245 -0
- specfuse_loop-0.3.0/tests/test_bookkeeping_commit_crash_run.py +121 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_closing_deliverable_guard.py +55 -0
- specfuse_loop-0.3.0/tests/test_doctor.py +269 -0
- specfuse_loop-0.3.0/tests/test_init_integration.py +389 -0
- specfuse_loop-0.3.0/tests/test_leak_findings_redaction.py +109 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_leak_scan.py +9 -0
- specfuse_loop-0.3.0/tests/test_lint_bare_produces_path.py +153 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_loop_orchestration.py +3 -3
- specfuse_loop-0.3.0/tests/test_migrate_legacy.py +268 -0
- specfuse_loop-0.3.0/tests/test_scaffold_data_in_sync.py +104 -0
- specfuse_loop-0.3.0/tests/test_scaffold_docs.py +101 -0
- specfuse_loop-0.3.0/tests/test_scaffold_init.py +133 -0
- specfuse_loop-0.3.0/tests/test_scaffold_manifest.py +144 -0
- specfuse_loop-0.3.0/tests/test_scaffold_resources.py +71 -0
- specfuse_loop-0.3.0/tests/test_scaffold_upgrade.py +184 -0
- specfuse_loop-0.3.0/tests/test_scaffold_wiring.py +325 -0
- specfuse_loop-0.3.0/tests/test_untracked_feature_folder.py +153 -0
- specfuse_loop-0.3.0/tests/test_upgrade_integration.py +391 -0
- specfuse_loop-0.3.0/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.0}/LICENSE +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/NOTICE +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/README.md +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/setup.cfg +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/specfuse/loop/__init__.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/specfuse/loop/_miniyaml.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/specfuse/loop/adopt_feature.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/specfuse/loop/gate_eval.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/specfuse/loop/gh_backend.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/specfuse/loop/gh_features.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/specfuse/loop/validate_event.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/specfuse_loop.egg-info/dependency_links.txt +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/specfuse_loop.egg-info/entry_points.txt +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/specfuse_loop.egg-info/requires.txt +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/specfuse_loop.egg-info/top_level.txt +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_adopt_feature.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_attempt_outcome_emission.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_backend.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_bookkeeping_commit_hook_crash.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_cost_tracking.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_deliverable_presence_gate.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_driver_integration.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_driver_lock.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_duration_tracking.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_empty_files_escalation.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_ensure_feature_branch.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_extra_gates.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_force_full_close.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_gate_eval.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_gate_eval_calibration.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_gate_eval_intermediate_wiring.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_gate_eval_terminal_wiring.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_gh_backend.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_gh_features.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_git_env_isolation.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_hashed_denylist.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_hashed_denylist_ci.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_leak_scan_content.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_legacy_4wu_terminal_flips.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_lifecycle_integration.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_lint_close_intermediate.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_lint_close_wu.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_lint_correlation_id.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_lint_correlation_id_close_intermediate.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_lint_oracle_env.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_lint_plan_errors.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_lint_plan_next_draft.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_lint_produces_driver_helper.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_lint_sections.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_lint_task_graph_yaml_selection.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_loop_auto_archive.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_loop_caveman_preamble.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_loop_close_intermediate.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_loop_defaults_by_type.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_loop_effort.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_loop_failure_note_cap.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_loop_files_changed_guard.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_loop_gate_budget.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_loop_model_alias.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_loop_post_pass_invariant.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_loop_reset_preserving_events.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_loop_smoke_runner.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_loop_unsandboxed.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_loop_zero_token_guard.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_miniyaml_equivalence.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_miniyaml_negative.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_planned_cost_lint.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_produces_field.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_result_block.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_roadmap_add_skill.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_roadmap_archive_skill.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_roadmap_row_parser.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_squash_commit_hook_crash.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_template_closing_shapes.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_terminal_flip_ownership.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_terminal_flips.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_validate_event.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_verdict_coupling.py +0 -0
- {specfuse_loop-0.2.0 → specfuse_loop-0.3.0}/tests/test_verify_empty_gate_set.py +0 -0
|
@@ -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.0"
|
|
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.0
|
|
@@ -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,188 @@
|
|
|
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 loop is stdlib-only Python plus Claude Code. There is no install step in your
|
|
10
|
+
target repo — `init.sh` copies a self-contained scaffold in.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 1. Install the scaffold
|
|
15
|
+
|
|
16
|
+
From your checkout of `specfuse/loop`, point `init.sh` at the repo you want to
|
|
17
|
+
drive:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
./init.sh /path/to/your-project
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This writes `.specfuse/` (templates, rules, skills, the driver, and the durable
|
|
24
|
+
docs) into your project and wires `.claude/` so Claude Code discovers the skills.
|
|
25
|
+
It refuses if `.specfuse/` already exists — use `./init.sh --upgrade` to update an
|
|
26
|
+
existing install in place without touching your authored files.
|
|
27
|
+
|
|
28
|
+
> **Don't gitignore `.specfuse/`.** The loop's durable state lives there and must
|
|
29
|
+
> be committed for the loop to work. `init.sh` warns if it detects the directory
|
|
30
|
+
> is ignored.
|
|
31
|
+
|
|
32
|
+
## 2. Match verification to your stack
|
|
33
|
+
|
|
34
|
+
`init.sh` seeds `.specfuse/verification.yml`. Open it and make the `code` gate set
|
|
35
|
+
run *your* project's checks:
|
|
36
|
+
|
|
37
|
+
```yaml
|
|
38
|
+
code:
|
|
39
|
+
- name: tests
|
|
40
|
+
command: "pytest -q"
|
|
41
|
+
- name: coverage
|
|
42
|
+
command: "coverage report --fail-under=90"
|
|
43
|
+
- name: lint
|
|
44
|
+
command: "ruff check ."
|
|
45
|
+
- name: security
|
|
46
|
+
command: "bandit -r src -ll"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
These commands are the **exit oracle**: the driver re-runs them itself after every
|
|
50
|
+
work unit and *they* decide whether the unit is done — the agent's own self-report
|
|
51
|
+
is advisory only ([methodology §5](methodology.md)). Keep this set in lock-step
|
|
52
|
+
with your GitHub branch protection, or an agent can pass locally and still be
|
|
53
|
+
unmergeable.
|
|
54
|
+
|
|
55
|
+
If your repo already has CI worth deriving from, run **`/derive-verification`** in
|
|
56
|
+
Claude Code instead of editing by hand — it inspects your CI and tooling and
|
|
57
|
+
drafts the file for you.
|
|
58
|
+
|
|
59
|
+
## 3. Author your first feature
|
|
60
|
+
|
|
61
|
+
Two ways to create a feature folder under `.specfuse/features/`:
|
|
62
|
+
|
|
63
|
+
- **Interactively (recommended):** run **`/pick-feature`** to choose from your
|
|
64
|
+
roadmap, then **`/draft-feature`**. Draft-feature asks framing questions, then
|
|
65
|
+
proposes a gate skeleton and gate 1's work units, writing only on your accept.
|
|
66
|
+
- **By hand:** copy the worked example,
|
|
67
|
+
`.specfuse/features/FEAT-2026-0001-health-endpoint/`, and adapt it. It's a
|
|
68
|
+
deliberately small two-unit feature that exercises the whole loop. Or start from
|
|
69
|
+
the bare templates in `.specfuse/templates/`.
|
|
70
|
+
|
|
71
|
+
A feature folder holds:
|
|
72
|
+
|
|
73
|
+
| File | Owns | Who writes it |
|
|
74
|
+
|------|------|---------------|
|
|
75
|
+
| `PLAN.md` | the *shape*: gate order, WU membership, dependency edges, feature status | you / `draft-feature` (gate 1); `plan-next` (later gates) |
|
|
76
|
+
| `GATE-NN.md` | one gate's status and definition of done | you / the planner |
|
|
77
|
+
| `WU-*.md` | a single work unit: frontmatter + the prompt a fresh session receives | you / `draft-feature` / `plan-next` |
|
|
78
|
+
|
|
79
|
+
Then create the branch named in `PLAN.md`'s frontmatter (`branch:`).
|
|
80
|
+
|
|
81
|
+
## 4. Validate before running
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
python .specfuse/scripts/lint_plan.py .specfuse/features/FEAT-2026-0001-health-endpoint
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The linter checks structure: every WU has the five mandatory sections, the closing
|
|
88
|
+
sequence is present and well-formed, dependencies resolve, IDs are well-formed.
|
|
89
|
+
Fix anything it flags before dispatching — it's far cheaper than a failed
|
|
90
|
+
dispatch.
|
|
91
|
+
|
|
92
|
+
## 5. Dry-run, then run
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
python .specfuse/scripts/loop.py --dry-run # show the gate walked, in dep order, no dispatch
|
|
96
|
+
python .specfuse/scripts/loop.py # the real thing
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
With no `--feature` flag the driver picks the single `active` feature. For each
|
|
100
|
+
ready work unit it:
|
|
101
|
+
|
|
102
|
+
1. marks the WU `in_progress`,
|
|
103
|
+
2. dispatches a **fresh** `claude -p` session with that unit's model and prompt,
|
|
104
|
+
3. runs the unit's verification **itself** as the exit oracle,
|
|
105
|
+
4. on pass, makes **one squashed commit** carrying the `Feature: FEAT-.../TNN`
|
|
106
|
+
trailer.
|
|
107
|
+
|
|
108
|
+
A failed gate is discarded and re-dispatched to a fresh session carrying the
|
|
109
|
+
failure evidence, up to three attempts, then the unit is escalated to
|
|
110
|
+
`blocked_human` and the gate halts.
|
|
111
|
+
|
|
112
|
+
> **One driver per working tree.** The driver holds an exclusive lock on
|
|
113
|
+
> `.specfuse/.loop.lock`. A second driver on the same checkout exits immediately.
|
|
114
|
+
> To run two features at once, use separate `git worktree` checkouts. `--dry-run`
|
|
115
|
+
> is exempt.
|
|
116
|
+
|
|
117
|
+
## 6. The gate boundary — where you come back in
|
|
118
|
+
|
|
119
|
+
A gate ends with a **closing sequence** that runs automatically: it writes a
|
|
120
|
+
retrospective, promotes durable lessons to `LEARNINGS.md`, reconciles docs, and —
|
|
121
|
+
crucially — **drafts the next gate's work units** (as `draft`) so the next gate is
|
|
122
|
+
waiting for you to review.
|
|
123
|
+
|
|
124
|
+
Two things can happen at the boundary:
|
|
125
|
+
|
|
126
|
+
- **The gate auto-closes.** On a clean, on-plan gate the deterministic predicate
|
|
127
|
+
(`gate_eval.py`) closes it without a reflective session — but `plan-next` still
|
|
128
|
+
drafts the next gate, so the human review step still fires
|
|
129
|
+
([methodology §3](methodology.md)).
|
|
130
|
+
- **The driver halts with `awaiting_review`.** The next gate's WUs are in `draft`
|
|
131
|
+
and the driver will refuse to execute them until you arm them. **Arming is the
|
|
132
|
+
human checkpoint and is deliberately not automated.**
|
|
133
|
+
|
|
134
|
+
Run **`/arm-gate`**. It walks each drafted WU — accept / revise / reject — flips
|
|
135
|
+
the ones you accept to `pending`, marks the finished gate `passed`, and prints the
|
|
136
|
+
resume command. Read the `GATE-NN-REVIEW.md` the planner wrote first: it's
|
|
137
|
+
weighted toward where the planner was *least* certain.
|
|
138
|
+
|
|
139
|
+
Then re-run `loop.py`. Repeat until the terminal gate is `done`.
|
|
140
|
+
|
|
141
|
+
## 7. Wrap up
|
|
142
|
+
|
|
143
|
+
When the terminal gate is `done`, run **`/wrap-feature`**: it pushes the feature
|
|
144
|
+
branch, opens a PR, optionally watches CI, and points at the next pick. Then
|
|
145
|
+
**`/roadmap-archive`** moves the finished feature's detail out of the active
|
|
146
|
+
roadmap.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Operating a running loop
|
|
151
|
+
|
|
152
|
+
The driver runs unattended within a gate, but real runs hit snags. The map:
|
|
153
|
+
|
|
154
|
+
| Symptom | What it means | Do this |
|
|
155
|
+
|---------|---------------|---------|
|
|
156
|
+
| 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) |
|
|
157
|
+
| 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 |
|
|
158
|
+
| Driver exits "could not acquire lock" | Another driver owns this checkout | Find/stop the other driver, or use a separate `git worktree` |
|
|
159
|
+
| A gate is `awaiting_review` | Normal gate boundary | Run **`/arm-gate`** (§6) |
|
|
160
|
+
| The feature isn't worth finishing | — | Run **`/abandon-feature`** — flips every WU/gate/PLAN/roadmap surface cleanly |
|
|
161
|
+
| A WU "passed" but wrote no code | Hollow pass | Tighten the WU's acceptance criteria and verification; see [`authoring-work-units`](skills.md) |
|
|
162
|
+
|
|
163
|
+
**Where the durable state lives** (nothing important is in a context window):
|
|
164
|
+
|
|
165
|
+
- `PLAN.md` / `GATE-NN.md` / `WU-*.md` frontmatter — current status of everything.
|
|
166
|
+
- `events.jsonl` (per feature) — the event log; every dispatch emits an
|
|
167
|
+
`attempt_outcome`.
|
|
168
|
+
- `RETROSPECTIVE.md` — feature-local raw observations from each close.
|
|
169
|
+
- `LEARNINGS.md` (repo root of `.specfuse/`) — cross-feature durable lessons, read
|
|
170
|
+
at planning time so each plan is better than the last. Run
|
|
171
|
+
**`/learnings-suggest`** periodically to mine recurring failures into new
|
|
172
|
+
entries.
|
|
173
|
+
|
|
174
|
+
When in doubt after a halt, start with **`/gate-status`** — it reads all of the
|
|
175
|
+
above and tells you where you stand.
|
|
176
|
+
|
|
177
|
+
## Fixing a bug (not a feature)
|
|
178
|
+
|
|
179
|
+
Bugs don't go through the feature methodology. Run **`/fix-bug`** with the issue
|
|
180
|
+
number or report: it's 1 bug = 1 branch = 1 PR, test-first. It refuses and
|
|
181
|
+
proposes promoting to a feature if the work turns out large or risky.
|
|
182
|
+
|
|
183
|
+
## Next
|
|
184
|
+
|
|
185
|
+
- [`methodology.md`](methodology.md) — the full gate-cycle contract.
|
|
186
|
+
- [`skills.md`](skills.md) — every skill, by lifecycle phase.
|
|
187
|
+
- [`concepts/ralph-lineage.md`](concepts/ralph-lineage.md) — why the loop is
|
|
188
|
+
shaped the way it is.
|