its-magic 0.1.3-3 → 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.
- package/package.json +1 -1
- package/scripts/check_intake_template_parity.py +71 -0
- package/template/.cursor/commands/auto.md +54 -4
- package/template/.cursor/commands/closure.md +184 -0
- package/template/.cursor/commands/intake.md +26 -0
- package/template/.cursor/commands/release.md +17 -26
- package/template/.cursor/scratchpad.local.example.md +168 -70
- package/template/docs/engineering/autonomy-stop-matrix.md +101 -0
- package/template/docs/engineering/context/installer-owned-paths.manifest +12 -0
- package/template/docs/engineering/runbook.md +3869 -3576
- package/template/its_magic/README.md +3981 -6
- package/template/scripts/autonomy_preset_lib.py +224 -0
- package/template/scripts/check_intake_template_parity.py +71 -0
- package/template/scripts/validate_autonomy_stop_matrix.py +486 -0
- package/template/scripts/validate_closure_verification.py +304 -0
- package/template/scripts/work_kind_classify_lib.py +543 -0
- package/template/scripts/work_kind_routing_lib.py +318 -0
- package/template/tests/__pycache__/scratchpad_example_parity_test.cpython-312-pytest-9.0.2.pyc +0 -0
- package/template/tests/us0118_contract_test.py +330 -0
- package/template/tests/us0119_autonomy_preset_test.py +168 -0
|
@@ -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())
|
package/template/tests/__pycache__/scratchpad_example_parity_test.cpython-312-pytest-9.0.2.pyc
ADDED
|
Binary file
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"""
|
|
2
|
+
US-0118 contract tests (R-0106 Q4 LOCKED — 12 `test_us0118_*` markers).
|
|
3
|
+
|
|
4
|
+
Covers:
|
|
5
|
+
- Each work-kind classification (DOC / MINI / CODE).
|
|
6
|
+
- Each recommended phase plan.
|
|
7
|
+
- Default-off zero-overhead behavior.
|
|
8
|
+
- L8 precedence vs explicit DELIVERY_MODE / AUTO_PHASE_*.
|
|
9
|
+
- Operator override path (backlog-row accepted recommendation).
|
|
10
|
+
- Each fail-closed reason code in the WORK_KIND_* family.
|
|
11
|
+
- --explain rule_trace emission.
|
|
12
|
+
- classify_touched_files reuse boundary (Q9 LOCKED import contract).
|
|
13
|
+
|
|
14
|
+
Pure stdlib pytest — no external deps. Active + `template/` parity.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
import pytest
|
|
24
|
+
|
|
25
|
+
_REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
26
|
+
_SCRIPTS_DIR = _REPO_ROOT / "scripts"
|
|
27
|
+
if str(_SCRIPTS_DIR) not in sys.path:
|
|
28
|
+
sys.path.insert(0, str(_SCRIPTS_DIR))
|
|
29
|
+
|
|
30
|
+
import work_kind_classify_lib as wkc # noqa: E402
|
|
31
|
+
import work_kind_routing_lib as wkr # noqa: E402
|
|
32
|
+
from work_kind_classify_lib import ( # noqa: E402
|
|
33
|
+
WorkKind,
|
|
34
|
+
classify_work_kind,
|
|
35
|
+
)
|
|
36
|
+
from work_kind_routing_lib import resolve_delivery_mode_with_work_kind # noqa: E402
|
|
37
|
+
|
|
38
|
+
# Also import dev_environment_lib to verify the reuse boundary.
|
|
39
|
+
import dev_environment_lib # noqa: E402
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
# AC-1, AC-2 — work-kind classification + phase plans
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_us0118_doc_kind_routes_to_lean_plan():
|
|
48
|
+
"""DOC kind → `[intake, execute, release]` (skip discovery/research/
|
|
49
|
+
architecture/sprint-plan/plan-verify/qa/verify-work)."""
|
|
50
|
+
result = classify_work_kind(
|
|
51
|
+
story_prose="Update README",
|
|
52
|
+
acceptance_criteria=["AC-1 README updated"],
|
|
53
|
+
touched_file_hints=["docs/engineering/runbook.md", "docs/product/backlog.md"],
|
|
54
|
+
)
|
|
55
|
+
assert result.work_kind is WorkKind.DOC
|
|
56
|
+
assert result.recommended_phase_plan == ["intake", "execute", "release"]
|
|
57
|
+
assert result.recommended_delivery_mode == "ultra_lean"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_us0118_mini_kind_routes_to_ultra_lean():
|
|
61
|
+
"""MINI kind → ultra_lean when mega_quick ineligible (AC count > 3)."""
|
|
62
|
+
result = classify_work_kind(
|
|
63
|
+
story_prose="Tweak nginx config",
|
|
64
|
+
acceptance_criteria=["AC-1", "AC-2", "AC-3", "AC-4"], # 4 > 3
|
|
65
|
+
touched_file_hints=[".env.example"],
|
|
66
|
+
component_scope="web",
|
|
67
|
+
)
|
|
68
|
+
assert result.work_kind is WorkKind.MINI
|
|
69
|
+
assert result.recommended_delivery_mode == "ultra_lean"
|
|
70
|
+
assert result.recommended_phase_plan == ["spec", "plan", "build+verify", "ship"]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_us0118_mini_kind_routes_to_mega_quick_when_eligible():
|
|
74
|
+
"""MINI kind + US-0096 eligibility (≤3 ACs, single component, no DEC)
|
|
75
|
+
→ mega_quick."""
|
|
76
|
+
result = classify_work_kind(
|
|
77
|
+
story_prose="Tiny fix",
|
|
78
|
+
acceptance_criteria=["AC-1", "AC-2"], # 2 ≤ 3
|
|
79
|
+
touched_file_hints=[".env.example"],
|
|
80
|
+
component_scope="web",
|
|
81
|
+
has_companion_dec=False,
|
|
82
|
+
)
|
|
83
|
+
assert result.work_kind is WorkKind.MINI
|
|
84
|
+
assert result.recommended_delivery_mode == "mega_quick"
|
|
85
|
+
assert result.recommended_phase_plan == ["quick"]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_us0118_code_kind_routes_to_standard():
|
|
89
|
+
"""CODE kind (tier A — package.json, or any non-skip source path) →
|
|
90
|
+
standard full lifecycle."""
|
|
91
|
+
result = classify_work_kind(
|
|
92
|
+
story_prose="New feature",
|
|
93
|
+
acceptance_criteria=["AC-1", "AC-2", "AC-3", "AC-4"],
|
|
94
|
+
touched_file_hints=["package.json", "src/index.ts"],
|
|
95
|
+
)
|
|
96
|
+
assert result.work_kind is WorkKind.CODE
|
|
97
|
+
assert result.recommended_delivery_mode == "standard"
|
|
98
|
+
assert result.recommended_phase_plan == [
|
|
99
|
+
"intake",
|
|
100
|
+
"discovery",
|
|
101
|
+
"research",
|
|
102
|
+
"architecture",
|
|
103
|
+
"sprint-plan",
|
|
104
|
+
"plan-verify",
|
|
105
|
+
"execute",
|
|
106
|
+
"qa",
|
|
107
|
+
"verify-work",
|
|
108
|
+
"release",
|
|
109
|
+
"refresh-context",
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# ---------------------------------------------------------------------------
|
|
114
|
+
# AC-6 — L8 precedence chain
|
|
115
|
+
# ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_us0118_explicit_delivery_mode_wins_over_work_kind():
|
|
119
|
+
"""L8 precedence: explicit DELIVERY_MODE wins over WORK_KIND_ROUTING-
|
|
120
|
+
derived recommendation even when the story is CODE."""
|
|
121
|
+
mode, plan, reason = resolve_delivery_mode_with_work_kind(
|
|
122
|
+
scratchpad={"WORK_KIND_ROUTING": "1", "DELIVERY_MODE": "ultra_lean"},
|
|
123
|
+
story_prose="code feature",
|
|
124
|
+
ac_set=["AC-1", "AC-2", "AC-3", "AC-4"],
|
|
125
|
+
touched_file_hints=["package.json", "src/index.ts"],
|
|
126
|
+
)
|
|
127
|
+
assert mode == "ultra_lean"
|
|
128
|
+
# Explicit resolution → reason is None (no conflict because no
|
|
129
|
+
# backlog recommendation was supplied to compare against).
|
|
130
|
+
assert reason is None
|
|
131
|
+
assert plan == ["spec", "plan", "build+verify", "ship"]
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def test_us0118_auto_phase_wins_over_work_kind():
|
|
135
|
+
"""L8 precedence: explicit AUTO_PHASE_* wins over WORK_KIND_ROUTING-
|
|
136
|
+
derived recommendation."""
|
|
137
|
+
mode, plan, reason = resolve_delivery_mode_with_work_kind(
|
|
138
|
+
scratchpad={
|
|
139
|
+
"WORK_KIND_ROUTING": "1",
|
|
140
|
+
"AUTO_PHASE_PLAN": '["spec","plan"]',
|
|
141
|
+
},
|
|
142
|
+
story_prose="code feature",
|
|
143
|
+
ac_set=["AC-1"],
|
|
144
|
+
touched_file_hints=["package.json"],
|
|
145
|
+
)
|
|
146
|
+
assert mode == "standard"
|
|
147
|
+
# Explicit AUTO_PHASE_* resolution → reason is None.
|
|
148
|
+
assert reason is None
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
# ---------------------------------------------------------------------------
|
|
152
|
+
# AC-3, AC-8 — default-off zero-overhead + backward compat
|
|
153
|
+
# ---------------------------------------------------------------------------
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def test_us0118_routing_off_is_noop():
|
|
157
|
+
"""WORK_KIND_ROUTING=0 → zero overhead: returns standard + full plan +
|
|
158
|
+
WORK_KIND_ROUTING_OFF; classifier NOT invoked."""
|
|
159
|
+
mode, plan, reason = resolve_delivery_mode_with_work_kind(
|
|
160
|
+
scratchpad={"WORK_KIND_ROUTING": "0"},
|
|
161
|
+
story_prose="anything",
|
|
162
|
+
ac_set=["AC-1"],
|
|
163
|
+
touched_file_hints=["docs/foo.md"],
|
|
164
|
+
)
|
|
165
|
+
assert mode == "standard"
|
|
166
|
+
assert reason == wkc.WORK_KIND_ROUTING_OFF
|
|
167
|
+
assert plan == wkr.FULL_STANDARD_PLAN
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def test_us0118_default_off_zero_overhead():
|
|
171
|
+
"""WORK_KIND_ROUTING=0 behavior byte-identical to pre-US-0118
|
|
172
|
+
(standard delivery mode + full canonical phase plan)."""
|
|
173
|
+
# Absent key → treated as "0" (default-off).
|
|
174
|
+
mode1, plan1, reason1 = resolve_delivery_mode_with_work_kind(
|
|
175
|
+
scratchpad={},
|
|
176
|
+
story_prose="anything",
|
|
177
|
+
ac_set=["AC-1"],
|
|
178
|
+
touched_file_hints=["docs/foo.md"],
|
|
179
|
+
)
|
|
180
|
+
mode2, plan2, reason2 = resolve_delivery_mode_with_work_kind(
|
|
181
|
+
scratchpad={"WORK_KIND_ROUTING": "0"},
|
|
182
|
+
story_prose="different",
|
|
183
|
+
ac_set=["AC-99"],
|
|
184
|
+
touched_file_hints=["src/x.ts"],
|
|
185
|
+
)
|
|
186
|
+
assert (mode1, plan1, reason1) == (mode2, plan2, reason2)
|
|
187
|
+
assert mode1 == "standard"
|
|
188
|
+
assert reason1 == wkc.WORK_KIND_ROUTING_OFF
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# ---------------------------------------------------------------------------
|
|
192
|
+
# AC-8 — reuse boundary (Q9 LOCKED import contract)
|
|
193
|
+
# ---------------------------------------------------------------------------
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def test_us0118_classify_touched_files_reuse():
|
|
197
|
+
"""classify_touched_files imported from dev_environment_lib — NOT
|
|
198
|
+
reimplemented in work_kind_classify_lib (Q9 LOCKED)."""
|
|
199
|
+
# The classifier module must reference the same function object.
|
|
200
|
+
assert wkc.classify_touched_files is dev_environment_lib.classify_touched_files
|
|
201
|
+
assert wkc.TIER_C_SKIP_PREFIXES is dev_environment_lib.TIER_C_SKIP_PREFIXES
|
|
202
|
+
# And the classifier produces tier-consistent results.
|
|
203
|
+
assert wkc.classify_touched_files(["package.json"]) == "A"
|
|
204
|
+
assert wkc.classify_touched_files([".env.example"]) == "B"
|
|
205
|
+
assert wkc.classify_touched_files(["docs/foo.md"]) is None
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
# ---------------------------------------------------------------------------
|
|
209
|
+
# AC-5, AC-9 — intake evidence schema extension
|
|
210
|
+
# ---------------------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def test_us0118_intake_evidence_records_work_kind():
|
|
214
|
+
"""The classifier output carries the intake-evidence schema extension
|
|
215
|
+
fields (work_kind, recommended_delivery_mode, work_kind_operator_decision)
|
|
216
|
+
so /intake step 5 can persist them directly."""
|
|
217
|
+
result = classify_work_kind(
|
|
218
|
+
story_prose="README update",
|
|
219
|
+
acceptance_criteria=["AC-1"],
|
|
220
|
+
touched_file_hints=["docs/foo.md"],
|
|
221
|
+
)
|
|
222
|
+
out = result.as_dict()
|
|
223
|
+
# work_kind is a string in the serialized form.
|
|
224
|
+
assert out["work_kind"] == "doc"
|
|
225
|
+
assert out["recommended_delivery_mode"] in ("standard", "ultra_lean", "mega_quick")
|
|
226
|
+
# work_kind_operator_decision defaults to None until the operator
|
|
227
|
+
# accept/override gate records a value.
|
|
228
|
+
assert "work_kind_operator_decision" in out
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
# ---------------------------------------------------------------------------
|
|
232
|
+
# AC-7 — fail-closed reason codes (R-0106 Q2 LOCKED)
|
|
233
|
+
# ---------------------------------------------------------------------------
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def test_us0118_reason_codes_preserved():
|
|
237
|
+
"""All WORK_KIND_* reason codes are emitted by the appropriate failure
|
|
238
|
+
modes (R-0106 Q2 LOCKED)."""
|
|
239
|
+
# WORK_KIND_ROUTING_OFF — info-only when flag off.
|
|
240
|
+
_, _, reason = resolve_delivery_mode_with_work_kind(
|
|
241
|
+
scratchpad={"WORK_KIND_ROUTING": "0"},
|
|
242
|
+
story_prose="x",
|
|
243
|
+
ac_set=["AC-1"],
|
|
244
|
+
touched_file_hints=["docs/foo.md"],
|
|
245
|
+
)
|
|
246
|
+
assert reason == wkc.WORK_KIND_ROUTING_OFF
|
|
247
|
+
|
|
248
|
+
# WORK_KIND_DELIVERY_MODE_CONFLICT — explicit DELIVERY_MODE conflicts
|
|
249
|
+
# with backlog-row recommendation.
|
|
250
|
+
_, _, reason = resolve_delivery_mode_with_work_kind(
|
|
251
|
+
scratchpad={"WORK_KIND_ROUTING": "1", "DELIVERY_MODE": "ultra_lean"},
|
|
252
|
+
story_prose="x",
|
|
253
|
+
ac_set=["AC-1"],
|
|
254
|
+
touched_file_hints=["package.json"],
|
|
255
|
+
backlog_work_kind="code",
|
|
256
|
+
backlog_recommended_delivery_mode="standard",
|
|
257
|
+
)
|
|
258
|
+
assert reason == wkc.WORK_KIND_DELIVERY_MODE_CONFLICT
|
|
259
|
+
|
|
260
|
+
# WORK_KIND_CLASSIFY_FAILED — classifier raises → fail-closed standard.
|
|
261
|
+
_, _, reason = resolve_delivery_mode_with_work_kind(
|
|
262
|
+
scratchpad={"WORK_KIND_ROUTING": "1"},
|
|
263
|
+
story_prose="x",
|
|
264
|
+
ac_set=["AC-1"],
|
|
265
|
+
touched_file_hints=None, # type: ignore[arg-type] # provokes TypeError
|
|
266
|
+
)
|
|
267
|
+
assert reason == "WORK_KIND_CLASSIFY_FAILED"
|
|
268
|
+
|
|
269
|
+
# All reason codes are present in the WORK_KIND_REASON_CODES tuple.
|
|
270
|
+
expected = {
|
|
271
|
+
"WORK_KIND_ROUTING_OFF",
|
|
272
|
+
"WORK_KIND_DELIVERY_MODE_CONFLICT",
|
|
273
|
+
"WORK_KIND_CLASSIFY_FAILED",
|
|
274
|
+
"WORK_KIND_UNKNOWN_ROUTE",
|
|
275
|
+
"WORK_KIND_PLAN_COVERAGE_MISSING",
|
|
276
|
+
"WORK_KIND_TIE_BREAK_APPLIED",
|
|
277
|
+
}
|
|
278
|
+
assert set(wkc.WORK_KIND_REASON_CODES) == expected
|
|
279
|
+
# Every reason code has remediation guidance.
|
|
280
|
+
for code in wkc.WORK_KIND_REASON_CODES:
|
|
281
|
+
assert code in wkc.REASON_CODE_REMEDIATION
|
|
282
|
+
assert wkc.REASON_CODE_REMEDIATION[code]
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
# ---------------------------------------------------------------------------
|
|
286
|
+
# AC-1 — --explain flag emits rule_trace (Q3 LOCKED)
|
|
287
|
+
# ---------------------------------------------------------------------------
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def test_us0118_explain_emits_rule_trace():
|
|
291
|
+
"""--explain flag (explain=True) emits a non-empty rule_trace list of
|
|
292
|
+
(rule_id, matched_signal, contribution) tuples."""
|
|
293
|
+
result = classify_work_kind(
|
|
294
|
+
story_prose="code feature",
|
|
295
|
+
acceptance_criteria=["AC-1"],
|
|
296
|
+
touched_file_hints=["package.json"],
|
|
297
|
+
explain=True,
|
|
298
|
+
)
|
|
299
|
+
assert result.rule_trace
|
|
300
|
+
for entry in result.rule_trace:
|
|
301
|
+
assert isinstance(entry, tuple)
|
|
302
|
+
assert len(entry) == 3
|
|
303
|
+
rule_id, matched_signal, contribution = entry
|
|
304
|
+
assert isinstance(rule_id, str) and rule_id
|
|
305
|
+
assert isinstance(matched_signal, str) and matched_signal
|
|
306
|
+
assert isinstance(contribution, str) and contribution
|
|
307
|
+
|
|
308
|
+
# Without explain, rule_trace is empty.
|
|
309
|
+
no_explain = classify_work_kind(
|
|
310
|
+
story_prose="code feature",
|
|
311
|
+
acceptance_criteria=["AC-1"],
|
|
312
|
+
touched_file_hints=["package.json"],
|
|
313
|
+
)
|
|
314
|
+
assert no_explain.rule_trace == []
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
# ---------------------------------------------------------------------------
|
|
318
|
+
# Q1 LOCKED — tie-break (highest tier wins)
|
|
319
|
+
# ---------------------------------------------------------------------------
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def test_us0118_tie_break_code_wins():
|
|
323
|
+
"""Story touching both docs/ and src/ (mixed tier) → CODE (highest
|
|
324
|
+
tier wins per Q1 LOCKED)."""
|
|
325
|
+
result = classify_work_kind(
|
|
326
|
+
story_prose="Feature + docs",
|
|
327
|
+
acceptance_criteria=["AC-1"],
|
|
328
|
+
touched_file_hints=["docs/foo.md", "package.json"],
|
|
329
|
+
)
|
|
330
|
+
assert result.work_kind is WorkKind.CODE
|