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,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
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Contract tests for US-0119 — Autonomous-autonomy presets.
|
|
3
|
+
|
|
4
|
+
10 test markers per DEC-0119 §9, covering AC-6, AC-7, AC-10, AC-12.
|
|
5
|
+
"""
|
|
6
|
+
import pytest
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_us0119_preset_none_is_noop():
|
|
12
|
+
"""AC-6: AUTONOMY_PRESET=none produces byte-identical pre-US-0119 behaviour (empty expansion)."""
|
|
13
|
+
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
|
|
14
|
+
from autonomy_preset_lib import expand_autonomy_preset
|
|
15
|
+
|
|
16
|
+
result = expand_autonomy_preset("none")
|
|
17
|
+
assert result == {}, "AUTONOMY_PRESET=none MUST produce empty expansion (byte-identical pre-US-0119)"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_us0119_preset_balanced_expansion():
|
|
21
|
+
"""AC-2: balanced preset expands into documented 8 flags per DEC-0119 §7."""
|
|
22
|
+
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
|
|
23
|
+
from autonomy_preset_lib import expand_autonomy_preset
|
|
24
|
+
|
|
25
|
+
result = expand_autonomy_preset("balanced")
|
|
26
|
+
assert len(result) == 8, f"balanced preset MUST expand to 8 flags, got {len(result)}"
|
|
27
|
+
expected_keys = {
|
|
28
|
+
"WORK_KIND_AUTO_ACCEPT",
|
|
29
|
+
"CROSS_MODEL_REWORK_EXHAUSTED_POLICY",
|
|
30
|
+
"CROSS_MODEL_SKIP_PHASES",
|
|
31
|
+
"RESUME_BRIEF_AUTO_REFRESH",
|
|
32
|
+
"RUNTIME_PROOF_KIND",
|
|
33
|
+
"GOAL_CONVERGENCE_INTERVAL",
|
|
34
|
+
"SOVEREIGN_DRAIN_AUTO_ACCEPT",
|
|
35
|
+
"AUTONOMY_STOP_POLICY",
|
|
36
|
+
}
|
|
37
|
+
assert set(result.keys()) == expected_keys, f"balanced preset keys mismatch: {set(result.keys())} != {expected_keys}"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_us0119_preset_full_expansion():
|
|
41
|
+
"""AC-2: full preset expands into documented 12 flags per DEC-0119 §7."""
|
|
42
|
+
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
|
|
43
|
+
from autonomy_preset_lib import expand_autonomy_preset
|
|
44
|
+
|
|
45
|
+
result = expand_autonomy_preset("full")
|
|
46
|
+
assert len(result) == 12, f"full preset MUST expand to 12 flags, got {len(result)}"
|
|
47
|
+
expected_keys = {
|
|
48
|
+
"INTAKE_AUTONOMY_MODE",
|
|
49
|
+
"INTAKE_MINIMAL_PACK",
|
|
50
|
+
"INTAKE_ASSUME_STACK_CONTEXT",
|
|
51
|
+
"WORK_KIND_AUTO_ACCEPT",
|
|
52
|
+
"CROSS_MODEL_REWORK_EXHAUSTED_POLICY",
|
|
53
|
+
"CROSS_MODEL_SKIP_PHASES",
|
|
54
|
+
"RESUME_BRIEF_AUTO_REFRESH",
|
|
55
|
+
"RUNTIME_PROOF_KIND",
|
|
56
|
+
"GOAL_CONVERGENCE_INTERVAL",
|
|
57
|
+
"SOVEREIGN_DRAIN_AUTO_ACCEPT",
|
|
58
|
+
"RELEASE_PUBLISH_AUTO_CONFIRM",
|
|
59
|
+
"AUTONOMY_STOP_POLICY",
|
|
60
|
+
}
|
|
61
|
+
assert set(result.keys()) == expected_keys, f"full preset keys mismatch: {set(result.keys())} != {expected_keys}"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_us0119_explicit_flag_overrides_preset():
|
|
65
|
+
"""AC-2: explicit per-flag > preset expansion (LOCKED precedence)."""
|
|
66
|
+
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
|
|
67
|
+
from autonomy_preset_lib import expand_autonomy_preset
|
|
68
|
+
|
|
69
|
+
result = expand_autonomy_preset("balanced", {"WORK_KIND_AUTO_ACCEPT": "0"})
|
|
70
|
+
assert result["WORK_KIND_AUTO_ACCEPT"] == "0", "explicit override MUST win over preset value"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_us0119_preset_expansion_uses_known_keys_only():
|
|
74
|
+
"""AC-12: expansion output contains only keys in pre-US-0119 scratchpad schema (compose guard)."""
|
|
75
|
+
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
|
|
76
|
+
from autonomy_preset_lib import expand_autonomy_preset, AUTONOMY_FLAGS
|
|
77
|
+
|
|
78
|
+
for preset in ["none", "balanced", "full"]:
|
|
79
|
+
result = expand_autonomy_preset(preset)
|
|
80
|
+
unknown_keys = set(result.keys()) - AUTONOMY_FLAGS
|
|
81
|
+
assert not unknown_keys, f"preset={preset} expansion contains unknown keys: {unknown_keys}"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_us0119_matrix_validator_passes():
|
|
85
|
+
"""AC-4: scripts/validate_autonomy_stop_matrix.py --self-test exits 0."""
|
|
86
|
+
import subprocess
|
|
87
|
+
|
|
88
|
+
validator_path = Path(__file__).parent.parent / "scripts" / "validate_autonomy_stop_matrix.py"
|
|
89
|
+
result = subprocess.run(
|
|
90
|
+
["python", str(validator_path), "--self-test"],
|
|
91
|
+
capture_output=True,
|
|
92
|
+
text=True,
|
|
93
|
+
)
|
|
94
|
+
assert result.returncode == 0, f"validator --self-test MUST exit 0, got {result.returncode}\nstderr: {result.stderr}"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_us0119_security_hard_gates_never_auto_repaired():
|
|
98
|
+
"""AC-7: matrix security_hard rows all carry auto_repair_kind=n/a."""
|
|
99
|
+
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
|
|
100
|
+
from validate_autonomy_stop_matrix import parse_yaml_matrix, REPO_ROOT
|
|
101
|
+
|
|
102
|
+
yaml_path = REPO_ROOT / "scripts" / "data" / "autonomy_stop_matrix.yaml"
|
|
103
|
+
matrix = parse_yaml_matrix(yaml_path)
|
|
104
|
+
|
|
105
|
+
for entry in matrix.get("reason_codes", []):
|
|
106
|
+
if entry.get("stop_class") == "security_hard":
|
|
107
|
+
assert entry.get("auto_repair_kind") == "n/a", (
|
|
108
|
+
f"security_hard code {entry['code']} MUST have auto_repair_kind=n/a, "
|
|
109
|
+
f"got {entry.get('auto_repair_kind')}"
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_us0119_stop_policy_affects_repair_dispatch():
|
|
114
|
+
"""AC-3: auto_repair_then_block vs auto_repair_then_skip dispatch correctly."""
|
|
115
|
+
# This test verifies the stop-policy dispatch logic is documented in scratchpad
|
|
116
|
+
# Actual dispatch happens at runtime based on AUTONOMY_STOP_POLICY value
|
|
117
|
+
|
|
118
|
+
scratchpad_path = Path(__file__).parent.parent / ".cursor" / "scratchpad.md"
|
|
119
|
+
content = scratchpad_path.read_text(encoding="utf-8")
|
|
120
|
+
|
|
121
|
+
assert "AUTONOMY_STOP_POLICY=" in content, "AUTONOMY_STOP_POLICY MUST be documented in scratchpad"
|
|
122
|
+
assert "block|" in content or "auto_repair_then_block|" in content, (
|
|
123
|
+
"AUTONOMY_STOP_POLICY MUST document block/auto_repair_then_block/auto_repair_then_skip enum"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def test_us0119_repair_ledger_cap_escalates():
|
|
128
|
+
"""AC-8: cap exhaustion -> AUTONOMY_REPAIR_CAP_EXHAUSTED terminal stop."""
|
|
129
|
+
# This test verifies the terminal stop code is in the matrix
|
|
130
|
+
|
|
131
|
+
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
|
|
132
|
+
from validate_autonomy_stop_matrix import parse_yaml_matrix, REPO_ROOT
|
|
133
|
+
|
|
134
|
+
yaml_path = REPO_ROOT / "scripts" / "data" / "autonomy_stop_matrix.yaml"
|
|
135
|
+
matrix = parse_yaml_matrix(yaml_path)
|
|
136
|
+
|
|
137
|
+
reason_codes = {entry["code"]: entry for entry in matrix.get("reason_codes", [])}
|
|
138
|
+
assert "AUTONOMY_REPAIR_CAP_EXHAUSTED" in reason_codes, (
|
|
139
|
+
"AUTONOMY_REPAIR_CAP_EXHAUSTED terminal stop code MUST be in matrix"
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
terminal_entry = reason_codes["AUTONOMY_REPAIR_CAP_EXHAUSTED"]
|
|
143
|
+
assert terminal_entry["stop_class"] == "autonomy_resolvable", (
|
|
144
|
+
"AUTONOMY_REPAIR_CAP_EXHAUSTED MUST be autonomy_resolvable (bounded cap)"
|
|
145
|
+
)
|
|
146
|
+
assert terminal_entry["auto_repair_kind"] == "n/a", (
|
|
147
|
+
"AUTONOMY_REPAIR_CAP_EXHAUSTED MUST have auto_repair_kind=n/a (terminal)"
|
|
148
|
+
)
|
|
149
|
+
assert terminal_entry["cap"] == 0, (
|
|
150
|
+
"AUTONOMY_REPAIR_CAP_EXHAUSTED MUST have cap=0 (terminal stop)"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def test_us0119_matrix_no_orphan_codes():
|
|
155
|
+
"""AC-4: no orphan reason codes outside YAML manifest."""
|
|
156
|
+
# This test is implicitly covered by test_us0119_matrix_validator_passes,
|
|
157
|
+
# which runs the validator's --self-test. If the validator passes, no orphan
|
|
158
|
+
# codes exist.
|
|
159
|
+
|
|
160
|
+
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
|
|
161
|
+
from validate_autonomy_stop_matrix import self_test
|
|
162
|
+
|
|
163
|
+
passed, violations = self_test()
|
|
164
|
+
assert passed, f"validator self_test MUST pass, got violations: {violations}"
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
if __name__ == "__main__":
|
|
168
|
+
pytest.main([__file__, "-v"])
|