specfuse-loop 0.3.1__tar.gz → 0.3.2__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.
Files changed (131) hide show
  1. {specfuse_loop-0.3.1/specfuse_loop.egg-info → specfuse_loop-0.3.2}/PKG-INFO +1 -1
  2. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/pyproject.toml +1 -1
  3. specfuse_loop-0.3.2/specfuse/loop/data/VERSION +1 -0
  4. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse/loop/data/templates/GATE.template.md +0 -6
  5. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse/loop/data/templates/PLAN.template.md +0 -6
  6. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse/loop/data/templates/WU.template.md +3 -6
  7. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse/loop/loop.py +15 -1
  8. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2/specfuse_loop.egg-info}/PKG-INFO +1 -1
  9. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse_loop.egg-info/SOURCES.txt +2 -1
  10. specfuse_loop-0.3.2/tests/test_wu_execution_metadata.py +115 -0
  11. specfuse_loop-0.3.1/specfuse/loop/data/VERSION +0 -1
  12. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/LICENSE +0 -0
  13. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/NOTICE +0 -0
  14. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/README.md +0 -0
  15. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/setup.cfg +0 -0
  16. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse/loop/__init__.py +0 -0
  17. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse/loop/_miniyaml.py +0 -0
  18. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse/loop/adopt_feature.py +0 -0
  19. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse/loop/data/LEARNINGS.template.md +0 -0
  20. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse/loop/data/docs/concepts/architecture-addendum-gates-and-iterative-planning.md +0 -0
  21. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse/loop/data/docs/concepts/ralph-lineage.md +0 -0
  22. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse/loop/data/docs/getting-started.md +0 -0
  23. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse/loop/data/docs/methodology.md +0 -0
  24. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse/loop/data/docs/skills.md +0 -0
  25. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse/loop/data/gitignore.snippet +0 -0
  26. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse/loop/data/roadmap.template.md +0 -0
  27. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse/loop/data/rules/correlation-ids.md +0 -0
  28. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse/loop/data/rules/never-touch.md +0 -0
  29. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse/loop/data/rules/result-contract.md +0 -0
  30. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse/loop/data/rules/security-boundaries.md +0 -0
  31. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse/loop/data/verification.yml.example +0 -0
  32. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse/loop/gate_eval.py +0 -0
  33. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse/loop/gh_backend.py +0 -0
  34. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse/loop/gh_features.py +0 -0
  35. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse/loop/lint_plan.py +0 -0
  36. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse/loop/scaffold.py +0 -0
  37. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse/loop/validate_event.py +0 -0
  38. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse_loop.egg-info/dependency_links.txt +0 -0
  39. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse_loop.egg-info/entry_points.txt +0 -0
  40. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse_loop.egg-info/requires.txt +0 -0
  41. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/specfuse_loop.egg-info/top_level.txt +0 -0
  42. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_adopt_feature.py +0 -0
  43. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_arm_gate_edits_uncommitted.py +0 -0
  44. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_attempt_outcome_emission.py +0 -0
  45. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_autosync.py +0 -0
  46. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_autosync_consent.py +0 -0
  47. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_autosync_firstrun.py +0 -0
  48. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_autosync_plugin.py +0 -0
  49. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_backend.py +0 -0
  50. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_bookkeeping_commit_crash_run.py +0 -0
  51. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_bookkeeping_commit_hook_crash.py +0 -0
  52. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_closing_deliverable_guard.py +0 -0
  53. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_cost_tracking.py +0 -0
  54. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_deliverable_presence_gate.py +0 -0
  55. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_doctor.py +0 -0
  56. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_driver_integration.py +0 -0
  57. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_driver_lock.py +0 -0
  58. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_duration_tracking.py +0 -0
  59. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_empty_files_escalation.py +0 -0
  60. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_ensure_feature_branch.py +0 -0
  61. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_extra_gates.py +0 -0
  62. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_force_full_close.py +0 -0
  63. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_gate_eval.py +0 -0
  64. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_gate_eval_calibration.py +0 -0
  65. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_gate_eval_intermediate_wiring.py +0 -0
  66. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_gate_eval_terminal_wiring.py +0 -0
  67. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_gh_backend.py +0 -0
  68. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_gh_features.py +0 -0
  69. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_git_env_isolation.py +0 -0
  70. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_hashed_denylist.py +0 -0
  71. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_hashed_denylist_ci.py +0 -0
  72. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_init_integration.py +0 -0
  73. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_leak_findings_redaction.py +0 -0
  74. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_leak_scan.py +0 -0
  75. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_leak_scan_content.py +0 -0
  76. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_legacy_4wu_terminal_flips.py +0 -0
  77. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_lifecycle_integration.py +0 -0
  78. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_lint_bare_produces_path.py +0 -0
  79. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_lint_close_intermediate.py +0 -0
  80. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_lint_close_wu.py +0 -0
  81. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_lint_correlation_id.py +0 -0
  82. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_lint_correlation_id_close_intermediate.py +0 -0
  83. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_lint_oracle_env.py +0 -0
  84. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_lint_plan_errors.py +0 -0
  85. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_lint_plan_next_draft.py +0 -0
  86. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_lint_produces_driver_helper.py +0 -0
  87. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_lint_sections.py +0 -0
  88. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_lint_task_graph_yaml_selection.py +0 -0
  89. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_loop_auto_archive.py +0 -0
  90. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_loop_caveman_preamble.py +0 -0
  91. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_loop_close_intermediate.py +0 -0
  92. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_loop_defaults_by_type.py +0 -0
  93. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_loop_effort.py +0 -0
  94. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_loop_failure_note_cap.py +0 -0
  95. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_loop_files_changed_guard.py +0 -0
  96. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_loop_gate_budget.py +0 -0
  97. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_loop_model_alias.py +0 -0
  98. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_loop_orchestration.py +0 -0
  99. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_loop_post_pass_invariant.py +0 -0
  100. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_loop_reset_preserving_events.py +0 -0
  101. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_loop_smoke_runner.py +0 -0
  102. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_loop_unsandboxed.py +0 -0
  103. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_loop_zero_token_guard.py +0 -0
  104. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_migrate_legacy.py +0 -0
  105. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_miniyaml_equivalence.py +0 -0
  106. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_miniyaml_negative.py +0 -0
  107. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_planned_cost_lint.py +0 -0
  108. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_produces_field.py +0 -0
  109. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_result_block.py +0 -0
  110. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_roadmap_add_skill.py +0 -0
  111. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_roadmap_archive_skill.py +0 -0
  112. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_roadmap_row_parser.py +0 -0
  113. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_scaffold_data_in_sync.py +0 -0
  114. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_scaffold_docs.py +0 -0
  115. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_scaffold_init.py +0 -0
  116. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_scaffold_manifest.py +0 -0
  117. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_scaffold_resources.py +0 -0
  118. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_scaffold_seed_sanity.py +0 -0
  119. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_scaffold_upgrade.py +0 -0
  120. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_scaffold_wiring.py +0 -0
  121. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_squash_commit_hook_crash.py +0 -0
  122. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_template_closing_shapes.py +0 -0
  123. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_terminal_flip_ownership.py +0 -0
  124. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_terminal_flips.py +0 -0
  125. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_untracked_feature_folder.py +0 -0
  126. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_upgrade_integration.py +0 -0
  127. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_validate_event.py +0 -0
  128. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_verdict_coupling.py +0 -0
  129. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_verify_empty_gate_set.py +0 -0
  130. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_version_consistency.py +0 -0
  131. {specfuse_loop-0.3.1 → specfuse_loop-0.3.2}/tests/test_version_skew.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specfuse-loop
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: Local-first executor for the Specfuse Plan + Work Unit gate-cycle methodology.
5
5
  Author: Specfuse contributors
