prizmkit 1.1.153 → 1.1.155

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 (178) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/adapters/codex/skill-adapter.js +1 -1
  3. package/bundled/adapters/pi/skill-adapter.js +2 -1
  4. package/bundled/dev-pipeline/.env.example +2 -6
  5. package/bundled/dev-pipeline/README.md +120 -113
  6. package/bundled/dev-pipeline/assets/skill-subagent-integration.md +1 -1
  7. package/bundled/dev-pipeline/prizmkit_runtime/checkpoint_state.py +355 -15
  8. package/bundled/dev-pipeline/prizmkit_runtime/cli.py +189 -110
  9. package/bundled/dev-pipeline/prizmkit_runtime/commands.py +146 -111
  10. package/bundled/dev-pipeline/prizmkit_runtime/daemon.py +4 -20
  11. package/bundled/dev-pipeline/prizmkit_runtime/gitops.py +48 -14
  12. package/bundled/dev-pipeline/prizmkit_runtime/interoperability.py +0 -1
  13. package/bundled/dev-pipeline/prizmkit_runtime/paths.py +0 -3
  14. package/bundled/dev-pipeline/prizmkit_runtime/reset.py +122 -15
  15. package/bundled/dev-pipeline/prizmkit_runtime/reset_preserve.py +1 -1
  16. package/bundled/dev-pipeline/prizmkit_runtime/runner_classification.py +50 -2
  17. package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +1 -10
  18. package/bundled/dev-pipeline/prizmkit_runtime/runner_prompts.py +3 -3
  19. package/bundled/dev-pipeline/prizmkit_runtime/runners.py +139 -74
  20. package/bundled/dev-pipeline/prizmkit_runtime/runtime_commit.py +482 -0
  21. package/bundled/dev-pipeline/prizmkit_runtime/status.py +106 -1
  22. package/bundled/dev-pipeline/prizmkit_runtime/test_result.py +64 -11
  23. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +145 -367
  24. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +91 -62
  25. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +105 -69
  26. package/bundled/dev-pipeline/scripts/init-bugfix-pipeline.py +2 -2
  27. package/bundled/dev-pipeline/scripts/prompt_framework.py +36 -41
  28. package/bundled/dev-pipeline/scripts/update-bug-status.py +197 -71
  29. package/bundled/dev-pipeline/scripts/update-checkpoint.py +77 -16
  30. package/bundled/dev-pipeline/scripts/update-feature-status.py +46 -121
  31. package/bundled/dev-pipeline/scripts/update-refactor-status.py +46 -123
  32. package/bundled/dev-pipeline/scripts/utils.py +119 -0
  33. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +22 -27
  34. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +71 -104
  35. package/bundled/dev-pipeline/templates/bug-fix-list-schema.json +3 -2
  36. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +59 -111
  37. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +37 -72
  38. package/bundled/dev-pipeline/templates/sections/artifact-manifest.md +39 -0
  39. package/bundled/dev-pipeline/templates/sections/bugfix-critical-paths.md +3 -1
  40. package/bundled/dev-pipeline/templates/sections/bugfix-phase-commit-report.md +8 -25
  41. package/bundled/dev-pipeline/templates/sections/bugfix-phase-diagnose-plan.md +3 -3
  42. package/bundled/dev-pipeline/templates/sections/bugfix-phase-implement.md +2 -2
  43. package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +3 -3
  44. package/bundled/dev-pipeline/templates/sections/bugfix-phase-test.md +2 -2
  45. package/bundled/dev-pipeline/templates/sections/bugfix-reminders.md +4 -4
  46. package/bundled/dev-pipeline/templates/sections/bugfix-session-context.md +0 -1
  47. package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +7 -4
  48. package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +14 -27
  49. package/bundled/dev-pipeline/templates/sections/feature-completion-summary.md +20 -0
  50. package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +8 -71
  51. package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +7 -7
  52. package/bundled/dev-pipeline/templates/sections/phase-prizmkit-test.md +3 -3
  53. package/bundled/dev-pipeline/templates/sections/phase-review-full.md +4 -4
  54. package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +5 -17
  55. package/bundled/dev-pipeline/templates/sections/refactor-critical-paths.md +3 -1
  56. package/bundled/dev-pipeline/templates/sections/refactor-phase-commit-report.md +10 -23
  57. package/bundled/dev-pipeline/templates/sections/refactor-phase-implement.md +2 -2
  58. package/bundled/dev-pipeline/templates/sections/refactor-phase-plan.md +2 -2
  59. package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +3 -3
  60. package/bundled/dev-pipeline/templates/sections/refactor-phase-test.md +2 -2
  61. package/bundled/dev-pipeline/templates/sections/refactor-reminders.md +7 -7
  62. package/bundled/dev-pipeline/templates/sections/refactor-session-context.md +0 -1
  63. package/bundled/dev-pipeline/templates/sections/retrospective.md +12 -0
  64. package/bundled/dev-pipeline/templates/sections/runtime-commit-handoff.md +35 -0
  65. package/bundled/dev-pipeline/templates/session-status-schema.json +45 -62
  66. package/bundled/dev-pipeline/tests/test_auto_skip.py +111 -32
  67. package/bundled/dev-pipeline/tests/test_checkpoint_state.py +617 -30
  68. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +262 -194
  69. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +62 -23
  70. package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +76 -28
  71. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +281 -206
  72. package/bundled/dev-pipeline/tests/test_recovery_workflow.py +322 -0
  73. package/bundled/dev-pipeline/tests/test_reset_modes.py +935 -0
  74. package/bundled/dev-pipeline/tests/test_reset_preserve.py +5 -7
  75. package/bundled/dev-pipeline/tests/test_runtime_commit.py +608 -0
  76. package/bundled/dev-pipeline/tests/test_unified_cli.py +617 -188
  77. package/bundled/skills/_metadata.json +9 -9
  78. package/bundled/skills/app-planner/SKILL.md +3 -3
  79. package/bundled/skills/app-planner/references/generated-plan-review.md +2 -1
  80. package/bundled/skills/app-planner/references/rules/backend/derivation-rules.md +56 -64
  81. package/bundled/skills/app-planner/references/rules/backend/fixed-rules.md +85 -240
  82. package/bundled/skills/app-planner/references/rules/backend/question-bank.md +25 -25
  83. package/bundled/skills/app-planner/references/rules/backend/question-manifest.json +16 -7
  84. package/bundled/skills/app-planner/references/rules/backend/template.md +74 -92
  85. package/bundled/skills/app-planner/references/rules/database/derivation-rules.md +93 -93
  86. package/bundled/skills/app-planner/references/rules/database/fixed-rules.md +66 -173
  87. package/bundled/skills/app-planner/references/rules/database/question-bank.md +31 -26
  88. package/bundled/skills/app-planner/references/rules/database/question-manifest.json +13 -4
  89. package/bundled/skills/app-planner/references/rules/database/template.md +76 -98
  90. package/bundled/skills/app-planner/references/rules/frontend/derivation-rules.md +68 -95
  91. package/bundled/skills/app-planner/references/rules/frontend/fixed-rules.md +93 -166
  92. package/bundled/skills/app-planner/references/rules/frontend/question-bank.md +46 -36
  93. package/bundled/skills/app-planner/references/rules/frontend/question-manifest.json +11 -2
  94. package/bundled/skills/app-planner/references/rules/frontend/template.md +79 -257
  95. package/bundled/skills/app-planner/references/rules/mobile/derivation-rules.md +91 -99
  96. package/bundled/skills/app-planner/references/rules/mobile/fixed-rules.md +73 -246
  97. package/bundled/skills/app-planner/references/rules/mobile/question-bank.md +17 -17
  98. package/bundled/skills/app-planner/references/rules/mobile/question-manifest.json +17 -2
  99. package/bundled/skills/app-planner/references/rules/mobile/template.md +73 -85
  100. package/bundled/skills/app-planner/references/rules-configuration.md +133 -65
  101. package/bundled/skills/app-planner/scripts/validate-rules-configuration.py +291 -0
  102. package/bundled/skills/bug-planner/references/schema-validation.md +1 -1
  103. package/bundled/skills/bug-planner/scripts/validate-bug-list.py +1 -1
  104. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +21 -13
  105. package/bundled/skills/feature-pipeline-launcher/SKILL.md +22 -14
  106. package/bundled/skills/prizmkit/SKILL.md +17 -18
  107. package/bundled/skills/prizmkit/references/workflow-state-protocol.md +22 -18
  108. package/bundled/skills/prizmkit-code-review/SKILL.md +22 -58
  109. package/bundled/skills/prizmkit-code-review/references/independent-code-review.md +6 -6
  110. package/bundled/skills/prizmkit-code-review/references/review-report-template.md +2 -2
  111. package/bundled/skills/prizmkit-committer/SKILL.md +82 -132
  112. package/bundled/skills/prizmkit-deploy/SKILL.md +27 -11
  113. package/bundled/skills/prizmkit-deploy/assets/deploy-document-template.md +89 -0
  114. package/bundled/skills/prizmkit-deploy/references/ci-cd-workflows.md +44 -14
  115. package/bundled/skills/prizmkit-deploy/references/cloud-platform-deploy.md +19 -3
  116. package/bundled/skills/prizmkit-deploy/references/database-setup.md +22 -12
  117. package/bundled/skills/prizmkit-deploy/references/deploy-config-schema.md +3 -1
  118. package/bundled/skills/prizmkit-deploy/references/deploy-history-schema.md +22 -6
  119. package/bundled/skills/prizmkit-deploy/references/deploy-metadata-schema.json +49 -0
  120. package/bundled/skills/prizmkit-deploy/references/live-validation-notes.md +1 -1
  121. package/bundled/skills/prizmkit-deploy/references/pending-input-schema.json +164 -0
  122. package/bundled/skills/prizmkit-deploy/references/ssh-adapter-flow.md +9 -8
  123. package/bundled/skills/prizmkit-deploy/references/ssh-execution-flow.md +6 -6
  124. package/bundled/skills/prizmkit-implement/SKILL.md +30 -66
  125. package/bundled/skills/prizmkit-implement/references/implementation-subagent-procedure.md +4 -4
  126. package/bundled/skills/prizmkit-init/SKILL.md +4 -0
  127. package/bundled/skills/prizmkit-plan/SKILL.md +21 -64
  128. package/bundled/skills/prizmkit-plan/references/artifact-identity.md +38 -0
  129. package/bundled/skills/prizmkit-plan/references/independent-plan-review.md +4 -4
  130. package/bundled/skills/prizmkit-plan/references/review-plan-spec-loop.md +5 -5
  131. package/bundled/skills/prizmkit-prizm-docs/SKILL.md +11 -1
  132. package/bundled/skills/prizmkit-retrospective/SKILL.md +63 -77
  133. package/bundled/skills/prizmkit-retrospective/references/knowledge-injection-steps.md +3 -3
  134. package/bundled/skills/prizmkit-retrospective/references/retrospective-result-schema.json +138 -0
  135. package/bundled/skills/prizmkit-retrospective/references/structural-sync-steps.md +17 -23
  136. package/bundled/skills/prizmkit-test/SKILL.md +64 -51
  137. package/bundled/skills/prizmkit-test/references/boundary-contract-and-test-double-guidance.md +206 -0
  138. package/bundled/skills/prizmkit-test/references/independent-test-review.md +17 -9
  139. package/bundled/skills/prizmkit-test/references/test-coverage-model.md +45 -21
  140. package/bundled/skills/prizmkit-test/references/test-report-template.md +20 -13
  141. package/bundled/skills/prizmkit-workflow/SKILL.md +35 -37
  142. package/bundled/skills/prizmkit-workflow/references/artifact-identity.md +34 -0
  143. package/bundled/skills/prizmkit-workflow/references/workflow-state-protocol.md +22 -18
  144. package/bundled/skills/recovery-workflow/SKILL.md +9 -6
  145. package/bundled/skills/recovery-workflow/evals/evals.json +2 -2
  146. package/bundled/skills/recovery-workflow/references/detection.md +7 -6
  147. package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +16 -6
  148. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +21 -13
  149. package/bundled/templates/project-memory-template.md +19 -11
  150. package/package.json +1 -1
  151. package/src/scaffold.js +33 -22
  152. package/bundled/dev-pipeline/prizmkit_runtime/runner_recovery.py +0 -228
  153. package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +0 -767
  154. package/bundled/dev-pipeline/templates/sections/bugfix-artifacts.md +0 -16
  155. package/bundled/dev-pipeline/templates/sections/bugfix-session-status.md +0 -31
  156. package/bundled/dev-pipeline/templates/sections/critical-paths-agent.md +0 -7
  157. package/bundled/dev-pipeline/templates/sections/critical-paths-lite.md +0 -7
  158. package/bundled/dev-pipeline/templates/sections/directory-convention-agent.md +0 -9
  159. package/bundled/dev-pipeline/templates/sections/directory-convention-lite.md +0 -6
  160. package/bundled/dev-pipeline/templates/sections/headless-commit-authorization.md +0 -15
  161. package/bundled/dev-pipeline/templates/sections/phase-commit.md +0 -61
  162. package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-agent-suffix.md +0 -24
  163. package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-lite-suffix.md +0 -12
  164. package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +0 -44
  165. package/bundled/dev-pipeline/templates/sections/phase-implement-lite.md +0 -25
  166. package/bundled/dev-pipeline/templates/sections/phase-plan-agent.md +0 -20
  167. package/bundled/dev-pipeline/templates/sections/phase-plan-lite.md +0 -20
  168. package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +0 -20
  169. package/bundled/dev-pipeline/templates/sections/refactor-artifacts.md +0 -17
  170. package/bundled/dev-pipeline/templates/sections/refactor-session-status.md +0 -28
  171. package/bundled/dev-pipeline/templates/sections/test-failure-recovery-agent.md +0 -52
  172. package/bundled/dev-pipeline/templates/sections/test-failure-recovery-lite.md +0 -52
  173. package/bundled/skills/prizmkit-code-review/references/workflow-state-protocol.md +0 -174
  174. package/bundled/skills/prizmkit-committer/references/workflow-state-protocol.md +0 -174
  175. package/bundled/skills/prizmkit-implement/references/workflow-state-protocol.md +0 -174
  176. package/bundled/skills/prizmkit-plan/references/workflow-state-protocol.md +0 -174
  177. package/bundled/skills/prizmkit-retrospective/references/workflow-state-protocol.md +0 -174
  178. package/bundled/skills/prizmkit-test/references/external-contract-mock-guidance.md +0 -119
