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.
- package/package.json +1 -1
- package/scripts/check_intake_template_parity.py +190 -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 +636 -377
- 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 -3510
- 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/scratchpad_example_parity_test.py +124 -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,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
|
+
)
|