specfuse-loop 0.3.2__tar.gz → 0.3.4__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.3.2/specfuse_loop.egg-info → specfuse_loop-0.3.4}/PKG-INFO +1 -1
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/pyproject.toml +1 -1
- specfuse_loop-0.3.4/specfuse/loop/data/VERSION +1 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse/loop/data/docs/getting-started.md +10 -1
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse/loop/loop.py +98 -17
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4/specfuse_loop.egg-info}/PKG-INFO +1 -1
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse_loop.egg-info/SOURCES.txt +1 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_arm_gate_edits_uncommitted.py +4 -4
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_loop_orchestration.py +2 -2
- specfuse_loop-0.3.4/tests/test_prepare_feature.py +159 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_untracked_feature_folder.py +3 -3
- specfuse_loop-0.3.2/specfuse/loop/data/VERSION +0 -1
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/LICENSE +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/NOTICE +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/README.md +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/setup.cfg +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse/loop/__init__.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse/loop/_miniyaml.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse/loop/adopt_feature.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse/loop/data/LEARNINGS.template.md +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse/loop/data/docs/concepts/architecture-addendum-gates-and-iterative-planning.md +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse/loop/data/docs/concepts/ralph-lineage.md +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse/loop/data/docs/methodology.md +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse/loop/data/docs/skills.md +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse/loop/data/gitignore.snippet +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse/loop/data/roadmap.template.md +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse/loop/data/rules/correlation-ids.md +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse/loop/data/rules/never-touch.md +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse/loop/data/rules/result-contract.md +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse/loop/data/rules/security-boundaries.md +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse/loop/data/templates/GATE.template.md +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse/loop/data/templates/PLAN.template.md +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse/loop/data/templates/WU.template.md +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse/loop/data/verification.yml.example +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse/loop/gate_eval.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse/loop/gh_backend.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse/loop/gh_features.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse/loop/lint_plan.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse/loop/scaffold.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse/loop/validate_event.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse_loop.egg-info/dependency_links.txt +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse_loop.egg-info/entry_points.txt +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse_loop.egg-info/requires.txt +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse_loop.egg-info/top_level.txt +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_adopt_feature.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_attempt_outcome_emission.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_autosync.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_autosync_consent.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_autosync_firstrun.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_autosync_plugin.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_backend.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_bookkeeping_commit_crash_run.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_bookkeeping_commit_hook_crash.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_closing_deliverable_guard.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_cost_tracking.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_deliverable_presence_gate.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_doctor.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_driver_integration.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_driver_lock.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_duration_tracking.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_empty_files_escalation.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_ensure_feature_branch.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_extra_gates.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_force_full_close.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_gate_eval.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_gate_eval_calibration.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_gate_eval_intermediate_wiring.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_gate_eval_terminal_wiring.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_gh_backend.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_gh_features.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_git_env_isolation.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_hashed_denylist.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_hashed_denylist_ci.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_init_integration.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_leak_findings_redaction.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_leak_scan.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_leak_scan_content.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_legacy_4wu_terminal_flips.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_lifecycle_integration.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_lint_bare_produces_path.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_lint_close_intermediate.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_lint_close_wu.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_lint_correlation_id.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_lint_correlation_id_close_intermediate.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_lint_oracle_env.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_lint_plan_errors.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_lint_plan_next_draft.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_lint_produces_driver_helper.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_lint_sections.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_lint_task_graph_yaml_selection.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_loop_auto_archive.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_loop_caveman_preamble.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_loop_close_intermediate.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_loop_defaults_by_type.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_loop_effort.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_loop_failure_note_cap.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_loop_files_changed_guard.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_loop_gate_budget.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_loop_model_alias.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_loop_post_pass_invariant.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_loop_reset_preserving_events.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_loop_smoke_runner.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_loop_unsandboxed.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_loop_zero_token_guard.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_migrate_legacy.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_miniyaml_equivalence.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_miniyaml_negative.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_planned_cost_lint.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_produces_field.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_result_block.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_roadmap_add_skill.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_roadmap_archive_skill.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_roadmap_row_parser.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_scaffold_data_in_sync.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_scaffold_docs.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_scaffold_init.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_scaffold_manifest.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_scaffold_resources.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_scaffold_seed_sanity.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_scaffold_upgrade.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_scaffold_wiring.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_squash_commit_hook_crash.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_template_closing_shapes.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_terminal_flip_ownership.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_terminal_flips.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_upgrade_integration.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_validate_event.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_verdict_coupling.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_verify_empty_gate_set.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_version_consistency.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_version_skew.py +0 -0
- {specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_wu_execution_metadata.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.3.
|
|
7
|
+
version = "0.3.4"
|
|
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"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.3.4
|
|
@@ -109,7 +109,16 @@ A feature folder holds:
|
|
|
109
109
|
| `GATE-NN.md` | one gate's status and definition of done | you / the planner |
|
|
110
110
|
| `WU-*.md` | a single work unit: frontmatter + the prompt a fresh session receives | you / `draft-feature` / `plan-next` |
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
`draft-feature` writes the folder but doesn't commit it or switch branches, so
|
|
113
|
+
the loop will refuse to start until the folder is on its branch and committed.
|
|
114
|
+
Let the driver do both for you:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
specfuse-loop --prepare # create the PLAN.md `branch:`, commit the folder, then run
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
(Or by hand: `git checkout -b <branch>` from `PLAN.md`'s frontmatter, then
|
|
121
|
+
`git add` + `git commit` the feature folder.)
|
|
113
122
|
|
|
114
123
|
## 4. Validate before running
|
|
115
124
|
|
|
@@ -62,7 +62,7 @@ SPECFUSE_DIR = Path(".specfuse")
|
|
|
62
62
|
REPO_ROOT = SPECFUSE_DIR.parent
|
|
63
63
|
FEATURES_DIR = SPECFUSE_DIR / "features"
|
|
64
64
|
VERIFICATION_PATH = SPECFUSE_DIR / "verification.yml"
|
|
65
|
-
DRIVER_VERSION = "0.3.
|
|
65
|
+
DRIVER_VERSION = "0.3.4"
|
|
66
66
|
# Oldest scaffold layout this driver can drive. init.sh stamps the scaffold's own
|
|
67
67
|
# version into `.specfuse/VERSION`; check_scaffold_version() fails loud at startup if
|
|
68
68
|
# the consumer's scaffold is older than this, pointing at `specfuse upgrade`. Bump
|
|
@@ -709,7 +709,40 @@ def untracked_feature_files(feature_dir: "Path") -> list[str]:
|
|
|
709
709
|
return [ln.strip() for ln in out.splitlines() if ln.strip()]
|
|
710
710
|
|
|
711
711
|
|
|
712
|
-
def
|
|
712
|
+
def _current_branch() -> str:
|
|
713
|
+
"""The checked-out branch name ('' when detached)."""
|
|
714
|
+
return git("branch", "--show-current")
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
def _branch_prep_hint(feature_dir: "Path", feat_fm: dict, feature_id: str) -> str:
|
|
718
|
+
"""Actionable next-step block for the two pre-flight guards.
|
|
719
|
+
|
|
720
|
+
`draft-feature` writes the feature folder but neither creates the feature's
|
|
721
|
+
branch nor commits — so the loop refuses to start. The fix is two steps when
|
|
722
|
+
you're not yet on the feature's branch (create it, then commit), and the
|
|
723
|
+
`--prepare` flag does both for you. Build a hint that names the real branch
|
|
724
|
+
and only includes the checkout line when it's actually needed.
|
|
725
|
+
"""
|
|
726
|
+
branch = feat_fm.get("branch") or f"feat/{feature_id}"
|
|
727
|
+
on_branch = _current_branch() == branch
|
|
728
|
+
lines = [
|
|
729
|
+
"",
|
|
730
|
+
"Easiest — let the loop create the branch and commit for you:",
|
|
731
|
+
" specfuse-loop --prepare # …then run",
|
|
732
|
+
" specfuse-loop --prepare-only # …then stop, so you can review first",
|
|
733
|
+
"",
|
|
734
|
+
"Or do it manually:",
|
|
735
|
+
]
|
|
736
|
+
if not on_branch:
|
|
737
|
+
lines.append(f" git checkout -b {branch}")
|
|
738
|
+
lines.append(f" git add {feature_dir}")
|
|
739
|
+
lines.append(f" git commit -m 'chore: scaffold feature {feature_id}'")
|
|
740
|
+
return "\n".join(lines)
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
def require_feature_folder_committed(
|
|
744
|
+
feature_dir: "Path", feat_fm: dict, feature_id: str,
|
|
745
|
+
) -> None:
|
|
713
746
|
"""Hard-stop if the dispatched feature folder has untracked files (#71).
|
|
714
747
|
|
|
715
748
|
The per-attempt `git reset --hard head_before` rolls tracked files back to
|
|
@@ -726,12 +759,11 @@ def require_feature_folder_committed(feature_dir: "Path") -> None:
|
|
|
726
759
|
listed = "\n ".join(sorted(untracked))
|
|
727
760
|
sys.exit(
|
|
728
761
|
f"loop.py: feature folder '{feature_dir}' has untracked files — "
|
|
729
|
-
f"commit them before running
|
|
730
|
-
f"`git reset --hard` will delete the folder mid-run (and
|
|
731
|
-
f"will crash reading a WU file that no longer exists).
|
|
732
|
-
f" {listed}\n"
|
|
733
|
-
|
|
734
|
-
f"'chore: commit feature folder'`, then re-run."
|
|
762
|
+
f"create the feature branch and commit them before running, or the "
|
|
763
|
+
f"per-attempt `git reset --hard` will delete the folder mid-run (and "
|
|
764
|
+
f"the driver will crash reading a WU file that no longer exists). "
|
|
765
|
+
f"Untracked:\n {listed}\n"
|
|
766
|
+
+ _branch_prep_hint(feature_dir, feat_fm, feature_id)
|
|
735
767
|
)
|
|
736
768
|
|
|
737
769
|
|
|
@@ -786,7 +818,9 @@ def feature_folder_tracked_modifications(feature_dir: "Path") -> list[str]:
|
|
|
786
818
|
return mods
|
|
787
819
|
|
|
788
820
|
|
|
789
|
-
def require_feature_folder_unmodified(
|
|
821
|
+
def require_feature_folder_unmodified(
|
|
822
|
+
feature_dir: "Path", feat_fm: dict, feature_id: str,
|
|
823
|
+
) -> None:
|
|
790
824
|
"""Hard-stop on uncommitted arm-gate / WU-revision edits before dispatch (#74).
|
|
791
825
|
|
|
792
826
|
arm-gate (and any manual WU-body revision at a gate boundary) writes
|
|
@@ -808,11 +842,30 @@ def require_feature_folder_unmodified(feature_dir: "Path") -> None:
|
|
|
808
842
|
f"`git reset --hard` will discard them (your armed WU statuses and "
|
|
809
843
|
f"any acceptance-criteria revisions would silently revert). "
|
|
810
844
|
f"Modified:\n {listed}\n"
|
|
811
|
-
|
|
812
|
-
f"'chore(arm): commit gate edits'`, then re-run."
|
|
845
|
+
+ _branch_prep_hint(feature_dir, feat_fm, feature_id)
|
|
813
846
|
)
|
|
814
847
|
|
|
815
848
|
|
|
849
|
+
def prepare_feature(feat_fm: dict, feature_dir: "Path", feature_id: str) -> None:
|
|
850
|
+
"""`--prepare`: put the feature folder on its declared branch and commit it.
|
|
851
|
+
|
|
852
|
+
`draft-feature` leaves the folder uncommitted on whatever branch you were on
|
|
853
|
+
(usually the default). This creates/checks out the feature's branch (from
|
|
854
|
+
PLAN.md `branch:`, carrying the untracked folder along) and commits the
|
|
855
|
+
folder, so the pre-flight guards pass and the run can start. Idempotent: a
|
|
856
|
+
no-op commit when there is nothing to commit.
|
|
857
|
+
"""
|
|
858
|
+
branch = feat_fm.get("branch")
|
|
859
|
+
ensure_feature_branch(feat_fm, feature_dir)
|
|
860
|
+
git("add", "--", str(feature_dir))
|
|
861
|
+
staged = git("status", "--porcelain", "--", str(feature_dir))
|
|
862
|
+
if staged:
|
|
863
|
+
git("commit", "-m", f"chore: scaffold feature {feature_id}")
|
|
864
|
+
print(f"Prepared: committed feature folder on branch '{branch}'.")
|
|
865
|
+
else:
|
|
866
|
+
print(f"Prepare: feature folder already committed on branch '{branch}'.")
|
|
867
|
+
|
|
868
|
+
|
|
816
869
|
def _checked_checkout(checkout_args: list[str], action: str) -> str:
|
|
817
870
|
"""Run a `git checkout ...` guarded: on non-zero exit raise FeatureBranchError
|
|
818
871
|
carrying git's stderr, instead of a bare CalledProcessError that hides it.
|
|
@@ -874,9 +927,15 @@ def ensure_feature_branch(feat_fm: dict, feature_dir: "Path | None" = None) -> N
|
|
|
874
927
|
).returncode == 0
|
|
875
928
|
if not is_ancestor:
|
|
876
929
|
raise FeatureBranchError(
|
|
877
|
-
f"branch '{branch}' exists and
|
|
878
|
-
f"
|
|
879
|
-
f"
|
|
930
|
+
f"branch '{branch}' already exists and has diverged from your "
|
|
931
|
+
f"current branch '{current}' (it carries commits HEAD does not). "
|
|
932
|
+
f"Refusing to silently check it out — that could build on a stale "
|
|
933
|
+
f"base or lose work. Pick one, then re-run:\n"
|
|
934
|
+
f" - resume that branch's work: git checkout {branch}\n"
|
|
935
|
+
f" - rebase it onto here: git checkout {branch} && "
|
|
936
|
+
f"git rebase {current}\n"
|
|
937
|
+
f" - discard it (work not needed): git branch -D {branch} "
|
|
938
|
+
f"(the loop recreates it from HEAD)"
|
|
880
939
|
)
|
|
881
940
|
_checked_checkout(["checkout", branch], f"checkout of existing branch '{branch}'")
|
|
882
941
|
print(f"Switched to feature branch '{branch}' (was on '{current}').")
|
|
@@ -2921,6 +2980,8 @@ def run(
|
|
|
2921
2980
|
feature_arg: str | None,
|
|
2922
2981
|
dry_run: bool,
|
|
2923
2982
|
force_full_close: str | None = None,
|
|
2983
|
+
prepare: bool = False,
|
|
2984
|
+
prepare_only: bool = False,
|
|
2924
2985
|
) -> int:
|
|
2925
2986
|
# Fail-fast on a malformed verification.yml BEFORE we touch any WU state.
|
|
2926
2987
|
# The per-gate `verify()` call lazy-loads the same file; if it's malformed,
|
|
@@ -2974,6 +3035,16 @@ def run(
|
|
|
2974
3035
|
)
|
|
2975
3036
|
return 1
|
|
2976
3037
|
require_git_ready()
|
|
3038
|
+
# --prepare / --prepare-only: create the feature branch + commit the
|
|
3039
|
+
# folder up front so the guards below pass (the do-it-for-me path
|
|
3040
|
+
# draft-feature can't take). --prepare-only stops after, so you can
|
|
3041
|
+
# review the commit before the loop dispatches anything.
|
|
3042
|
+
if prepare or prepare_only:
|
|
3043
|
+
prepare_feature(feat_fm, feature_dir, feature_id)
|
|
3044
|
+
if prepare_only:
|
|
3045
|
+
print("Prepared: feature is on its branch and committed. "
|
|
3046
|
+
"Re-run `specfuse-loop` to start the gate.")
|
|
3047
|
+
return 0
|
|
2977
3048
|
# Pre-flight guards — both run BEFORE ensure_feature_branch so the
|
|
2978
3049
|
# refusal happens before any branch mutation, and both protect against
|
|
2979
3050
|
# the per-attempt `git reset --hard` destroying uncommitted state:
|
|
@@ -2981,8 +3052,8 @@ def run(
|
|
|
2981
3052
|
# deleted by the reset (and crash the next frontmatter write).
|
|
2982
3053
|
# #74 — uncommitted arm-gate / WU-revision edits (armed statuses, AC
|
|
2983
3054
|
# revisions) would be silently discarded by the reset.
|
|
2984
|
-
require_feature_folder_committed(feature_dir)
|
|
2985
|
-
require_feature_folder_unmodified(feature_dir)
|
|
3055
|
+
require_feature_folder_committed(feature_dir, feat_fm, feature_id)
|
|
3056
|
+
require_feature_folder_unmodified(feature_dir, feat_fm, feature_id)
|
|
2986
3057
|
ensure_feature_branch(feat_fm, feature_dir)
|
|
2987
3058
|
|
|
2988
3059
|
try:
|
|
@@ -3952,11 +4023,21 @@ def main() -> int:
|
|
|
3952
4023
|
"path for the named feature. Must match the feature being processed.")
|
|
3953
4024
|
ap.add_argument("--no-autosync", action="store_true",
|
|
3954
4025
|
help="Skip auto-sync entirely (no scaffold create or overlay).")
|
|
4026
|
+
ap.add_argument("--prepare", action="store_true",
|
|
4027
|
+
help="Create the active feature's branch (from PLAN.md "
|
|
4028
|
+
"'branch:') and commit its folder, then run — the do-it-for-me "
|
|
4029
|
+
"path after /draft-feature, which leaves the folder "
|
|
4030
|
+
"uncommitted on the current branch.")
|
|
4031
|
+
ap.add_argument("--prepare-only", action="store_true",
|
|
4032
|
+
help="Like --prepare (create branch + commit the folder) but "
|
|
4033
|
+
"STOP afterwards without dispatching — review the commit, then "
|
|
4034
|
+
"re-run `specfuse-loop` to start.")
|
|
3955
4035
|
args = ap.parse_args()
|
|
3956
4036
|
if not FEATURES_DIR.exists():
|
|
3957
4037
|
sys.exit(f"No {FEATURES_DIR}. Run from your repo root.")
|
|
3958
4038
|
auto_sync(dry_run=args.dry_run, no_autosync=args.no_autosync)
|
|
3959
|
-
return run(args.feature, args.dry_run, force_full_close=args.force_full_close
|
|
4039
|
+
return run(args.feature, args.dry_run, force_full_close=args.force_full_close,
|
|
4040
|
+
prepare=args.prepare, prepare_only=args.prepare_only)
|
|
3960
4041
|
|
|
3961
4042
|
|
|
3962
4043
|
if __name__ == "__main__":
|
|
@@ -101,6 +101,7 @@ tests/test_migrate_legacy.py
|
|
|
101
101
|
tests/test_miniyaml_equivalence.py
|
|
102
102
|
tests/test_miniyaml_negative.py
|
|
103
103
|
tests/test_planned_cost_lint.py
|
|
104
|
+
tests/test_prepare_feature.py
|
|
104
105
|
tests/test_produces_field.py
|
|
105
106
|
tests/test_result_block.py
|
|
106
107
|
tests/test_roadmap_add_skill.py
|
|
@@ -81,7 +81,7 @@ class TestFeatureFolderTrackedModifications(unittest.TestCase):
|
|
|
81
81
|
mods = loop.feature_folder_tracked_modifications(rel)
|
|
82
82
|
self.assertTrue(any("WU-05.md" in m for m in mods))
|
|
83
83
|
with self.assertRaises(SystemExit):
|
|
84
|
-
loop.require_feature_folder_unmodified(rel)
|
|
84
|
+
loop.require_feature_folder_unmodified(rel, {"branch": "feat/t"}, "FEAT-2026-9999")
|
|
85
85
|
|
|
86
86
|
def test_clean_folder_passes(self):
|
|
87
87
|
with tempfile.TemporaryDirectory() as tmp:
|
|
@@ -91,7 +91,7 @@ class TestFeatureFolderTrackedModifications(unittest.TestCase):
|
|
|
91
91
|
os.chdir(root)
|
|
92
92
|
rel = fdir.relative_to(root)
|
|
93
93
|
self.assertEqual(loop.feature_folder_tracked_modifications(rel), [])
|
|
94
|
-
loop.require_feature_folder_unmodified(rel) # must not raise
|
|
94
|
+
loop.require_feature_folder_unmodified(rel, {"branch": "feat/t"}, "FEAT-2026-9999") # must not raise
|
|
95
95
|
|
|
96
96
|
def test_plan_md_flip_does_not_block(self):
|
|
97
97
|
"""pick-feature's PLAN.md status flip is expected, not an arm edit."""
|
|
@@ -106,7 +106,7 @@ class TestFeatureFolderTrackedModifications(unittest.TestCase):
|
|
|
106
106
|
os.chdir(root)
|
|
107
107
|
rel = fdir.relative_to(root)
|
|
108
108
|
self.assertEqual(loop.feature_folder_tracked_modifications(rel), [])
|
|
109
|
-
loop.require_feature_folder_unmodified(rel) # must not raise
|
|
109
|
+
loop.require_feature_folder_unmodified(rel, {"branch": "feat/t"}, "FEAT-2026-9999") # must not raise
|
|
110
110
|
|
|
111
111
|
def test_events_jsonl_change_does_not_block(self):
|
|
112
112
|
"""events.jsonl is driver-managed (preserved across reset), not an arm edit."""
|
|
@@ -118,7 +118,7 @@ class TestFeatureFolderTrackedModifications(unittest.TestCase):
|
|
|
118
118
|
os.chdir(root)
|
|
119
119
|
rel = fdir.relative_to(root)
|
|
120
120
|
self.assertEqual(loop.feature_folder_tracked_modifications(rel), [])
|
|
121
|
-
loop.require_feature_folder_unmodified(rel) # must not raise
|
|
121
|
+
loop.require_feature_folder_unmodified(rel, {"branch": "feat/t"}, "FEAT-2026-9999") # must not raise
|
|
122
122
|
|
|
123
123
|
|
|
124
124
|
class TestRunRefusesUncommittedArmEdits(unittest.TestCase):
|
|
@@ -706,7 +706,7 @@ class TestMainArgparse(unittest.TestCase):
|
|
|
706
706
|
with patch.object(sys, "argv", ["loop", "--feature", "my-feature"]):
|
|
707
707
|
result = loop.main()
|
|
708
708
|
self.assertEqual(result, 0)
|
|
709
|
-
mock_run.assert_called_once_with("my-feature", False, force_full_close=None)
|
|
709
|
+
mock_run.assert_called_once_with("my-feature", False, force_full_close=None, prepare=False, prepare_only=False)
|
|
710
710
|
|
|
711
711
|
def test_dry_run_flag_forwarded_to_run(self):
|
|
712
712
|
"""--dry-run is forwarded as dry_run=True to run()."""
|
|
@@ -718,7 +718,7 @@ class TestMainArgparse(unittest.TestCase):
|
|
|
718
718
|
with patch.object(loop, "run", return_value=0) as mock_run:
|
|
719
719
|
with patch.object(sys, "argv", ["loop", "--dry-run"]):
|
|
720
720
|
loop.main()
|
|
721
|
-
mock_run.assert_called_once_with(None, True, force_full_close=None)
|
|
721
|
+
mock_run.assert_called_once_with(None, True, force_full_close=None, prepare=False, prepare_only=False)
|
|
722
722
|
|
|
723
723
|
def test_multi_active_error_propagates_from_run(self):
|
|
724
724
|
"""main() with no args propagates the find_feature multi-active SystemExit."""
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright 2026 Specfuse contributors
|
|
3
|
+
# Licensed under the Apache License, Version 2.0. See LICENSE.
|
|
4
|
+
#
|
|
5
|
+
"""--prepare and the branch-aware guard hints.
|
|
6
|
+
|
|
7
|
+
`draft-feature` writes the feature folder but neither creates the feature's
|
|
8
|
+
branch nor commits, so the loop refuses to start (#71/#74). The refusal must
|
|
9
|
+
tell you to create the branch first (when you're on the default branch), and
|
|
10
|
+
`--prepare` must do branch-create + commit for you.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import io
|
|
16
|
+
import os
|
|
17
|
+
import subprocess
|
|
18
|
+
import tempfile
|
|
19
|
+
import unittest
|
|
20
|
+
from contextlib import redirect_stderr
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
from tests._loop_loader import load_loop
|
|
24
|
+
|
|
25
|
+
loop = load_loop()
|
|
26
|
+
|
|
27
|
+
_FEAT_ID = "FEAT-2026-9999"
|
|
28
|
+
_BRANCH = "feat/FEAT-2026-9999-thing"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _init_git(root: Path) -> None:
|
|
32
|
+
subprocess.run(["git", "init", "-q", "-b", "main", str(root)], check=True)
|
|
33
|
+
subprocess.run(["git", "-C", str(root), "config", "user.email", "t@example.com"],
|
|
34
|
+
check=True)
|
|
35
|
+
subprocess.run(["git", "-C", str(root), "config", "user.name", "Test"], check=True)
|
|
36
|
+
subprocess.run(["git", "-C", str(root), "config", "commit.gpgSign", "false"],
|
|
37
|
+
check=True)
|
|
38
|
+
(root / "README.md").write_text("# fixture\n")
|
|
39
|
+
specfuse = root / ".specfuse"
|
|
40
|
+
specfuse.mkdir()
|
|
41
|
+
(specfuse / "verification.yml").write_text(
|
|
42
|
+
"code:\n - name: noop\n command: \"true\"\n"
|
|
43
|
+
"doc:\n - name: noop\n command: \"true\"\n"
|
|
44
|
+
"plannext:\n - name: noop\n command: \"true\"\n"
|
|
45
|
+
)
|
|
46
|
+
subprocess.run(["git", "-C", str(root), "add", "."], check=True)
|
|
47
|
+
subprocess.run(["git", "-C", str(root), "commit", "-q", "-m", "init"], check=True)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _write_uncommitted_feature(root: Path) -> Path:
|
|
51
|
+
"""A freshly-drafted, UNcommitted feature folder on the current branch."""
|
|
52
|
+
fdir = root / ".specfuse" / "features" / f"{_FEAT_ID}-thing"
|
|
53
|
+
fdir.mkdir(parents=True)
|
|
54
|
+
(fdir / "PLAN.md").write_text(
|
|
55
|
+
f"---\nfeature_id: {_FEAT_ID}\ntitle: T\nbranch: {_BRANCH}\n"
|
|
56
|
+
f"roadmap_goal: t\nstatus: active\n---\n\n# Plan\n\n```yaml\n"
|
|
57
|
+
f"gates:\n - gate: 1\n file: GATE-01.md\n work_units:\n"
|
|
58
|
+
f" - id: {_FEAT_ID}/T01\n file: WU-01.md\n"
|
|
59
|
+
f" depends_on: []\n```\n"
|
|
60
|
+
)
|
|
61
|
+
(fdir / "GATE-01.md").write_text("---\ngate: 1\nstatus: open\n---\n\n# Gate 1\n")
|
|
62
|
+
(fdir / "WU-01.md").write_text(
|
|
63
|
+
f"---\nid: {_FEAT_ID}/T01\ntype: implementation\n"
|
|
64
|
+
"status: pending\nattempts: 0\n---\n\n# WU\n"
|
|
65
|
+
)
|
|
66
|
+
return fdir
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _feat_fm() -> dict:
|
|
70
|
+
return {"feature_id": _FEAT_ID, "branch": _BRANCH}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class TestPrepareFeature(unittest.TestCase):
|
|
74
|
+
|
|
75
|
+
def setUp(self):
|
|
76
|
+
self._cwd = os.getcwd()
|
|
77
|
+
|
|
78
|
+
def tearDown(self):
|
|
79
|
+
os.chdir(self._cwd)
|
|
80
|
+
|
|
81
|
+
def test_prepare_creates_branch_and_commits(self):
|
|
82
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
83
|
+
root = Path(tmp)
|
|
84
|
+
_init_git(root)
|
|
85
|
+
fdir = _write_uncommitted_feature(root)
|
|
86
|
+
os.chdir(root)
|
|
87
|
+
rel = fdir.relative_to(root)
|
|
88
|
+
loop.prepare_feature(_feat_fm(), rel, _FEAT_ID)
|
|
89
|
+
# on the feature branch now
|
|
90
|
+
self.assertEqual(loop._current_branch(), _BRANCH)
|
|
91
|
+
# folder is tracked + tree clean (committed)
|
|
92
|
+
self.assertEqual(loop.untracked_feature_files(rel), [])
|
|
93
|
+
self.assertEqual(loop.feature_folder_tracked_modifications(rel), [])
|
|
94
|
+
tracked = subprocess.run(["git", "ls-files", str(rel)],
|
|
95
|
+
capture_output=True, text=True).stdout
|
|
96
|
+
self.assertIn("PLAN.md", tracked)
|
|
97
|
+
|
|
98
|
+
def test_prepare_only_prepares_then_stops(self):
|
|
99
|
+
"""--prepare-only branches + commits but does NOT dispatch (rc=0)."""
|
|
100
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
101
|
+
root = Path(tmp)
|
|
102
|
+
_init_git(root)
|
|
103
|
+
_write_uncommitted_feature(root)
|
|
104
|
+
os.chdir(root)
|
|
105
|
+
|
|
106
|
+
called = {"dispatch": False}
|
|
107
|
+
|
|
108
|
+
def _no_dispatch(*a, **k):
|
|
109
|
+
called["dispatch"] = True
|
|
110
|
+
raise AssertionError("must not dispatch under --prepare-only")
|
|
111
|
+
|
|
112
|
+
orig = loop.dispatch
|
|
113
|
+
loop.dispatch = _no_dispatch
|
|
114
|
+
try:
|
|
115
|
+
rc = loop.run(None, dry_run=False, prepare_only=True)
|
|
116
|
+
finally:
|
|
117
|
+
loop.dispatch = orig
|
|
118
|
+
|
|
119
|
+
self.assertEqual(rc, 0)
|
|
120
|
+
self.assertFalse(called["dispatch"])
|
|
121
|
+
self.assertEqual(loop._current_branch(), _BRANCH)
|
|
122
|
+
# folder committed (tree clean)
|
|
123
|
+
rel = Path(".specfuse/features") / f"{_FEAT_ID}-thing"
|
|
124
|
+
self.assertEqual(loop.untracked_feature_files(rel), [])
|
|
125
|
+
|
|
126
|
+
def test_committed_guard_message_suggests_prepare_and_branch(self):
|
|
127
|
+
"""On the default branch, the untracked-folder refusal names --prepare
|
|
128
|
+
AND the `git checkout -b <branch>` step."""
|
|
129
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
130
|
+
root = Path(tmp)
|
|
131
|
+
_init_git(root)
|
|
132
|
+
fdir = _write_uncommitted_feature(root)
|
|
133
|
+
os.chdir(root)
|
|
134
|
+
rel = fdir.relative_to(root)
|
|
135
|
+
err = io.StringIO()
|
|
136
|
+
with self.assertRaises(SystemExit) as cm, redirect_stderr(err):
|
|
137
|
+
loop.require_feature_folder_committed(rel, _feat_fm(), _FEAT_ID)
|
|
138
|
+
msg = str(cm.exception)
|
|
139
|
+
self.assertIn("specfuse-loop --prepare", msg)
|
|
140
|
+
self.assertIn(f"git checkout -b {_BRANCH}", msg)
|
|
141
|
+
|
|
142
|
+
def test_guard_message_omits_checkout_when_on_feature_branch(self):
|
|
143
|
+
"""When already on the feature branch, no checkout line — just commit."""
|
|
144
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
145
|
+
root = Path(tmp)
|
|
146
|
+
_init_git(root)
|
|
147
|
+
fdir = _write_uncommitted_feature(root)
|
|
148
|
+
os.chdir(root)
|
|
149
|
+
subprocess.run(["git", "checkout", "-q", "-b", _BRANCH], check=True)
|
|
150
|
+
rel = fdir.relative_to(root)
|
|
151
|
+
with self.assertRaises(SystemExit) as cm:
|
|
152
|
+
loop.require_feature_folder_committed(rel, _feat_fm(), _FEAT_ID)
|
|
153
|
+
msg = str(cm.exception)
|
|
154
|
+
self.assertIn("specfuse-loop --prepare", msg)
|
|
155
|
+
self.assertNotIn("git checkout -b", msg)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
if __name__ == "__main__":
|
|
159
|
+
unittest.main()
|
|
@@ -78,7 +78,7 @@ class TestRequireFeatureFolderCommitted(unittest.TestCase):
|
|
|
78
78
|
files = loop.untracked_feature_files(rel)
|
|
79
79
|
self.assertTrue(files, "the freshly written folder must read as untracked")
|
|
80
80
|
with self.assertRaises(SystemExit):
|
|
81
|
-
loop.require_feature_folder_committed(rel)
|
|
81
|
+
loop.require_feature_folder_committed(rel, {"branch": "feat/t"}, "FEAT-2026-9999")
|
|
82
82
|
|
|
83
83
|
def test_committed_folder_passes(self):
|
|
84
84
|
"""A committed feature folder is a no-op (no exit)."""
|
|
@@ -92,7 +92,7 @@ class TestRequireFeatureFolderCommitted(unittest.TestCase):
|
|
|
92
92
|
os.chdir(root)
|
|
93
93
|
rel = fdir.relative_to(root)
|
|
94
94
|
self.assertEqual(loop.untracked_feature_files(rel), [])
|
|
95
|
-
loop.require_feature_folder_committed(rel) # must not raise
|
|
95
|
+
loop.require_feature_folder_committed(rel, {"branch": "feat/t"}, "FEAT-2026-9999") # must not raise
|
|
96
96
|
|
|
97
97
|
def test_gitignored_work_dir_does_not_block(self):
|
|
98
98
|
"""Driver-managed gitignored paths (work/) must not count as untracked."""
|
|
@@ -111,7 +111,7 @@ class TestRequireFeatureFolderCommitted(unittest.TestCase):
|
|
|
111
111
|
rel = fdir.relative_to(root)
|
|
112
112
|
self.assertEqual(loop.untracked_feature_files(rel), [],
|
|
113
113
|
"gitignored work/ must not be flagged")
|
|
114
|
-
loop.require_feature_folder_committed(rel) # must not raise
|
|
114
|
+
loop.require_feature_folder_committed(rel, {"branch": "feat/t"}, "FEAT-2026-9999") # must not raise
|
|
115
115
|
|
|
116
116
|
|
|
117
117
|
class TestWriteFrontmatterFieldHardening(unittest.TestCase):
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.3.2
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/specfuse/loop/data/docs/concepts/ralph-lineage.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{specfuse_loop-0.3.2 → specfuse_loop-0.3.4}/tests/test_lint_correlation_id_close_intermediate.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|