@@ -0,0 +1,322 @@
1
+ """Behavior tests for the canonical detector-only Recovery Workflow."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib.util
6
+ import json
7
+ import subprocess
8
+ import sys
9
+ from pathlib import Path
10
+
11
+
12
+ REPO_ROOT = Path(__file__).resolve().parents[2]
13
+ DETECTOR_PATH = (
14
+ REPO_ROOT
15
+ / "core"
16
+ / "skills"
17
+ / "orchestration-skill"
18
+ / "workflows"
19
+ / "recovery-workflow"
20
+ / "scripts"
21
+ / "detect-recovery-state.py"
22
+ )
23
+ STATE_DIRS = {
24
+ "feature": "features",
25
+ "bugfix": "bugfix",
26
+ "refactor": "refactor",
27
+ }
28
+
29
+
30
+ def _load_detector(name: str):
31
+ spec = importlib.util.spec_from_file_location(name, DETECTOR_PATH)
32
+ assert spec is not None and spec.loader is not None
33
+ module = importlib.util.module_from_spec(spec)
34
+ spec.loader.exec_module(module)
35
+ return module
36
+
37
+
38
+ def _git(project: Path, *args: str) -> str:
39
+ result = subprocess.run(
40
+ ["git", *args],
41
+ cwd=project,
42
+ check=True,
43
+ text=True,
44
+ stdout=subprocess.PIPE,
45
+ stderr=subprocess.PIPE,
46
+ )
47
+ return result.stdout.strip()
48
+
49
+
50
+ def _init_repository(project: Path) -> None:
51
+ project.mkdir()
52
+ _git(project, "init", "-b", "main")
53
+ _git(project, "config", "user.email", "detector@example.com")
54
+ _git(project, "config", "user.name", "Detector Test")
55
+ (project / "README.md").write_text("detector fixture\n", encoding="utf-8")
56
+ _git(project, "add", "README.md")
57
+ _git(project, "commit", "-m", "fixture")
58
+
59
+
60
+ def _write_checkout(
61
+ project: Path,
62
+ family: str,
63
+ task_id: str,
64
+ branch: str,
65
+ *,
66
+ state: str = "active",
67
+ worktree_path: str | None = None,
68
+ ) -> Path:
69
+ checkout = project / ".prizmkit" / "state" / STATE_DIRS[family] / task_id / "checkout.json"
70
+ checkout.parent.mkdir(parents=True, exist_ok=True)
71
+ checkout.write_text(
72
+ json.dumps(
73
+ {
74
+ "task_type": family,
75
+ "task_id": task_id,
76
+ "state": state,
77
+ "active_dev_branch": branch,
78
+ "base_branch": "main",
79
+ "checkout_mode": "worktree" if worktree_path else "branch",
80
+ "worktree_path": worktree_path,
81
+ }
82
+ )
83
+ + "\n",
84
+ encoding="utf-8",
85
+ )
86
+ return checkout
87
+
88
+
89
+ def _write_raw_checkout(
90
+ project: Path,
91
+ family: str,
92
+ directory_id: str,
93
+ payload: dict[str, object],
94
+ ) -> Path:
95
+ checkout = (
96
+ project
97
+ / ".prizmkit"
98
+ / "state"
99
+ / STATE_DIRS[family]
100
+ / directory_id
101
+ / "checkout.json"
102
+ )
103
+ checkout.parent.mkdir(parents=True, exist_ok=True)
104
+ checkout.write_text(json.dumps(payload) + "\n", encoding="utf-8")
105
+ return checkout
106
+
107
+
108
+ def _repository_snapshot(project: Path) -> tuple[str, str, str]:
109
+ return (
110
+ _git(project, "branch", "--show-current"),
111
+ _git(project, "for-each-ref", "--format=%(refname):%(objectname)", "refs/heads"),
112
+ _git(project, "status", "--porcelain=v1", "--untracked-files=all"),
113
+ )
114
+
115
+
116
+ def _historical_snapshot(historical: Path) -> dict[str, bytes]:
117
+ return {
118
+ path.relative_to(historical).as_posix(): path.read_bytes()
119
+ for path in historical.rglob("*")
120
+ if path.is_file()
121
+ }
122
+
123
+
124
+ def test_single_active_checkout_reports_operation_first_command_without_mutation(tmp_path):
125
+ project = tmp_path / "project"
126
+ _init_repository(project)
127
+ branch = "dev/F-061-resume"
128
+ _git(project, "branch", branch)
129
+ checkout = _write_checkout(project, "feature", "F-061", branch)
130
+ before = _repository_snapshot(project)
131
+
132
+ result = subprocess.run(
133
+ [sys.executable, str(DETECTOR_PATH), "--project-root", str(project)],
134
+ cwd=project,
135
+ check=False,
136
+ text=True,
137
+ stdout=subprocess.PIPE,
138
+ stderr=subprocess.PIPE,
139
+ )
140
+ report = json.loads(result.stdout)
141
+
142
+ assert result.returncode == 0
143
+ assert result.stderr == ""
144
+ assert report["detected"] is True
145
+ assert report["selection_required"] is False
146
+ assert report["current_branch"] == "main"
147
+ assert len(report["candidates"]) == 1
148
+ candidate = report["candidates"][0]
149
+ assert candidate["family"] == "feature"
150
+ assert candidate["task_id"] == "F-061"
151
+ assert candidate["branch"] == branch
152
+ assert candidate["checkout_path"] == checkout.relative_to(project).as_posix()
153
+ assert candidate["worktree_path"] is None
154
+ assert candidate["continue_command"] == (
155
+ "python3 ./.prizmkit/dev-pipeline/cli.py run feature F-061"
156
+ )
157
+ assert _repository_snapshot(project) == before
158
+ assert not (project / ".prizmkit" / "state" / "recovery").exists()
159
+
160
+
161
+ def test_multiple_candidates_remain_skill_owned_and_ignore_historical_recovery_data(tmp_path):
162
+ project = tmp_path / "project"
163
+ _init_repository(project)
164
+ branches = {
165
+ "feature": ("F-002", "dev/F-002-resume"),
166
+ "bugfix": ("B-007", "fix/B-007-resume"),
167
+ }
168
+ for task_id, branch in branches.values():
169
+ _git(project, "branch", branch)
170
+ for family, (task_id, branch) in branches.items():
171
+ _write_checkout(project, family, task_id, branch)
172
+
173
+ historical = project / ".prizmkit" / "state" / "recovery" / "old-session"
174
+ historical.mkdir(parents=True)
175
+ (historical / "detection.json").write_text(
176
+ '{"detected": true, "legacy": true}\n', encoding="utf-8"
177
+ )
178
+ history_before = _historical_snapshot(historical)
179
+ repository_before = _repository_snapshot(project)
180
+ detector = _load_detector("recovery_workflow_multiple")
181
+
182
+ report = detector.build_report(project)
183
+
184
+ assert report["detected"] is True
185
+ assert report["selection_required"] is True
186
+ assert [candidate["task_id"] for candidate in report["candidates"]] == ["F-002", "B-007"]
187
+ assert [candidate["continue_command"] for candidate in report["candidates"]] == [
188
+ "python3 ./.prizmkit/dev-pipeline/cli.py run feature F-002",
189
+ "python3 ./.prizmkit/dev-pipeline/cli.py run bugfix B-007",
190
+ ]
191
+ assert _repository_snapshot(project) == repository_before
192
+ assert _historical_snapshot(historical) == history_before
193
+ assert sorted(path.name for path in historical.parent.iterdir()) == ["old-session"]
194
+
195
+
196
+ def test_zero_candidates_filters_invalid_records_without_history_fallback_or_session_creation(tmp_path):
197
+ project = tmp_path / "project"
198
+ _init_repository(project)
199
+ completed_branch = "fix/B-003-complete"
200
+ wrong_family_branch = "dev/B-999-wrong-family"
201
+ _git(project, "branch", completed_branch)
202
+ _git(project, "branch", wrong_family_branch)
203
+ _write_checkout(
204
+ project,
205
+ "bugfix",
206
+ "B-003",
207
+ completed_branch,
208
+ state="completed",
209
+ )
210
+ _write_checkout(project, "feature", "B-999", wrong_family_branch)
211
+ _write_checkout(project, "refactor", "R-008", "refactor/R-008-missing")
212
+
213
+ historical = project / ".prizmkit" / "state" / "recovery" / "historical-only"
214
+ historical.mkdir(parents=True)
215
+ (historical / "bootstrap-prompt.md").write_text("do not read or delete\n", encoding="utf-8")
216
+ history_before = _historical_snapshot(historical)
217
+ repository_before = _repository_snapshot(project)
218
+ detector = _load_detector("recovery_workflow_empty")
219
+
220
+ report = detector.build_report(project)
221
+
222
+ assert report == {
223
+ "detected": False,
224
+ "current_branch": "main",
225
+ "candidates": [],
226
+ "message": "No active interrupted pipeline task branch was found.",
227
+ }
228
+ assert _repository_snapshot(project) == repository_before
229
+ assert _historical_snapshot(historical) == history_before
230
+ assert sorted(path.name for path in historical.parent.iterdir()) == ["historical-only"]
231
+
232
+
233
+ def test_detector_rejects_malformed_or_mismatched_checkout_identity(tmp_path):
234
+ project = tmp_path / "project"
235
+ _init_repository(project)
236
+ invalid_records = (
237
+ (
238
+ "feature",
239
+ "F-not-an-id",
240
+ "dev/F-not-an-id",
241
+ {
242
+ "task_type": "feature",
243
+ "task_id": "F-not-an-id",
244
+ "state": "active",
245
+ "active_dev_branch": "dev/F-not-an-id",
246
+ "base_branch": "main",
247
+ "checkout_mode": "branch",
248
+ },
249
+ ),
250
+ (
251
+ "bugfix",
252
+ "B-004",
253
+ "bugfix/B-004-missing-id",
254
+ {
255
+ "task_type": "bugfix",
256
+ "state": "active",
257
+ "active_dev_branch": "bugfix/B-004-missing-id",
258
+ "base_branch": "main",
259
+ "checkout_mode": "branch",
260
+ },
261
+ ),
262
+ (
263
+ "refactor",
264
+ "R-005",
265
+ "refactor/R-005-wrong-type",
266
+ {
267
+ "task_type": "feature",
268
+ "task_id": "R-005",
269
+ "state": "active",
270
+ "active_dev_branch": "refactor/R-005-wrong-type",
271
+ "base_branch": "main",
272
+ "checkout_mode": "branch",
273
+ },
274
+ ),
275
+ (
276
+ "feature",
277
+ "F-006",
278
+ "dev/F-006-path-mismatch",
279
+ {
280
+ "task_type": "feature",
281
+ "task_id": "F-007",
282
+ "state": "active",
283
+ "active_dev_branch": "dev/F-006-path-mismatch",
284
+ "base_branch": "main",
285
+ "checkout_mode": "branch",
286
+ },
287
+ ),
288
+ )
289
+ for family, directory_id, branch, payload in invalid_records:
290
+ _git(project, "branch", branch)
291
+ _write_raw_checkout(project, family, directory_id, payload)
292
+
293
+ detector = _load_detector("recovery_workflow_invalid_identity")
294
+
295
+ assert detector.detect_candidates(project) == []
296
+
297
+
298
+ def test_recovery_workflow_metadata_matches_detector_only_contract():
299
+ metadata = json.loads((REPO_ROOT / "core" / "skills" / "_metadata.json").read_text())
300
+ recovery = metadata["skills"]["recovery-workflow"]
301
+ description = recovery["description"].lower()
302
+
303
+ assert recovery["hasScripts"] is True
304
+ assert "active feature, bugfix, or refactor checkout records" in description
305
+ assert "operation-first" in description
306
+ assert "direct requirement" not in description
307
+
308
+
309
+ def test_canonical_recovery_and_cli_guidance_has_no_retired_contracts():
310
+ env_example = (REPO_ROOT / "dev-pipeline" / ".env.example").read_text(encoding="utf-8")
311
+ readme = (REPO_ROOT / "README.md").read_text(encoding="utf-8")
312
+ readme_en = (REPO_ROOT / "README_EN.md").read_text(encoding="utf-8")
313
+
314
+ assert "cli.py feature run" not in env_example
315
+ assert "cli.py run feature" in env_example
316
+ assert "从断点继续" not in readme
317
+ assert "reset/clean/unskip/complete" not in readme
318
+ assert "reset/clean/reset_state/complete" in readme
319
+ assert "Auto-detect + resume from breakpoint" not in readme_en
320
+ assert "interrupted at Phase 4" not in readme_en
321
+ assert "Resumes from Phase 5" not in readme_en
322
+ assert "Does not execute the command or infer a lifecycle phase" in readme_en