specfuse-loop 0.3.10__tar.gz → 0.3.12__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.10/specfuse_loop.egg-info → specfuse_loop-0.3.12}/PKG-INFO +1 -1
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/pyproject.toml +1 -1
- specfuse_loop-0.3.12/specfuse/loop/data/VERSION +1 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/data/docs/concepts/architecture-addendum-gates-and-iterative-planning.md +2 -2
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/data/docs/concepts/ralph-lineage.md +1 -1
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/data/templates/WU.template.md +1 -1
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/loop.py +100 -3
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12/specfuse_loop.egg-info}/PKG-INFO +1 -1
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse_loop.egg-info/SOURCES.txt +2 -0
- specfuse_loop-0.3.12/tests/test_prepare_scaffold_sync.py +172 -0
- specfuse_loop-0.3.12/tests/test_scaffold_doc_hygiene.py +79 -0
- specfuse_loop-0.3.10/specfuse/loop/data/VERSION +0 -1
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/LICENSE +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/NOTICE +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/README.md +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/setup.cfg +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/__init__.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/_miniyaml.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/adopt_feature.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/data/LEARNINGS.template.md +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/data/docs/getting-started.md +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/data/docs/methodology.md +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/data/docs/skills.md +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/data/gitignore.snippet +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/data/roadmap.template.md +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/data/rules/correlation-ids.md +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/data/rules/never-touch.md +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/data/rules/result-contract.md +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/data/rules/security-boundaries.md +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/data/rules/verification-discipline.md +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/data/schemas/event.schema.json +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/data/schemas/events/initiative_created.schema.json +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/data/schemas/events/spec_issue_resolved.schema.json +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/data/schemas/events/spec_issue_routed.schema.json +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/data/schemas/events/spec_validated.schema.json +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/data/templates/GATE.template.md +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/data/templates/PLAN.template.md +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/data/verification.yml.example +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/gate_eval.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/gh_backend.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/gh_features.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/lint_plan.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/scaffold.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/validate_event.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse_loop.egg-info/dependency_links.txt +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse_loop.egg-info/entry_points.txt +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse_loop.egg-info/requires.txt +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse_loop.egg-info/top_level.txt +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_adopt_feature.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_arm_gate_edits_uncommitted.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_attempt_outcome_emission.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_autosync.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_autosync_consent.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_autosync_firstrun.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_autosync_plugin.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_backend.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_bookkeeping_commit_crash_run.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_bookkeeping_commit_hook_crash.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_closing_deliverable_guard.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_cost_tracking.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_degraded_oracle.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_deliverable_presence_gate.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_doctor.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_driver_integration.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_driver_lock.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_duration_tracking.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_empty_files_escalation.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_ensure_feature_branch.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_events_redaction.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_extra_gates.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_force_full_close.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_gate_eval.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_gate_eval_calibration.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_gate_eval_intermediate_wiring.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_gate_eval_terminal_wiring.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_gh_backend.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_gh_features.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_git_env_isolation.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_hashed_denylist.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_hashed_denylist_ci.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_init_integration.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_leak_findings_redaction.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_leak_scan.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_leak_scan_content.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_learnings_query.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_legacy_4wu_terminal_flips.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_lifecycle_integration.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_lint_bare_produces_path.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_lint_close_intermediate.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_lint_close_wu.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_lint_correlation_id.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_lint_correlation_id_close_intermediate.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_lint_oracle_env.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_lint_plan_errors.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_lint_plan_next_draft.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_lint_plan_relative_import.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_lint_produces_driver_helper.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_lint_sections.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_lint_task_graph_yaml_selection.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_loop_auto_archive.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_loop_caveman_preamble.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_loop_close_intermediate.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_loop_defaults_by_type.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_loop_effort.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_loop_failure_note_cap.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_loop_files_changed_guard.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_loop_gate_budget.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_loop_model_alias.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_loop_orchestration.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_loop_post_pass_invariant.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_loop_reset_preserving_events.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_loop_smoke_runner.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_loop_unsandboxed.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_loop_zero_token_guard.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_migrate_legacy.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_miniyaml_equivalence.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_miniyaml_negative.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_planned_cost_lint.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_prepare_feature.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_produces_field.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_result_block.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_roadmap_add_skill.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_roadmap_archive_skill.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_roadmap_row_parser.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_scaffold_data_in_sync.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_scaffold_docs.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_scaffold_init.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_scaffold_manifest.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_scaffold_resources.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_scaffold_seed_sanity.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_scaffold_upgrade.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_scaffold_wiring.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_skills_vendored_in_sync.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_squash_commit_hook_crash.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_template_closing_shapes.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_terminal_flip_ownership.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_terminal_flips.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_untracked_feature_folder.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_upgrade_integration.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_upgrade_merge_gate.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_validate_event.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_verdict_coupling.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_verify_empty_gate_set.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_version_consistency.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/tests/test_version_skew.py +0 -0
- {specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/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.12"
|
|
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.12
|
|
@@ -24,7 +24,7 @@ implementation feature == a loop feature**: the receiving component's loop decom
|
|
|
24
24
|
So gates are **internal to the loop**, not orchestrator state. The orchestrator owns
|
|
25
25
|
`initiative → feature` decomposition, cross-repo dependency ordering, and the spec/generated
|
|
26
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).
|
|
27
|
+
per-gate `plan_review`. The loop owns all of that, per [`methodology.md`](../methodology.md).
|
|
28
28
|
|
|
29
29
|
**Why Model B (summary).** The loop is single-repo + edit-and-commit; codegen freezes the
|
|
30
30
|
cross-repo interface (generated `emit-*`/`on-*` contracts are immutable `_generated/`), so
|
|
@@ -60,7 +60,7 @@ gates were placed in the loop instead.
|
|
|
60
60
|
|
|
61
61
|
## 3. What the loop owns (the gate layer)
|
|
62
62
|
|
|
63
|
-
Per [`methodology.md`](methodology.md): the gate cycle (plan → execute → close → review&arm), the
|
|
63
|
+
Per [`methodology.md`](../methodology.md): the gate cycle (plan → execute → close → review&arm), the
|
|
64
64
|
four-type closing sequence (`retrospective → lessons → docs → plan-next`), `plan-next` drafting
|
|
65
65
|
the next gate (never arming it), `LEARNINGS.md`, and per-gate autonomy. These are **loop-internal**
|
|
66
66
|
to each dispatched feature; the orchestrator sees only the feature's overall state (via issue
|
{specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/data/docs/concepts/ralph-lineage.md
RENAMED
|
@@ -50,7 +50,7 @@ independently-adoptable projects live under it:
|
|
|
50
50
|
for multi-repo, spec-first feature delivery.
|
|
51
51
|
|
|
52
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
|
|
53
|
+
(see [`methodology.md`](../methodology.md)); they share the gate cycle, the
|
|
54
54
|
work-unit contract, the correlation-ID scheme, and the verification discipline.
|
|
55
55
|
The loop is the right home for work that lives in one repo or has no formal
|
|
56
56
|
spec; the orchestrator is the right home when the work genuinely spans repos and
|
|
@@ -38,7 +38,7 @@ AUTHOR-SET FIELDS — fill or override these at draft/arm time:
|
|
|
38
38
|
- `produces` — OPTIONAL. File path(s) this WU must produce; machine-enforced by the driver's
|
|
39
39
|
presence gate (FEAT-2026-0022). Lint WARN when absent on `implementation` WUs.
|
|
40
40
|
|
|
41
|
-
DRIVER-OWNED FIELDS
|
|
41
|
+
DRIVER-OWNED FIELDS - the driver writes these at outcome time; authors leave them absent:
|
|
42
42
|
<!-- driver-owned: attempts, cost_usd, input_tokens, output_tokens, duration_seconds,
|
|
43
43
|
cumulative_cost_usd, cumulative_duration_seconds, cumulative_input_tokens,
|
|
44
44
|
cumulative_output_tokens, re_arm_count, re_arm_history -->
|
|
@@ -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.12"
|
|
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
|
|
@@ -766,6 +766,84 @@ def _current_branch() -> str:
|
|
|
766
766
|
return git("branch", "--show-current")
|
|
767
767
|
|
|
768
768
|
|
|
769
|
+
def _default_branch() -> "str | None":
|
|
770
|
+
"""The repo's default branch, or None if undeterminable.
|
|
771
|
+
|
|
772
|
+
Prefers origin's HEAD symref (`origin/main` -> `main`); falls back to a
|
|
773
|
+
local `main`/`master` when there is no remote. None when neither resolves —
|
|
774
|
+
callers then treat the current branch as non-default (auto_sync commits its
|
|
775
|
+
scaffold sync rather than leaving it dangling).
|
|
776
|
+
"""
|
|
777
|
+
ref = subprocess.run(
|
|
778
|
+
["git", "symbolic-ref", "--short", "refs/remotes/origin/HEAD"],
|
|
779
|
+
capture_output=True, text=True,
|
|
780
|
+
)
|
|
781
|
+
if ref.returncode == 0 and ref.stdout.strip():
|
|
782
|
+
name = ref.stdout.strip()
|
|
783
|
+
return name[len("origin/"):] if name.startswith("origin/") else name
|
|
784
|
+
for cand in ("main", "master"):
|
|
785
|
+
if subprocess.run(
|
|
786
|
+
["git", "rev-parse", "--verify", "--quiet", cand],
|
|
787
|
+
capture_output=True, text=True,
|
|
788
|
+
).returncode == 0:
|
|
789
|
+
return cand
|
|
790
|
+
return None
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
# Paths the scaffold overlay owns — auto_sync writes these on upgrade. They are
|
|
794
|
+
# the driver's, not the user's edits: they may carry onto a feature branch and
|
|
795
|
+
# are folded into the --prepare scaffold commit. Mirrors scaffold.py's versioned
|
|
796
|
+
# overlay set (templates/, rules/, docs/, schemas/) plus the meta files.
|
|
797
|
+
_SCAFFOLD_MANAGED_PREFIXES: tuple[str, ...] = (
|
|
798
|
+
".specfuse/templates/", ".specfuse/rules/",
|
|
799
|
+
".specfuse/docs/", ".specfuse/schemas/",
|
|
800
|
+
)
|
|
801
|
+
_SCAFFOLD_MANAGED_EXACT: frozenset[str] = frozenset({
|
|
802
|
+
".specfuse/VERSION", ".specfuse/.scaffold-manifest",
|
|
803
|
+
".specfuse/verification.yml.example",
|
|
804
|
+
})
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
def _is_scaffold_managed(path: str) -> bool:
|
|
808
|
+
return (path in _SCAFFOLD_MANAGED_EXACT
|
|
809
|
+
or any(path.startswith(p) for p in _SCAFFOLD_MANAGED_PREFIXES))
|
|
810
|
+
|
|
811
|
+
|
|
812
|
+
def _scaffold_managed_dirty() -> set[str]:
|
|
813
|
+
"""Tracked-dirty paths the scaffold overlay owns (subset of the dirty set)."""
|
|
814
|
+
return {p for p in _tracked_dirty_paths() if _is_scaffold_managed(p)}
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
def _persist_scaffold_sync(installed: str) -> None:
|
|
818
|
+
"""After auto_sync overlays a newer scaffold, keep the tree clean for the
|
|
819
|
+
rest of the run. On a non-default branch, commit the scaffold-managed
|
|
820
|
+
changes (`chore(loop): sync scaffold to X.Y.Z`). On the DEFAULT branch, leave
|
|
821
|
+
them and print guidance — committing scaffold churn onto the default branch
|
|
822
|
+
is undesirable; --prepare (or the next ensure_feature_branch) carries them
|
|
823
|
+
onto the feature branch and folds them into its scaffold commit instead.
|
|
824
|
+
"""
|
|
825
|
+
dirty = _scaffold_managed_dirty()
|
|
826
|
+
if not dirty:
|
|
827
|
+
return
|
|
828
|
+
branch = _current_branch()
|
|
829
|
+
on_default = (not branch) or branch == _default_branch()
|
|
830
|
+
if on_default:
|
|
831
|
+
print(
|
|
832
|
+
f"auto_sync: scaffold upgraded to {installed}; leaving "
|
|
833
|
+
f"{len(dirty)} scaffold file(s) uncommitted on default branch "
|
|
834
|
+
f"'{branch or '(detached)'}'. --prepare will carry them onto the "
|
|
835
|
+
f"feature branch, or commit them yourself.",
|
|
836
|
+
file=sys.stderr,
|
|
837
|
+
)
|
|
838
|
+
return
|
|
839
|
+
commit_bookkeeping(sorted(dirty), f"chore(loop): sync scaffold to {installed}")
|
|
840
|
+
print(
|
|
841
|
+
f"auto_sync: scaffold upgraded to {installed}; committed "
|
|
842
|
+
f"{len(dirty)} scaffold file(s) on '{branch}'.",
|
|
843
|
+
file=sys.stderr,
|
|
844
|
+
)
|
|
845
|
+
|
|
846
|
+
|
|
769
847
|
def _branch_prep_hint(feature_dir: "Path", feat_fm: dict, feature_id: str) -> str:
|
|
770
848
|
"""Actionable next-step block for the two pre-flight guards.
|
|
771
849
|
|
|
@@ -920,6 +998,11 @@ def prepare_feature(feat_fm: dict, feature_dir: "Path", feature_id: str) -> None
|
|
|
920
998
|
for rel in (".specfuse/roadmap.md", ".specfuse/roadmap-archive.md"):
|
|
921
999
|
if (repo_root / rel).exists():
|
|
922
1000
|
add_paths.append(str(repo_root / rel))
|
|
1001
|
+
# Fold auto_sync's scaffold overlay into this commit too. On the default
|
|
1002
|
+
# branch auto_sync leaves those uncommitted (see _persist_scaffold_sync);
|
|
1003
|
+
# committing them here lands the upgrade on the feature branch, clean.
|
|
1004
|
+
for rel in sorted(_scaffold_managed_dirty()):
|
|
1005
|
+
add_paths.append(str(repo_root / rel))
|
|
923
1006
|
git("add", "--", *add_paths)
|
|
924
1007
|
staged = git("status", "--porcelain", "--", *add_paths)
|
|
925
1008
|
if staged:
|
|
@@ -1006,7 +1089,11 @@ def ensure_feature_branch(feat_fm: dict, feature_dir: "Path | None" = None) -> N
|
|
|
1006
1089
|
# Create-from-HEAD carries the working tree onto the new branch. Only
|
|
1007
1090
|
# the expected /pick-feature flips may ride along; anything else stops.
|
|
1008
1091
|
dirty = _tracked_dirty_paths()
|
|
1009
|
-
|
|
1092
|
+
# Scaffold-overlay files (auto_sync's own upgrade writes) ride along too:
|
|
1093
|
+
# they are driver-owned, and prepare_feature folds them into the scaffold
|
|
1094
|
+
# commit. Only genuinely-unrelated edits remain "unexpected" (#prepare).
|
|
1095
|
+
allowed = _expected_flip_paths(feature_dir) | _scaffold_managed_dirty()
|
|
1096
|
+
unexpected = dirty - allowed
|
|
1010
1097
|
if unexpected:
|
|
1011
1098
|
raise FeatureBranchError(
|
|
1012
1099
|
"working tree has uncommitted changes to unexpected paths: "
|
|
@@ -3112,7 +3199,14 @@ def run(
|
|
|
3112
3199
|
# draft-feature can't take). --prepare-only stops after, so you can
|
|
3113
3200
|
# review the commit before the loop dispatches anything.
|
|
3114
3201
|
if prepare or prepare_only:
|
|
3115
|
-
|
|
3202
|
+
# Genuinely-unrelated dirty paths (not the /pick-feature flips, not
|
|
3203
|
+
# auto_sync's scaffold overlay) still stop --prepare — but with a
|
|
3204
|
+
# clean, actionable message and a non-zero exit, not a traceback.
|
|
3205
|
+
try:
|
|
3206
|
+
prepare_feature(feat_fm, feature_dir, feature_id)
|
|
3207
|
+
except FeatureBranchError as exc:
|
|
3208
|
+
print(f"--prepare cannot proceed:\n{exc}", file=sys.stderr)
|
|
3209
|
+
return 1
|
|
3116
3210
|
if prepare_only:
|
|
3117
3211
|
print("Prepared: feature is on its branch and committed. "
|
|
3118
3212
|
"Re-run `specfuse-loop` to start the gate.")
|
|
@@ -3998,6 +4092,7 @@ def auto_sync(
|
|
|
3998
4092
|
f"WARNING: auto_sync: plugin config drift corrected: {', '.join(changes)}",
|
|
3999
4093
|
file=sys.stderr,
|
|
4000
4094
|
)
|
|
4095
|
+
_persist_scaffold_sync(installed)
|
|
4001
4096
|
return
|
|
4002
4097
|
|
|
4003
4098
|
# Older with modified files.
|
|
@@ -4056,6 +4151,7 @@ def auto_sync(
|
|
|
4056
4151
|
f"WARNING: auto_sync: plugin config drift corrected: {', '.join(changes)}",
|
|
4057
4152
|
file=sys.stderr,
|
|
4058
4153
|
)
|
|
4154
|
+
_persist_scaffold_sync(installed)
|
|
4059
4155
|
else:
|
|
4060
4156
|
# Non-interactive (CI / claude -p): skip modified files + warn; never block.
|
|
4061
4157
|
print(
|
|
@@ -4092,6 +4188,7 @@ def auto_sync(
|
|
|
4092
4188
|
f"WARNING: auto_sync: plugin config drift corrected: {', '.join(changes)}",
|
|
4093
4189
|
file=sys.stderr,
|
|
4094
4190
|
)
|
|
4191
|
+
_persist_scaffold_sync(installed)
|
|
4095
4192
|
|
|
4096
4193
|
|
|
4097
4194
|
def main() -> int:
|
|
@@ -112,12 +112,14 @@ tests/test_miniyaml_equivalence.py
|
|
|
112
112
|
tests/test_miniyaml_negative.py
|
|
113
113
|
tests/test_planned_cost_lint.py
|
|
114
114
|
tests/test_prepare_feature.py
|
|
115
|
+
tests/test_prepare_scaffold_sync.py
|
|
115
116
|
tests/test_produces_field.py
|
|
116
117
|
tests/test_result_block.py
|
|
117
118
|
tests/test_roadmap_add_skill.py
|
|
118
119
|
tests/test_roadmap_archive_skill.py
|
|
119
120
|
tests/test_roadmap_row_parser.py
|
|
120
121
|
tests/test_scaffold_data_in_sync.py
|
|
122
|
+
tests/test_scaffold_doc_hygiene.py
|
|
121
123
|
tests/test_scaffold_docs.py
|
|
122
124
|
tests/test_scaffold_init.py
|
|
123
125
|
tests/test_scaffold_manifest.py
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright 2026 Specfuse contributors
|
|
3
|
+
# Licensed under the Apache License, Version 2.0. See LICENSE.
|
|
4
|
+
#
|
|
5
|
+
"""auto_sync's scaffold upgrade must not poison the same run's --prepare.
|
|
6
|
+
|
|
7
|
+
Before this fix, `specfuse-loop --prepare` crashed with a traceback: auto_sync
|
|
8
|
+
overlays the new scaffold (dirtying .specfuse/VERSION, .scaffold-manifest,
|
|
9
|
+
docs/, templates/) and leaves it uncommitted, then ensure_feature_branch
|
|
10
|
+
refuses to carry those "unexpected" paths onto the new feature branch.
|
|
11
|
+
|
|
12
|
+
The graceful behavior pinned here:
|
|
13
|
+
|
|
14
|
+
1. auto_sync COMMITS its own scaffold overlay on a non-default branch
|
|
15
|
+
(`chore(loop): sync scaffold to X.Y.Z`) so the tree is clean; on the
|
|
16
|
+
DEFAULT branch it leaves them + prints guidance (--prepare carries them).
|
|
17
|
+
2. ensure_feature_branch CARRIES scaffold-managed dirty paths onto a new
|
|
18
|
+
branch instead of refusing — but still blocks genuinely-unrelated edits.
|
|
19
|
+
3. prepare_feature FOLDS the scaffold-managed paths into its scaffold commit
|
|
20
|
+
(so the default-branch case lands them committed on the feature branch).
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import os
|
|
26
|
+
import subprocess
|
|
27
|
+
import unittest
|
|
28
|
+
from contextlib import contextmanager
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from tempfile import TemporaryDirectory
|
|
31
|
+
|
|
32
|
+
from tests._loop_loader import load_loop
|
|
33
|
+
|
|
34
|
+
loop = load_loop()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _git(root: Path, *args: str) -> str:
|
|
38
|
+
return subprocess.run(
|
|
39
|
+
["git", "-C", str(root), *args],
|
|
40
|
+
capture_output=True, text=True, check=True,
|
|
41
|
+
).stdout.strip()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@contextmanager
|
|
45
|
+
def _chdir(path: Path):
|
|
46
|
+
prev = Path.cwd()
|
|
47
|
+
os.chdir(path)
|
|
48
|
+
try:
|
|
49
|
+
yield
|
|
50
|
+
finally:
|
|
51
|
+
os.chdir(prev)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@contextmanager
|
|
55
|
+
def _repo():
|
|
56
|
+
"""Temp git repo on `main`, one commit, a tracked scaffold template."""
|
|
57
|
+
with TemporaryDirectory(ignore_cleanup_errors=True) as tmp:
|
|
58
|
+
root = Path(tmp)
|
|
59
|
+
subprocess.run(["git", "init", "-q", "-b", "main", str(root)], check=True)
|
|
60
|
+
_git(root, "config", "user.email", "test@example.com")
|
|
61
|
+
_git(root, "config", "user.name", "Test")
|
|
62
|
+
_git(root, "config", "commit.gpgSign", "false")
|
|
63
|
+
tmpl = root / ".specfuse/templates/WU.template.md"
|
|
64
|
+
tmpl.parent.mkdir(parents=True)
|
|
65
|
+
tmpl.write_text("old template\n")
|
|
66
|
+
(root / ".specfuse/VERSION").write_text("0.3.0\n")
|
|
67
|
+
(root / "README.md").write_text("# fixture\n")
|
|
68
|
+
_git(root, "add", ".")
|
|
69
|
+
_git(root, "commit", "-q", "-m", "init")
|
|
70
|
+
yield root
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _dirty_scaffold(root: Path) -> None:
|
|
74
|
+
"""Simulate auto_sync's overlay: modify tracked scaffold files."""
|
|
75
|
+
(root / ".specfuse/templates/WU.template.md").write_text("new template\n")
|
|
76
|
+
(root / ".specfuse/VERSION").write_text("0.3.11\n")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class TestDefaultBranch(unittest.TestCase):
|
|
80
|
+
def test_local_main_detected(self):
|
|
81
|
+
with _repo() as root, _chdir(root):
|
|
82
|
+
self.assertEqual(loop._default_branch(), "main")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class TestScaffoldManagedDirty(unittest.TestCase):
|
|
86
|
+
def test_classifies_scaffold_vs_user_paths(self):
|
|
87
|
+
with _repo() as root:
|
|
88
|
+
_dirty_scaffold(root)
|
|
89
|
+
(root / "README.md").write_text("# edited\n") # user edit
|
|
90
|
+
with _chdir(root):
|
|
91
|
+
managed = loop._scaffold_managed_dirty()
|
|
92
|
+
self.assertIn(".specfuse/templates/WU.template.md", managed)
|
|
93
|
+
self.assertIn(".specfuse/VERSION", managed)
|
|
94
|
+
self.assertNotIn("README.md", managed)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class TestPersistScaffoldSync(unittest.TestCase):
|
|
98
|
+
def test_commits_on_feature_branch(self):
|
|
99
|
+
with _repo() as root:
|
|
100
|
+
_git(root, "checkout", "-q", "-b", "feat/x")
|
|
101
|
+
_dirty_scaffold(root)
|
|
102
|
+
with _chdir(root):
|
|
103
|
+
loop._persist_scaffold_sync("0.3.11")
|
|
104
|
+
dirty = loop._tracked_dirty_paths()
|
|
105
|
+
self.assertEqual(dirty, set(), "scaffold sync must be committed")
|
|
106
|
+
self.assertIn("sync scaffold to 0.3.11",
|
|
107
|
+
_git(root, "log", "-1", "--pretty=%s"))
|
|
108
|
+
|
|
109
|
+
def test_guides_and_leaves_on_default_branch(self):
|
|
110
|
+
with _repo() as root: # on main == default
|
|
111
|
+
_dirty_scaffold(root)
|
|
112
|
+
with _chdir(root):
|
|
113
|
+
loop._persist_scaffold_sync("0.3.11")
|
|
114
|
+
dirty = loop._tracked_dirty_paths()
|
|
115
|
+
# left uncommitted (no surprise commit on the default branch)
|
|
116
|
+
self.assertIn(".specfuse/VERSION", dirty)
|
|
117
|
+
self.assertEqual("init", _git(root, "log", "-1", "--pretty=%s"))
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class TestEnsureFeatureBranchCarriesScaffold(unittest.TestCase):
|
|
121
|
+
def test_scaffold_paths_carry_onto_new_branch(self):
|
|
122
|
+
with _repo() as root:
|
|
123
|
+
_dirty_scaffold(root)
|
|
124
|
+
with _chdir(root):
|
|
125
|
+
loop.ensure_feature_branch({"branch": "feat/y"})
|
|
126
|
+
current = _git(root, "branch", "--show-current")
|
|
127
|
+
self.assertEqual(current, "feat/y")
|
|
128
|
+
self.assertEqual(
|
|
129
|
+
(root / ".specfuse/templates/WU.template.md").read_text(),
|
|
130
|
+
"new template\n",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def test_unexpected_edit_still_blocks_even_with_scaffold_dirty(self):
|
|
134
|
+
with _repo() as root:
|
|
135
|
+
_commit = root / "src/app.py"
|
|
136
|
+
_commit.parent.mkdir()
|
|
137
|
+
_commit.write_text("x = 1\n")
|
|
138
|
+
_git(root, "add", ".")
|
|
139
|
+
_git(root, "commit", "-q", "-m", "app")
|
|
140
|
+
_dirty_scaffold(root)
|
|
141
|
+
(root / "src/app.py").write_text("x = 2\n") # unrelated edit
|
|
142
|
+
with _chdir(root):
|
|
143
|
+
with self.assertRaises(loop.FeatureBranchError) as ctx:
|
|
144
|
+
loop.ensure_feature_branch({"branch": "feat/z"})
|
|
145
|
+
self.assertIn("src/app.py", str(ctx.exception))
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class TestPrepareFoldsScaffold(unittest.TestCase):
|
|
149
|
+
def test_default_branch_scaffold_folds_into_prepare_commit(self):
|
|
150
|
+
with _repo() as root: # on main == default
|
|
151
|
+
feat_dir = root / ".specfuse/features/FEAT-2026-0099-x"
|
|
152
|
+
feat_dir.mkdir(parents=True)
|
|
153
|
+
(feat_dir / "PLAN.md").write_text("branch: feat/FEAT-2026-0099-x\n")
|
|
154
|
+
_dirty_scaffold(root) # auto_sync left these on default branch
|
|
155
|
+
with _chdir(root):
|
|
156
|
+
loop.prepare_feature(
|
|
157
|
+
{"branch": "feat/FEAT-2026-0099-x"}, feat_dir,
|
|
158
|
+
"FEAT-2026-0099",
|
|
159
|
+
)
|
|
160
|
+
dirty = loop._tracked_dirty_paths()
|
|
161
|
+
current = _git(root, "branch", "--show-current")
|
|
162
|
+
self.assertEqual(current, "feat/FEAT-2026-0099-x")
|
|
163
|
+
self.assertEqual(dirty, set(), "scaffold + folder must be committed")
|
|
164
|
+
# scaffold file is committed on the feature branch
|
|
165
|
+
self.assertEqual(
|
|
166
|
+
_git(root, "show", "HEAD:.specfuse/templates/WU.template.md"),
|
|
167
|
+
"new template",
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
if __name__ == "__main__":
|
|
172
|
+
unittest.main()
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright 2026 Specfuse Contributors
|
|
3
|
+
# Licensed under the Apache License, Version 2.0. See LICENSE.
|
|
4
|
+
#
|
|
5
|
+
"""Issue #140 — vendored scaffold docs/templates must not carry defects that
|
|
6
|
+
trip Markdown inspections downstream.
|
|
7
|
+
|
|
8
|
+
Two genuine defects were observed in a freshly-initialized project:
|
|
9
|
+
|
|
10
|
+
1. Broken relative links to `methodology.md` from `docs/concepts/*.md`: the
|
|
11
|
+
link uses a same-directory path but `methodology.md` lives one level up in
|
|
12
|
+
`docs/`. Correct target is `../methodology.md`.
|
|
13
|
+
2. A "Bad character" parse error on the em-dash (U+2014) leading the inline
|
|
14
|
+
text of the `DRIVER-OWNED FIELDS` paragraph in `WU.template.md`.
|
|
15
|
+
|
|
16
|
+
These guards run against the CANONICAL sources (`docs/`, `.specfuse/`); the
|
|
17
|
+
byte-match drift guard in test_scaffold_data_in_sync.py keeps the vendored
|
|
18
|
+
`specfuse/loop/data/` copies in lockstep, so fixing the canonicals + re-vendoring
|
|
19
|
+
propagates the fix everywhere.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import pathlib
|
|
25
|
+
import re
|
|
26
|
+
import unittest
|
|
27
|
+
|
|
28
|
+
REPO_ROOT = pathlib.Path(__file__).parent.parent
|
|
29
|
+
DOCS = REPO_ROOT / "docs"
|
|
30
|
+
WU_TEMPLATE = REPO_ROOT / ".specfuse" / "templates" / "WU.template.md"
|
|
31
|
+
|
|
32
|
+
# Markdown inline link with a relative path target (skips absolute URLs and
|
|
33
|
+
# pure-anchor links). Captures the path portion, dropping any `#anchor`.
|
|
34
|
+
_LINK_RE = re.compile(r"\]\((?!https?://)([^)#][^)]*?)(?:#[^)]*)?\)")
|
|
35
|
+
EM_DASH = "—"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class TestScaffoldDocHygiene(unittest.TestCase):
|
|
39
|
+
|
|
40
|
+
def test_docs_relative_md_links_resolve(self):
|
|
41
|
+
"""Every relative .md link in docs/ must resolve to an existing file
|
|
42
|
+
from the linking file's own directory (issue #140 defect 1)."""
|
|
43
|
+
broken: list[str] = []
|
|
44
|
+
for md in sorted(DOCS.rglob("*.md")):
|
|
45
|
+
for lineno, line in enumerate(
|
|
46
|
+
md.read_text(encoding="utf-8").splitlines(), start=1
|
|
47
|
+
):
|
|
48
|
+
for target in _LINK_RE.findall(line):
|
|
49
|
+
if not target.endswith(".md"):
|
|
50
|
+
continue
|
|
51
|
+
resolved = (md.parent / target).resolve()
|
|
52
|
+
if not resolved.is_file():
|
|
53
|
+
rel = md.relative_to(REPO_ROOT)
|
|
54
|
+
broken.append(f"{rel}:{lineno} -> {target}")
|
|
55
|
+
self.assertEqual(
|
|
56
|
+
[], broken,
|
|
57
|
+
"broken relative .md links in docs/ (fix the path):\n"
|
|
58
|
+
+ "\n".join(broken),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def test_wu_template_driver_owned_line_has_no_em_dash(self):
|
|
62
|
+
"""The DRIVER-OWNED FIELDS paragraph must not carry the U+2014 em-dash
|
|
63
|
+
that IntelliJ flags as a Bad character (issue #140 defect 2)."""
|
|
64
|
+
matches = [
|
|
65
|
+
line
|
|
66
|
+
for line in WU_TEMPLATE.read_text(encoding="utf-8").splitlines()
|
|
67
|
+
if line.startswith("DRIVER-OWNED FIELDS")
|
|
68
|
+
]
|
|
69
|
+
self.assertTrue(matches, "DRIVER-OWNED FIELDS line not found in template")
|
|
70
|
+
for line in matches:
|
|
71
|
+
self.assertNotIn(
|
|
72
|
+
EM_DASH, line,
|
|
73
|
+
"DRIVER-OWNED FIELDS line still contains the U+2014 em-dash "
|
|
74
|
+
"that trips the Markdown parser — use ' - ' instead",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
if __name__ == "__main__":
|
|
79
|
+
unittest.main()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.3.10
|
|
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.10 → specfuse_loop-0.3.12}/specfuse/loop/data/rules/security-boundaries.md
RENAMED
|
File without changes
|
{specfuse_loop-0.3.10 → specfuse_loop-0.3.12}/specfuse/loop/data/rules/verification-discipline.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.10 → specfuse_loop-0.3.12}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|