its-magic 0.1.3-0 → 0.1.3-2

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 (61) hide show
  1. package/installer.ps1 +8 -0
  2. package/installer.py +8 -0
  3. package/installer.sh +1 -0
  4. package/package.json +1 -1
  5. package/scripts/check_intake_template_parity.py +62 -0
  6. package/template/.cursor/agents/curator.mdc +1 -0
  7. package/template/.cursor/agents/po.mdc +1 -0
  8. package/template/.cursor/agents/release.mdc +1 -0
  9. package/template/.cursor/commands/auto.md +90 -0
  10. package/template/.cursor/commands/execute.md +26 -0
  11. package/template/.cursor/commands/refresh-context.md +32 -0
  12. package/template/.cursor/commands/sovereign-critic.md +104 -0
  13. package/template/.cursor/model-catalog.local.example.cursor-only.json +9 -0
  14. package/template/.cursor/model-catalog.local.example.json +9 -0
  15. package/template/.cursor/model-catalog.local.example.level-1-easy.json +9 -0
  16. package/template/.cursor/model-catalog.local.example.level-2-complex.json +9 -0
  17. package/template/.cursor/model-catalog.local.example.level-3-mega.json +9 -0
  18. package/template/.cursor/model-catalog.local.example.level-4-super.json +9 -0
  19. package/template/.cursor/model-catalog.local.example.role-based-balanced.json +18 -0
  20. package/template/.cursor/model-catalog.local.example.role-based-highend.json +18 -0
  21. package/template/.cursor/rules/sovereign-role-manifest.mdc.example +27 -0
  22. package/template/.cursor/scratchpad.local.example.md +55 -0
  23. package/template/.cursor/scratchpad.md +203 -0
  24. package/template/.cursor/sovereign-role-manifest.yaml.example +53 -0
  25. package/template/decisions/DEC-0104.md +279 -0
  26. package/template/decisions/DEC-0105.md +231 -0
  27. package/template/decisions/DEC-0107.md +246 -0
  28. package/template/docs/engineering/architecture.md +78 -0
  29. package/template/docs/engineering/auto-orchestration-reference.md +7 -0
  30. package/template/docs/engineering/context/installer-owned-paths.manifest +8 -0
  31. package/template/docs/engineering/reason_codes.md +411 -0
  32. package/template/docs/engineering/runbook.md +1119 -0
  33. package/template/docs/engineering/sovereign-memory/.gitkeep +0 -0
  34. package/template/docs/engineering/sovereign-memory/retrospectives/.gitkeep +0 -0
  35. package/template/handoffs/sovereign_decisions/.gitkeep +0 -0
  36. package/template/handoffs/sovereign_deferrals/.gitkeep +0 -0
  37. package/template/handoffs/sovereign_role_reviews.jsonl +0 -0
  38. package/template/scripts/__pycache__/decision_ledger_lib.cpython-312.pyc +0 -0
  39. package/template/scripts/check_intake_template_parity.py +181 -0
  40. package/template/scripts/decision_ledger_lib.py +732 -0
  41. package/template/scripts/ledger_validate.py +153 -0
  42. package/template/scripts/model_tier_lib.py +680 -0
  43. package/template/scripts/model_tier_validate.py +368 -0
  44. package/template/scripts/parallel_dev_arbiter.py +923 -0
  45. package/template/scripts/release_trigger_adapters.py +843 -0
  46. package/template/scripts/self_healing_deploy_lib.py +463 -0
  47. package/template/scripts/self_healing_deploy_validate.py +78 -0
  48. package/template/scripts/sovereign_convergence_lib.py +994 -0
  49. package/template/scripts/sovereign_convergence_validate.py +206 -0
  50. package/template/scripts/sovereign_critic_lib.py +629 -0
  51. package/template/scripts/sovereign_critic_validate.py +131 -0
  52. package/template/scripts/sovereign_loop_lib.py +828 -0
  53. package/template/scripts/sovereign_loop_validate.py +122 -0
  54. package/template/scripts/sovereign_memory_lib.py +869 -0
  55. package/template/scripts/sovereign_memory_validate.py +153 -0
  56. package/template/scripts/sovereign_role_manifest_lib.py +547 -0
  57. package/template/scripts/sovereign_role_manifest_validate.py +105 -0
  58. package/template/tests/us0108_contract_test.py +207 -0
  59. package/template/tests/us0109_contract_test.py +209 -0
  60. package/template/tests/us0109_us0110_compose_test.py +34 -0
  61. package/template/tests/us0111_contract_test.py +345 -0
