its-magic 0.1.3-2 → 0.1.3-4

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.
@@ -0,0 +1,318 @@
1
+ """
2
+ Work-kind routing resolver (US-0118 / DEC-0118).
3
+
4
+ Resolves ``(delivery_mode, phase_plan, reason_code)`` from the merged
5
+ scratchpad + story signals using the **L8 precedence chain** (LOCKED):
6
+
7
+ 1. **``start-from=<phase>``** — always wins (intersects with whatever
8
+ plan is active per DEC-0052 §2.5; handled by the caller).
9
+ 2. **Explicit ``DELIVERY_MODE``** (US-0096 / DEC-0082) — argv
10
+ ``delivery-mode=`` > backlog row ``delivery_mode`` (when
11
+ ``AUTO_DELIVERY_ROUTING=backlog_then_scratchpad``) > scratchpad
12
+ ``DELIVERY_MODE``.
13
+ 3. **Explicit ``AUTO_PHASE_*``** (US-0070 / DEC-0052) —
14
+ ``AUTO_PHASE_PLAN`` / ``AUTO_PHASE_EXCLUDE`` / ``AUTO_PHASE_INCLUDE``
15
+ / ``AUTO_PHASE_PROFILE``.
16
+ 4. **``WORK_KIND_ROUTING``-derived ``recommended_delivery_mode``** — only
17
+ when ``WORK_KIND_ROUTING=1`` AND the backlog row carries ``work_kind``
18
+ AND higher-precedence keys are unset.
19
+ 5. **Current default lifecycle** — full DEC-0052 chain; ``standard``
20
+ delivery mode.
21
+
22
+ Zero-overhead-when-off (Q8 LOCKED): when ``WORK_KIND_ROUTING != "1"`` the
23
+ resolver early-returns ``(standard, full_plan, "WORK_KIND_ROUTING_OFF")``
24
+ without invoking :func:`classify_work_kind`. Existing backlog rows
25
+ without ``work_kind``/``recommended_delivery_mode`` route via current
26
+ ``DELIVERY_MODE``/``AUTO_PHASE_*`` precedence (no forced reclassification,
27
+ no schema-migration).
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ import os
33
+ import sys
34
+ from typing import List, Optional, Tuple
35
+
36
+ # Path bootstrap so the bare ``import work_kind_classify_lib`` resolves
37
+ # whether the script is run from the repo root or as a file path.
38
+ _SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
39
+ if _SCRIPT_DIR not in sys.path:
40
+ sys.path.insert(0, _SCRIPT_DIR)
41
+
42
+ import work_kind_classify_lib # noqa: E402
43
+ from work_kind_classify_lib import ( # noqa: E402
44
+ CODE_STANDARD_PHASE_PLAN,
45
+ WorkKind,
46
+ WorkKindClassification,
47
+ WORK_KIND_DELIVERY_MODE_CONFLICT,
48
+ WORK_KIND_ROUTING_OFF,
49
+ classify_work_kind,
50
+ )
51
+
52
+
53
+ # Reason code emitted by this resolver when both ``WORK_KIND_ROUTING=1``
54
+ # and explicit ``DELIVERY_MODE`` are set (the explicit value wins, but the
55
+ # conflict is surfaced for diagnostics).
56
+ WORK_KIND_ROUTING_CONFLICT_INFO = "WORK_KIND_DELIVERY_MODE_CONFLICT"
57
+
58
+ # Reason code emitted when the classifier-derived delivery mode is
59
+ # actually applied (info-only).
60
+ WORK_KIND_ROUTING_APPLIED = "WORK_KIND_ROUTING_APPLIED"
61
+
62
+ FULL_STANDARD_PLAN: List[str] = list(CODE_STANDARD_PHASE_PLAN)
63
+
64
+
65
+ def _is_set(value: Optional[str]) -> bool:
66
+ """Return True when ``value`` is a non-empty string after strip."""
67
+ return value is not None and bool(str(value).strip())
68
+
69
+
70
+ def _has_auto_phase_keys(scratchpad: dict) -> bool:
71
+ """Return True when any ``AUTO_PHASE_*`` key is non-empty."""
72
+ for key in (
73
+ "AUTO_PHASE_PLAN",
74
+ "AUTO_PHASE_EXCLUDE",
75
+ "AUTO_PHASE_INCLUDE",
76
+ "AUTO_PHASE_PROFILE",
77
+ ):
78
+ if _is_set(scratchpad.get(key)):
79
+ return True
80
+ return False
81
+
82
+
83
+ def resolve_delivery_mode_with_work_kind(
84
+ scratchpad: dict,
85
+ story_prose: str,
86
+ ac_set: List[str],
87
+ touched_file_hints: List[str],
88
+ component_scope: Optional[str] = None,
89
+ *,
90
+ backlog_work_kind: Optional[str] = None,
91
+ backlog_recommended_delivery_mode: Optional[str] = None,
92
+ has_companion_dec: bool = False,
93
+ ) -> Tuple[str, List[str], Optional[str]]:
94
+ """
95
+ Resolve ``(delivery_mode, phase_plan, reason_code)`` from scratchpad +
96
+ story signals per the L8 precedence chain.
97
+
98
+ Parameters
99
+ ----------
100
+ scratchpad:
101
+ Merged scratchpad dict (Model B: local > baseline > example).
102
+ story_prose, ac_set, touched_file_hints, component_scope:
103
+ Forwarded to :func:`classify_work_kind` when routing is enabled.
104
+ backlog_work_kind, backlog_recommended_delivery_mode:
105
+ Optional backlog-row fields set at intake (AC-4). When present
106
+ and ``WORK_KIND_ROUTING=1`` the classifier is *not* re-run — the
107
+ backlog value is trusted as the operator-accepted recommendation.
108
+ has_companion_dec:
109
+ Forwarded to the classifier (affects ``mini`` → ``mega_quick``
110
+ eligibility).
111
+
112
+ Returns
113
+ -------
114
+ Tuple ``(delivery_mode, phase_plan, reason_code)`` where
115
+ ``reason_code`` is ``None`` on a clean explicit resolution, an
116
+ info-code (``WORK_KIND_ROUTING_OFF`` / ``WORK_KIND_ROUTING_APPLIED``)
117
+ on a derived resolution, or a fail-closed ``WORK_KIND_*`` code on
118
+ conflict.
119
+ """
120
+ routing_flag = (scratchpad.get("WORK_KIND_ROUTING") or "0").strip()
121
+ if routing_flag != "1":
122
+ # Q8 LOCKED zero-overhead-when-off — early return WITHOUT
123
+ # invoking the classifier. Byte-identical to pre-US-0118 behavior.
124
+ return ("standard", list(FULL_STANDARD_PLAN), WORK_KIND_ROUTING_OFF)
125
+
126
+ explicit_delivery_mode = (scratchpad.get("DELIVERY_MODE") or "").strip()
127
+ explicit_auto_phase = _has_auto_phase_keys(scratchpad)
128
+
129
+ # L8 precedence: explicit DELIVERY_MODE wins (2) > AUTO_PHASE_* (3) >
130
+ # WORK_KIND_ROUTING-derived (4) > default (5).
131
+ if _is_set(explicit_delivery_mode):
132
+ # If the classifier would have recommended a different mode, emit
133
+ # the conflict reason code (info for diagnostics; explicit still
134
+ # wins per L8). The classifier is NOT run when the backlog row
135
+ # already carries a recommendation — we compare against that.
136
+ if (
137
+ _is_set(backlog_recommended_delivery_mode)
138
+ and backlog_recommended_delivery_mode != explicit_delivery_mode
139
+ ):
140
+ return (
141
+ explicit_delivery_mode,
142
+ _phase_plan_for_delivery_mode(explicit_delivery_mode),
143
+ WORK_KIND_ROUTING_CONFLICT_INFO,
144
+ )
145
+ return (
146
+ explicit_delivery_mode,
147
+ _phase_plan_for_delivery_mode(explicit_delivery_mode),
148
+ None,
149
+ )
150
+
151
+ if explicit_auto_phase:
152
+ # Explicit AUTO_PHASE_* wins over work-kind-derived (L8 #3 > #4).
153
+ # The caller materializes the actual plan via DEC-0052; we return
154
+ # the standard delivery mode + full plan as the base.
155
+ return ("standard", list(FULL_STANDARD_PLAN), None)
156
+
157
+ # WORK_KIND_ROUTING-derived path (L8 #4). Use the backlog row's
158
+ # accepted recommendation when present; otherwise classify now.
159
+ if _is_set(backlog_recommended_delivery_mode) and _is_set(backlog_work_kind):
160
+ delivery_mode = backlog_recommended_delivery_mode
161
+ phase_plan = _phase_plan_for_delivery_mode(delivery_mode)
162
+ return (delivery_mode, phase_plan, WORK_KIND_ROUTING_APPLIED)
163
+
164
+ try:
165
+ classification: WorkKindClassification = classify_work_kind(
166
+ story_prose=story_prose,
167
+ acceptance_criteria=ac_set,
168
+ touched_file_hints=touched_file_hints,
169
+ component_scope=component_scope,
170
+ has_companion_dec=has_companion_dec,
171
+ )
172
+ except Exception:
173
+ # WORK_KIND_CLASSIFY_FAILED — fail closed to standard lifecycle.
174
+ return ("standard", list(FULL_STANDARD_PLAN), "WORK_KIND_CLASSIFY_FAILED")
175
+
176
+ if not classification.recommended_phase_plan:
177
+ return (
178
+ "standard",
179
+ list(FULL_STANDARD_PLAN),
180
+ "WORK_KIND_PLAN_COVERAGE_MISSING",
181
+ )
182
+
183
+ return (
184
+ classification.recommended_delivery_mode,
185
+ list(classification.recommended_phase_plan),
186
+ WORK_KIND_ROUTING_APPLIED,
187
+ )
188
+
189
+
190
+ def _phase_plan_for_delivery_mode(delivery_mode: str) -> List[str]:
191
+ """Map a delivery mode to its canonical phase plan (DEC-0082 table)."""
192
+ if delivery_mode == "ultra_lean":
193
+ return ["spec", "plan", "build+verify", "ship"]
194
+ if delivery_mode == "mega_quick":
195
+ return ["quick"]
196
+ # standard (or unknown — fall back to standard full lifecycle).
197
+ return list(FULL_STANDARD_PLAN)
198
+
199
+
200
+ # ---------------------------------------------------------------------------
201
+ # Self-test (AC-12)
202
+ # ---------------------------------------------------------------------------
203
+
204
+
205
+ def self_test() -> int:
206
+ """Built-in self-test. Exits 0 on success, 1 on failure."""
207
+ failures: List[str] = []
208
+
209
+ # WORK_KIND_ROUTING=0 → zero overhead, no classifier call.
210
+ mode, plan, reason = resolve_delivery_mode_with_work_kind(
211
+ scratchpad={"WORK_KIND_ROUTING": "0"},
212
+ story_prose="anything",
213
+ ac_set=["AC-1"],
214
+ touched_file_hints=["docs/foo.md"],
215
+ )
216
+ if mode != "standard":
217
+ failures.append(f"routing-off mode: expected standard, got {mode}")
218
+ if reason != WORK_KIND_ROUTING_OFF:
219
+ failures.append(f"routing-off reason: expected {WORK_KIND_ROUTING_OFF}, got {reason}")
220
+ if plan != FULL_STANDARD_PLAN:
221
+ failures.append("routing-off plan: expected full standard plan")
222
+
223
+ # Explicit DELIVERY_MODE wins over WORK_KIND_ROUTING (L8 #2 > #4).
224
+ mode, plan, reason = resolve_delivery_mode_with_work_kind(
225
+ scratchpad={"WORK_KIND_ROUTING": "1", "DELIVERY_MODE": "ultra_lean"},
226
+ story_prose="code feature",
227
+ ac_set=["AC-1", "AC-2", "AC-3", "AC-4"],
228
+ touched_file_hints=["package.json", "src/index.ts"],
229
+ )
230
+ if mode != "ultra_lean":
231
+ failures.append(f"explicit-delivery mode: expected ultra_lean, got {mode}")
232
+ if reason is not None:
233
+ failures.append(f"explicit-delivery reason: expected None, got {reason}")
234
+
235
+ # Conflict — backlog recommends standard, explicit DELIVERY_MODE=ultra_lean.
236
+ mode, plan, reason = resolve_delivery_mode_with_work_kind(
237
+ scratchpad={"WORK_KIND_ROUTING": "1", "DELIVERY_MODE": "ultra_lean"},
238
+ story_prose="code feature",
239
+ ac_set=["AC-1"],
240
+ touched_file_hints=["package.json"],
241
+ backlog_work_kind="code",
242
+ backlog_recommended_delivery_mode="standard",
243
+ )
244
+ if mode != "ultra_lean":
245
+ failures.append(f"conflict mode: expected ultra_lean (explicit wins), got {mode}")
246
+ if reason != WORK_KIND_ROUTING_CONFLICT_INFO:
247
+ failures.append(
248
+ f"conflict reason: expected {WORK_KIND_ROUTING_CONFLICT_INFO}, got {reason}"
249
+ )
250
+
251
+ # AUTO_PHASE_* wins over WORK_KIND_ROUTING (L8 #3 > #4).
252
+ mode, plan, reason = resolve_delivery_mode_with_work_kind(
253
+ scratchpad={"WORK_KIND_ROUTING": "1", "AUTO_PHASE_PLAN": '["spec","plan"]'},
254
+ story_prose="code feature",
255
+ ac_set=["AC-1"],
256
+ touched_file_hints=["package.json"],
257
+ )
258
+ if mode != "standard":
259
+ failures.append(f"auto-phase mode: expected standard, got {mode}")
260
+ if reason is not None:
261
+ failures.append(f"auto-phase reason: expected None, got {reason}")
262
+
263
+ # WORK_KIND_ROUTING=1 + doc story → DOC route (ultra_lean + lean plan).
264
+ mode, plan, reason = resolve_delivery_mode_with_work_kind(
265
+ scratchpad={"WORK_KIND_ROUTING": "1"},
266
+ story_prose="README update",
267
+ ac_set=["AC-1"],
268
+ touched_file_hints=["docs/engineering/runbook.md"],
269
+ )
270
+ if mode != "ultra_lean":
271
+ failures.append(f"doc-derived mode: expected ultra_lean, got {mode}")
272
+ if plan != ["intake", "execute", "release"]:
273
+ failures.append(f"doc-derived plan: expected lean plan, got {plan}")
274
+ if reason != WORK_KIND_ROUTING_APPLIED:
275
+ failures.append(
276
+ f"doc-derived reason: expected {WORK_KIND_ROUTING_APPLIED}, got {reason}"
277
+ )
278
+
279
+ # Backlog-row accepted recommendation is trusted (no re-classify).
280
+ mode, plan, reason = resolve_delivery_mode_with_work_kind(
281
+ scratchpad={"WORK_KIND_ROUTING": "1"},
282
+ story_prose="ignored",
283
+ ac_set=["AC-1"],
284
+ touched_file_hints=["package.json"],
285
+ backlog_work_kind="doc",
286
+ backlog_recommended_delivery_mode="ultra_lean",
287
+ )
288
+ if mode != "ultra_lean":
289
+ failures.append(f"backlog-accept mode: expected ultra_lean, got {mode}")
290
+ if reason != WORK_KIND_ROUTING_APPLIED:
291
+ failures.append(
292
+ f"backlog-accept reason: expected {WORK_KIND_ROUTING_APPLIED}, got {reason}"
293
+ )
294
+
295
+ if failures:
296
+ for f in failures:
297
+ print(f"[WORK_KIND_ROUTING_SELF_TEST_FAIL] {f}", file=sys.stderr)
298
+ return 1
299
+ print("[WORK_KIND_ROUTING_SELF_TEST_OK]")
300
+ return 0
301
+
302
+
303
+ def main(argv: Optional[List[str]] = None) -> int:
304
+ import argparse
305
+
306
+ p = argparse.ArgumentParser(
307
+ description="Work-kind routing resolver (US-0118 / DEC-0118)."
308
+ )
309
+ p.add_argument("--self-test", action="store_true", help="Run built-in self-test.")
310
+ args = p.parse_args(argv)
311
+ if args.self_test:
312
+ return self_test()
313
+ p.print_help()
314
+ return 0
315
+
316
+
317
+ if __name__ == "__main__":
318
+ sys.exit(main())
@@ -0,0 +1,124 @@
1
+ """
2
+ BUG-0013: Scratchpad example parity tests.
3
+
4
+ Verifies template/.cursor/scratchpad.local.example.md stays in sync
5
+ with canonical .cursor/scratchpad.md, excluding project-local overrides.
6
+ """
7
+
8
+ import re
9
+ from pathlib import Path
10
+
11
+ import pytest
12
+
13
+ CANONICAL = Path(__file__).parent.parent / ".cursor" / "scratchpad.md"
14
+ TEMPLATE = (
15
+ Path(__file__).parent.parent
16
+ / "template"
17
+ / ".cursor"
18
+ / "scratchpad.local.example.md"
19
+ )
20
+
21
+
22
+ def extract_keys(text):
23
+ """Extract KEY=value keys from scratchpad text."""
24
+ pattern = re.compile(r"^([A-Z_][A-Z0-9_]*)=", re.MULTILINE)
25
+ return set(pattern.findall(text))
26
+
27
+
28
+ def extract_sections(text):
29
+ """Extract # Section headers from scratchpad text."""
30
+ pattern = re.compile(r"^(#+\s+.+)$", re.MULTILINE)
31
+ return [m.strip() for m in pattern.findall(text)]
32
+
33
+
34
+ # ---------------------------------------------------------------------------
35
+ # Fixtures
36
+ # ---------------------------------------------------------------------------
37
+
38
+ @pytest.fixture
39
+ def canonical_text():
40
+ return CANONICAL.read_text(encoding="utf-8")
41
+
42
+
43
+ @pytest.fixture
44
+ def template_text():
45
+ return TEMPLATE.read_text(encoding="utf-8")
46
+
47
+
48
+ # ---------------------------------------------------------------------------
49
+ # Test markers
50
+ # ---------------------------------------------------------------------------
51
+
52
+ def test_bug0013_parity_check(canonical_text, template_text):
53
+ """AC-1: All canonical sections keys present in template example."""
54
+ # 1) Section headers should be present
55
+ canonical_sections = extract_sections(canonical_text)
56
+ template_sections = extract_sections(template_text)
57
+ missing_sections = set(canonical_sections) - set(template_sections)
58
+
59
+ # 2) Keys (canonical keys ⊆ template example keys)
60
+ canonical_keys = extract_keys(canonical_text)
61
+ template_keys = extract_keys(template_text)
62
+ missing_keys = canonical_keys - template_keys
63
+
64
+ msg = []
65
+ if missing_sections:
66
+ msg.append(
67
+ f"Template missing {len(missing_sections)} canonical section(s):\n"
68
+ + "\n".join(f" - {s}" for s in sorted(missing_sections))
69
+ )
70
+ if missing_keys:
71
+ msg.append(
72
+ f"Template missing {len(missing_keys)} canonical key(s):\n"
73
+ + "\n".join(f" - {k}" for k in sorted(missing_keys))
74
+ )
75
+
76
+ assert not msg, "\n".join(msg)
77
+
78
+
79
+ def test_bug0013_header_preserved(template_text):
80
+ """AC-1: Example-only header comment (L1-L5) preserved intact."""
81
+ lines = template_text.splitlines()[:5]
82
+
83
+ expected_patterns = [
84
+ r"^#", # L1: comment line
85
+ r"its-magic", # L2: contains "its-magic"
86
+ r"DEC-", # L3: contains DEC reference
87
+ r"Copy this file",# L4: contains copy instruction
88
+ r"local>", # L5: contains "local>" reference
89
+ ]
90
+ assert len(lines) >= 5, (
91
+ f"Template header must have at least 5 lines; only {len(lines)} found"
92
+ )
93
+
94
+ for idx, pattern in enumerate(expected_patterns, start=1):
95
+ assert pattern in lines[idx - 1] or re.match(pattern, lines[idx - 1]), (
96
+ f"Header line {idx} missing expected pattern '{pattern}'.\n"
97
+ f"Found: {lines[idx - 1]}"
98
+ )
99
+
100
+
101
+ def test_bug0013_local_overrides_preserved(template_text):
102
+ """AC-1: Project-local overrides NOT leaked into template example."""
103
+ # Patterns indicating project-local overrides that should NOT be in template
104
+ forbidden = [
105
+ r"^MODEL_[A-Z]+=", # Direct per-phase model overrides (e.g., MODEL_EXECUTE=...)
106
+ r"^#MODEL_", # Commented per-phase slug overrides
107
+ r"^#MODEL_RESOLVE=local_catalog", # Local catalog override (commented)
108
+ r"^#MODEL_RESOLVE=role_catalog", # Role catalog override (commented)
109
+ ]
110
+
111
+ violations = []
112
+ for pattern in forbidden:
113
+ matches = re.findall(pattern, template_text, re.MULTILINE)
114
+ if matches:
115
+ violations.append((pattern, matches[:3])) # up to 3 examples
116
+
117
+ assert not violations, (
118
+ f"Template contains {len(violations)} project-local override pattern(s) "
119
+ "that should NOT be present:\n"
120
+ + "\n".join(
121
+ f" - {pat} (examples: {ex})" for pat, ex in violations
122
+ )
123
+ + "\nProject-local overrides belong in consumer's .cursor/scratchpad.local.md"
124
+ )