6
6
  License: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "specfuse-loop"
7
- version = "0.3.1"
7
+ version = "0.3.2"
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.2
@@ -14,12 +14,6 @@ status: open # open | awaiting_review | passed
14
14
  # set it in the successor gate to exercise the brake for the first time.
15
15
  ---
16
16
 
17
- <!--
18
- Copyright 2026 Specfuse Contributors
19
- Licensed under the Apache License, Version 2.0. See LICENSE.
20
- -->
21
-
22
-
23
17
  # Gate 1 — <name of the milestone this gate proves>
24
18
 
25
19
  ## Definition of done
@@ -11,12 +11,6 @@ status: active # active | done | abandoned
11
11
  # planned_cost_usd: 0.00 # OPTIONAL — sum of WU planned costs; lint warns when missing or when delta from WU sum > 10%
12
12
  ---
13
13
 
14
- <!--
15
- Copyright 2026 Specfuse Contributors
16
- Licensed under the Apache License, Version 2.0. See LICENSE.
17
- -->
18
-
19
-
20
14
  # Plan: <short feature title>
21
15
 
22
16
  <One or two paragraphs of human-facing intent. The work units carry the executable
@@ -10,12 +10,6 @@ generated_surfaces: [] # OPTIONAL — paths to generated files this unit's a
10
10
  # oracle_env: macos_local # OPTIONAL — environment where this WU's verifying oracle runs; see frontmatter notes