@@ -0,0 +1,345 @@
1
+ """US-0111: Twelve `test_us0111_*` contract tests for Release Trigger Adapters.
2
+
3
+ DEC-0111 §7: adapter registry, GitHub webhook, npm publish, Git tag push,
4
+ manual /release, version comparison, atomic promotion, per-version notes,
5
+ ledger event, 9 reason codes, compose guards (US-0100, US-0054).
6
+
7
+ Default source: RELEASE_TRIGGER_SOURCE=manual (zero behavior change vs pre-US-0111).
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import os
14
+ import sys
15
+ import tempfile
16
+ import unittest
17
+ from pathlib import Path
18
+
19
+
20
+ def _repo_root() -> Path:
21
+ return Path(__file__).resolve().parents[1]
22
+
23
+
24
+ def _load_lib():
25
+ root = _repo_root()
26
+ scripts_dir = str(root / "scripts")
27
+ if scripts_dir not in sys.path:
28
+ sys.path.insert(0, scripts_dir)
29
+ import release_trigger_adapters as mod # noqa: E402
30
+ return mod
31
+
32
+
33
+ # --- T-001: Adapter registry dispatch (AC-1) ---------------------------------
34
+
35
+
36
+ class US0111AdapterRegistryDispatchTest(unittest.TestCase):
37
+ """test_us0111_adapter_registry_dispatch (AC-1)."""
38
+
39
+ def test_us0111_adapter_registry_dispatch(self) -> None:
40
+ lib = _load_lib()
41
+ registry = lib._ADAPTER_MAP
42
+ self.assertIn("manual", registry)
43
+ self.assertIn("github", registry)
44
+ self.assertIn("npm", registry)
45
+ self.assertIn("git_tag", registry)
46
+ self.assertEqual(len(registry), 4)
47
+
48
+ # Dispatch to manual adapter
49
+ ctx = lib.dispatch_to_adapter("manual", env_vars={}, current_version="1.2.3")
50
+ self.assertEqual(ctx.source, "manual")
51
+ self.assertEqual(ctx.version, "1.2.3")
52
+ self.assertIsNone(ctx.previous_version)
53
+
54
+ # Invalid source → fail-closed
55
+ with self.assertRaises(lib.ReleaseTriggerError) as cm:
56
+ lib.dispatch_to_adapter("invalid_source", env_vars={})
57
+ self.assertEqual(cm.exception.code, "RELEASE_TRIGGER_SOURCE_INVALID")
58
+
59
+
60
+ # --- T-002: GitHub webhook adapter (AC-2) ------------------------------------
61
+
62
+
63
+ class US0111GithubAdapterTest(unittest.TestCase):
64
+ """test_us0111_github_adapter_success_fail_closed (AC-2)."""
65
+
66
+ def test_us0111_github_adapter_success_fail_closed(self) -> None:
67
+ lib = _load_lib()
68
+ # Success path: valid payload with tag_name
69
+ payload = {"release": {"tag_name": "v1.2.3"}}
70
+ env = {"GITHUB_TOKEN": "test-token", "GITHUB_REPOSITORY": "owner/repo"}
71
+ adapter = lib.GithubReleaseAdapter(env_vars=env, payload=payload, timeout_sec=5)
72
+ ctx = adapter.get_version_info()
73
+ self.assertEqual(ctx.source, "github")
74
+ self.assertEqual(ctx.version, "1.2.3")
75
+ self.assertIn("GITHUB_TOKEN", ctx.metadata.get("token_env_ref", ""))
76
+
77
+ # Fail-closed: missing tag_name
78
+ adapter_bad = lib.GithubReleaseAdapter(env_vars={}, payload={}, timeout_sec=5)
79
+ with self.assertRaises(lib.ReleaseTriggerError) as cm:
80
+ adapter_bad.get_version_info()
81
+ self.assertEqual(cm.exception.code, "RELEASE_TRIGGER_TAG_MISSING")
82
+
83
+
84
+ # --- T-003: npm publish adapter (AC-3) ---------------------------------------
85
+
86
+
87
+ class US0111NpmAdapterTest(unittest.TestCase):
88
+ """test_us0111_npm_adapter_success_fail_closed (AC-3)."""
89
+
90
+ def test_us0111_npm_adapter_success_fail_closed(self) -> None:
91
+ lib = _load_lib()
92
+ # Success path: npm_package_version env var
93
+ env = {"npm_package_version": "2.0.1", "npm_package_name": "test-pkg"}
94
+ adapter = lib.NpmPublishAdapter(env_vars=env, timeout_sec=5)
95
+ ctx = adapter.get_version_info()
96
+ self.assertEqual(ctx.source, "npm")
97
+ self.assertEqual(ctx.version, "2.0.1")
98
+
99
+ # Fail-closed: missing package.json + env var
100
+ with tempfile.TemporaryDirectory() as tmp:
101
+ adapter_bad = lib.NpmPublishAdapter(
102
+ env_vars={}, timeout_sec=5, repo_root=tmp
103
+ )
104
+ with self.assertRaises(lib.ReleaseTriggerError) as cm:
105
+ adapter_bad.get_version_info()
106
+ self.assertEqual(cm.exception.code, "RELEASE_TRIGGER_PACKAGE_JSON_MISSING")
107
+
108
+
109
+ # --- T-004: Git tag push adapter (AC-4) --------------------------------------
110
+
111
+
112
+ class US0111GitTagAdapterTest(unittest.TestCase):
113
+ """test_us0111_git_tag_adapter_success_fail_closed (AC-4)."""
114
+
115
+ def test_us0111_git_tag_adapter_success_fail_closed(self) -> None:
116
+ lib = _load_lib()
117
+ # Success path: GITHUB_REF with semver tag
118
+ env = {"GITHUB_REF": "refs/tags/v3.1.4"}
119
+ adapter = lib.GitTagAdapter(env_vars=env, repo_root=".")
120
+ ctx = adapter.get_version_info()
121
+ self.assertEqual(ctx.source, "git_tag")
122
+ self.assertEqual(ctx.version, "3.1.4")
123
+
124
+ # Fail-closed: no tag resolvable — use tempdir without .git so both
125
+ # GITHUB_REF detection and `git describe` fail.
126
+ with tempfile.TemporaryDirectory() as tmp_empty:
127
+ adapter_bad = lib.GitTagAdapter(env_vars={}, repo_root=tmp_empty)
128
+ with self.assertRaises(lib.ReleaseTriggerError) as cm:
129
+ adapter_bad.get_version_info()
130
+ self.assertEqual(cm.exception.code, "RELEASE_TRIGGER_TAG_MISSING")
131
+
132
+
133
+ # --- T-005: Manual backward compatibility (AC-5) -----------------------------
134
+
135
+
136
+ class US0111ManualBackwardCompatTest(unittest.TestCase):
137
+ """test_us0111_manual_backward_compat_byte_identical (AC-5)."""
138
+
139
+ def test_us0111_manual_backward_compat_byte_identical(self) -> None:
140
+ lib = _load_lib()
141
+ # Manual adapter: byte-identical to pre-US-0111 /release behavior
142
+ ctx = lib.dispatch_to_adapter("manual", env_vars={}, current_version="1.0.0")
143
+ self.assertEqual(ctx.source, "manual")
144
+ self.assertEqual(ctx.version, "1.0.0")
145
+ self.assertIsNone(ctx.previous_version)
146
+ self.assertTrue(ctx.metadata.get("manual"))
147
+
148
+
149
+ # --- T-006: Version comparison logic (AC-6) ----------------------------------
150
+
151
+
152
+ class US0111CompareVersionsIntegrationTest(unittest.TestCase):
153
+ """test_us0111_compare_versions_from_trigger_integration (AC-6)."""
154
+
155
+ def test_us0111_compare_versions_from_trigger_integration(self) -> None:
156
+ lib = _load_lib()
157
+ trigger = lib.TriggerContext(
158
+ version="1.2.3",
159
+ previous_version="1.2.2",
160
+ source="github",
161
+ metadata={},
162
+ )
163
+ norm_current, norm_previous = lib.compare_versions_from_trigger(trigger)
164
+ self.assertEqual(norm_current, "1.2.3")
165
+ self.assertEqual(norm_previous, "1.2.2")
166
+
167
+ # Invalid version → fail-closed
168
+ trigger_bad = lib.TriggerContext(
169
+ version="invalid",
170
+ previous_version=None,
171
+ source="manual",
172
+ metadata={},
173
+ )
174
+ with self.assertRaises(lib.ReleaseTriggerError) as cm:
175
+ lib.compare_versions_from_trigger(trigger_bad)
176
+ self.assertEqual(cm.exception.code, "RELEASE_TRIGGER_COMPARE_VERSIONS_FAILED")
177
+
178
+
179
+ # --- T-007: Atomic promotion (AC-7) ------------------------------------------
180
+
181
+
182
+ class US0111AtomicPromotionTest(unittest.TestCase):
183
+ """test_us0111_atomic_promotion_temp_rename (AC-7)."""
184
+
185
+ def test_us0111_atomic_promotion_temp_rename(self) -> None:
186
+ lib = _load_lib()
187
+ with tempfile.TemporaryDirectory() as tmp:
188
+ target = os.path.join(tmp, "test.txt")
189
+ lib.atomic_write_file(target, "content")
190
+ self.assertTrue(os.path.exists(target))
191
+ with open(target, "r", encoding="utf-8") as f:
192
+ self.assertEqual(f.read(), "content")
193
+
194
+
195
+ # --- T-008: Per-version notes atomic write (AC-8) ----------------------------
196
+
197
+
198
+ class US0111PerVersionNotesTest(unittest.TestCase):
199
+ """test_us0111_per_version_notes_atomic_write (AC-8)."""
200
+
201
+ def test_us0111_per_version_notes_atomic_write(self) -> None:
202
+ lib = _load_lib()
203
+ with tempfile.TemporaryDirectory() as tmp:
204
+ repo = Path(tmp)
205
+ (repo / "handoffs" / "releases").mkdir(parents=True)
206
+ # Mock release_changelog_lib to avoid full dependency
207
+ sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
208
+ from release_changelog_lib import normalize_semver, version_fingerprint
209
+
210
+ norm = normalize_semver("1.0.0")
211
+ fp = version_fingerprint(norm, ["US-0111"])
212
+ target = repo / "handoffs" / "releases" / "1.0.0-release-notes.md"
213
+ content = f"# Release 1.0.0\n<!-- fingerprint: {fp} -->\n"
214
+ lib.atomic_write_file(str(target), content)
215
+ self.assertTrue(target.exists())
216
+ self.assertIn("Release 1.0.0", target.read_text(encoding="utf-8"))
217
+
218
+
219
+ # --- T-009: Sovereign loop integration (AC-9) --------------------------------
220
+
221
+
222
+ class US0111LedgerEventTest(unittest.TestCase):
223
+ """test_us0111_ledger_event_emit_shape (AC-9)."""
224
+
225
+ def test_us0111_ledger_event_emit_shape(self) -> None:
226
+ lib = _load_lib()
227
+ with tempfile.TemporaryDirectory() as tmp:
228
+ repo = Path(tmp)
229
+ (repo / "handoffs" / "release_events").mkdir(parents=True)
230
+ trigger = lib.TriggerContext(
231
+ version="1.0.0",
232
+ previous_version="0.9.0",
233
+ source="github",
234
+ metadata={"tag": "v1.0.0"},
235
+ )
236
+ # Mock ledger append to avoid schema validation
237
+ sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
238
+ import decision_ledger_lib as ledger_lib
239
+
240
+ original_append = ledger_lib.append_entry
241
+
242
+ def mock_append(*args, **kwargs):
243
+ return ledger_lib.AppendResult(
244
+ success=True,
245
+ reason_code="LEDGER_DISABLED",
246
+ reason_message="mock",
247
+ )
248
+
249
+ ledger_lib.append_entry = mock_append
250
+ try:
251
+ result = lib.emit_version_derivation_event(
252
+ trigger, "1.0.0", "0.9.0", str(repo), scratchpad={}
253
+ )
254
+ self.assertIn("event_path", result)
255
+ event_file = Path(result["event_path"])
256
+ self.assertTrue(event_file.exists())
257
+ event_data = json.loads(event_file.read_text(encoding="utf-8"))
258
+ self.assertEqual(event_data["semver"], "1.0.0")
259
+ self.assertEqual(event_data["previous_semver"], "0.9.0")
260
+ self.assertEqual(event_data["source"], "github")
261
+ finally:
262
+ ledger_lib.append_entry = original_append
263
+
264
+
265
+ # --- T-010: Fail-closed reason codes (AC-10) ---------------------------------
266
+
267
+
268
+ class US0111ReasonCodeInventoryTest(unittest.TestCase):
269
+ """test_us0111_reason_code_inventory_9_codes (AC-10)."""
270
+
271
+ def test_us0111_reason_code_inventory_9_codes(self) -> None:
272
+ lib = _load_lib()
273
+ # 9 reason codes in FAIL_CODES tuple
274
+ self.assertEqual(len(lib.FAIL_CODES), 9)
275
+ expected_codes = {
276
+ "RELEASE_TRIGGER_ADAPTER_FAILED",
277
+ "RELEASE_TRIGGER_TAG_MISSING",
278
+ "RELEASE_TRIGGER_PREVIOUS_MISSING",
279
+ "RELEASE_TRIGGER_PACKAGE_JSON_MISSING",
280
+ "RELEASE_TRIGGER_ATOMIC_PROMOTION_FAILED",
281
+ "RELEASE_TRIGGER_NOTES_WRITE_FAILED",
282
+ "RELEASE_TRIGGER_EVENT_EMIT_FAILED",
283
+ "RELEASE_TRIGGER_COMPARE_VERSIONS_FAILED",
284
+ "RELEASE_TRIGGER_SOURCE_INVALID",
285
+ }
286
+ self.assertEqual(set(lib.FAIL_CODES), expected_codes)
287
+
288
+ # Reason codes documented in reason_codes.md
289
+ root = _repo_root()
290
+ reason_codes_path = root / "docs" / "engineering" / "reason_codes.md"
291
+ text = reason_codes_path.read_text(encoding="utf-8")
292
+ self.assertIn("US-0111 — Release trigger adapter family", text)
293
+ self.assertIn("RELEASE_TRIGGER_* (9 codes)", text)
294
+ for code in expected_codes:
295
+ self.assertIn(code, text, f"missing {code} in reason_codes.md")
296
+
297
+
298
+ # --- T-010: Compose guard US-0100 (no API change) ----------------------------
299
+
300
+
301
+ class US0111US0100ComposeTest(unittest.TestCase):
302
+ """test_us0111_us0100_compose_no_derivation_semantics_change (AC-6 compose guard)."""
303
+
304
+ def test_us0111_us0100_compose_no_derivation_semantics_change(self) -> None:
305
+ # Verify release_changelog_lib APIs unchanged (US-0100 compose guard)
306
+ sys.path.insert(0, str(_repo_root() / "scripts"))
307
+ import release_changelog_lib
308
+ import inspect
309
+
310
+ # normalize_semver(raw) — existing API, unchanged
311
+ sig = inspect.signature(release_changelog_lib.normalize_semver)
312
+ self.assertEqual(len(sig.parameters), 1)
313
+
314
+ # promote_unreleased(semver, sprint_ids, repo_root, release_date) — unchanged
315
+ sig2 = inspect.signature(release_changelog_lib.promote_unreleased)
316
+ self.assertGreaterEqual(len(sig2.parameters), 3)
317
+
318
+ # build_version_doc(semver, sprint_ids, repo_root) — unchanged
319
+ sig3 = inspect.signature(release_changelog_lib.build_version_doc)
320
+ self.assertEqual(len(sig3.parameters), 3)
321
+
322
+ # version_fingerprint(semver, work_item_ids) — unchanged
323
+ sig4 = inspect.signature(release_changelog_lib.version_fingerprint)
324
+ self.assertEqual(len(sig4.parameters), 2)
325
+
326
+
327
+ # --- T-010: Compose guard US-0054 (no publish semantics change) --------------
328
+
329
+
330
+ class US0111US0054ComposeTest(unittest.TestCase):
331
+ """test_us0111_us0054_compose_no_publish_semantics_change (AC-5 compose guard)."""
332
+
333
+ def test_us0111_us0054_compose_no_publish_semantics_change(self) -> None:
334
+ # Manual adapter produces same TriggerContext as pre-US-0111 /release
335
+ lib = _load_lib()
336
+ ctx = lib.dispatch_to_adapter("manual", env_vars={}, current_version="1.0.0")
337
+ self.assertEqual(ctx.source, "manual")
338
+ self.assertEqual(ctx.version, "1.0.0")
339
+ self.assertIsNone(ctx.previous_version)
340
+ # No publish logic in adapter — US-0054 release-all.sh unchanged
341
+ self.assertNotIn("publish", ctx.metadata)
342
+
343
+
344
+ if __name__ == "__main__":
345
+ unittest.main()