11
11
  ---
12
12
 
13
- <!--
14
- Copyright 2026 Specfuse Contributors
15
- Licensed under the Apache License, Version 2.0. See LICENSE.
16
- -->
17
-
18
-
19
13
  <!--
20
14
  Frontmatter notes (single-repo):
21
15
 
@@ -48,6 +42,9 @@ DRIVER-OWNED FIELDS — the driver writes these at outcome time; authors leave t
48
42
  <!-- driver-owned: attempts, cost_usd, input_tokens, output_tokens, duration_seconds,
49
43
  cumulative_cost_usd, cumulative_duration_seconds, cumulative_input_tokens,
50
44
  cumulative_output_tokens, re_arm_count, re_arm_history -->
45
+ <!-- driver-stamped at dispatch (resolved execution metadata, visible in this .md):
46
+ model, effort (override or type default), gate_set (the verification.yml set
47
+ that is this WU's exit oracle), driver_version, started_at (UTC ISO). -->
51
48
  <!-- Full field semantics in docs/methodology.md §2 and events.jsonl outcome payloads. -->
52
49
 
53
50
  Dependencies live in PLAN.md's `gates[].work_units[].depends_on` graph, not
@@ -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.1"
65
+ DRIVER_VERSION = "0.3.2"
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
@@ -3156,6 +3156,20 @@ def run(
3156
3156
  if _is_rearm:
3157
3157
  fold_cumulative_on_rearm(wu, backend)
3158
3158
  backend.set_wu(wu, "status", "in_progress")
3159
+ # Stamp the resolved execution metadata into the WU frontmatter so
3160
+ # it is visible when you read the .md (not only on the console /
3161
+ # in events.jsonl): the model + effort it runs with (override or
3162
+ # type default), which verification gate set is its exit oracle,
3163
+ # the driver version that executed it, and when it was dispatched.
3164
+ # Written after head_before so they ride the WU's squash commit;
3165
+ # idempotent on retries/re-arms (same values overwrite cleanly,
3166
+ # started_at refreshes to the latest dispatch).
3167
+ backend.set_wu(wu, "model", wu.model)
3168
+ backend.set_wu(wu, "effort", wu.effort)
3169
+ backend.set_wu(wu, "gate_set", GATES_FOR_TYPE.get(wu.type, "code"))
3170
+ backend.set_wu(wu, "driver_version", DRIVER_VERSION)
3171
+ backend.set_wu(wu, "started_at",
3172
+ dt.datetime.now(dt.timezone.utc).isoformat())
3159
3173
  # Events and per-attempt notes are buffered in memory during the
3160
3174
  # WU's lifecycle and flushed at outcome time. This prevents the
3161
3175
  # `git reset --hard` between failed attempts from silently
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specfuse-loop
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: Local-first executor for the Specfuse Plan + Work Unit gate-cycle methodology.
5
5
  Author: Specfuse contributors
6
6
  License: Apache-2.0
@@ -124,4 +124,5 @@ tests/test_validate_event.py
124
124
  tests/test_verdict_coupling.py
125
125
  tests/test_verify_empty_gate_set.py
126
126
  tests/test_version_consistency.py
127
- tests/test_version_skew.py
127
+ tests/test_version_skew.py
128
+ tests/test_wu_execution_metadata.py
@@ -0,0 +1,115 @@
1
+ #
2
+ # Copyright 2026 Specfuse contributors
3
+ # Licensed under the Apache License, Version 2.0. See LICENSE.
4
+ #
5
+ """The driver stamps resolved execution metadata into a WU's frontmatter.
6
+
7
+ So the planned/effective model, effort, exit-oracle gate set, driver version,
8
+ and dispatch time are visible when you read the WU `.md` — not only on the
9
+ console or in events.jsonl. Written at dispatch (after status -> in_progress),
10
+ so they ride the WU's squash commit.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import os
16
+ import subprocess
17
+ import unittest
18
+ from pathlib import Path
19
+
20
+ from tests._loop_loader import load_loop
21
+ from tests.test_driver_integration import (
22
+ integration_workspace,
23
+ write_minimal_feature,
24
+ _read_frontmatter,
25
+ )
26
+
27
+ loop = load_loop()
28
+
29
+
30
+ def _write_verification_yml(root: Path) -> None:
31
+ (root / ".specfuse/verification.yml").write_text(
32
+ "code:\n - name: noop\n command: \"true\"\n"
33
+ "doc:\n - name: noop\n command: \"true\"\n"
34
+ "plannext:\n - name: noop\n command: \"true\"\n"
35
+ )
36
+ subprocess.run(["git", "-C", str(root), "add", ".specfuse/verification.yml"],
37
+ check=True)
38
+ subprocess.run(["git", "-C", str(root), "commit", "-q", "-m", "verif"], check=True)
39
+
40
+
41
+ class TestWUExecutionMetadata(unittest.TestCase):
42
+
43
+ def setUp(self):
44
+ self._cwd = os.getcwd()
45
+ self._patches: list[tuple[str, object]] = []
46
+
47
+ def tearDown(self):
48
+ os.chdir(self._cwd)
49
+ for name, original in self._patches:
50
+ setattr(loop, name, original)
51
+
52
+ def _patch(self, name: str, replacement) -> None:
53
+ self._patches.append((name, getattr(loop, name)))
54
+ setattr(loop, name, replacement)
55
+
56
+ def test_dispatch_stamps_model_effort_gateset_driver_started(self):
57
+ with integration_workspace() as root:
58
+ os.chdir(root)
59
+ _write_verification_yml(root)
60
+ write_minimal_feature(
61
+ root, "FEAT-2026-9301", "exec-meta", "feat/exec-meta",
62
+ [("FEAT-2026-9301/T01", "implementation", "pending")],
63
+ )
64
+ self._patch("dispatch", lambda wu, fn, cost_tracking=True: ("(stub)\n", None))
65
+ self._patch("verify", lambda wu, fd, cfg=None: (True, "(stub)"))
66
+
67
+ rc = loop.run(None, dry_run=False)
68
+ self.assertEqual(rc, 0)
69
+
70
+ fdir = root / ".specfuse/features/FEAT-2026-9301-exec-meta"
71
+ fm = _read_frontmatter(fdir / "WU-T01.md")
72
+
73
+ # model = the WU's resolved model (the integration helper authors a
74
+ # concrete haiku id); effort defaults to EFFORT_BY_TYPE[implementation]
75
+ # = medium (helper sets no effort); gate_set = GATES_FOR_TYPE = code.
76
+ self.assertEqual(fm.get("model"), "claude-haiku-4-5-20251001")
77
+ self.assertEqual(fm.get("effort"), "medium")
78
+ self.assertEqual(fm.get("gate_set"), "code")
79
+ self.assertEqual(fm.get("driver_version"), loop.DRIVER_VERSION)
80
+ # started_at is a UTC ISO timestamp.
81
+ self.assertIn("started_at", fm)
82
+ self.assertRegex(fm["started_at"], r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}")
83
+
84
+ def test_author_model_effort_override_is_preserved(self):
85
+ """An explicit model/effort override survives the stamp (same value)."""
86
+ with integration_workspace() as root:
87
+ os.chdir(root)
88
+ _write_verification_yml(root)
89
+ write_minimal_feature(
90
+ root, "FEAT-2026-9302", "exec-ovr", "feat/exec-ovr",
91
+ [("FEAT-2026-9302/T01", "implementation", "pending")],
92
+ )
93
+ # Override the helper's default model with an alias + set effort.
94
+ wu_path = root / ".specfuse/features/FEAT-2026-9302-exec-ovr/WU-T01.md"
95
+ text = wu_path.read_text()
96
+ text = text.replace("model: claude-haiku-4-5-20251001\n",
97
+ "model: opus\neffort: high\n", 1)
98
+ wu_path.write_text(text)
99
+ subprocess.run(["git", "-C", str(root), "add", "."], check=True)
100
+ subprocess.run(["git", "-C", str(root), "commit", "-q", "-m", "override"],
101
+ check=True)
102
+
103
+ self._patch("dispatch", lambda wu, fn, cost_tracking=True: ("(stub)\n", None))
104
+ self._patch("verify", lambda wu, fd, cfg=None: (True, "(stub)"))
105
+ rc = loop.run(None, dry_run=False)
106
+ self.assertEqual(rc, 0)
107
+
108
+ fm = _read_frontmatter(
109
+ root / ".specfuse/features/FEAT-2026-9302-exec-ovr/WU-T01.md")
110
+ self.assertEqual(fm.get("model"), "opus")
111
+ self.assertEqual(fm.get("effort"), "high")
112
+
113
+
114
+ if __name__ == "__main__":
115
+ unittest.main()
@@ -1 +0,0 @@
1
- 0.3.1
File without changes
File without changes
File without changes
File without changes