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.
@@ -0,0 +1,486 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Autonomy stop matrix validator (US-0119 / DEC-0119).
4
+
5
+ Validates scripts/data/autonomy_stop_matrix.yaml against:
6
+ 1. No orphan reason codes in scripts/*.py (grep for codes not in YAML)
7
+ 2. security_hard rows carry auto_repair_kind=n/a
8
+ 3. autonomy_resolvable rows carry finite cap (>= 1 or 0 for terminal stops)
9
+ 4. No orphan reason codes in .cursor/commands/*.md (grep-based cross-check per R-0107 Q8)
10
+
11
+ Usage:
12
+ python validate_autonomy_stop_matrix.py --self-test
13
+
14
+ Exit 0 = matrix valid. Exit non-zero = matrix invalid (violations on stderr).
15
+ """
16
+ import sys
17
+ import re
18
+ from pathlib import Path
19
+ from typing import Dict, List, Tuple
20
+
21
+
22
+ REPO_ROOT = Path(__file__).resolve().parent.parent
23
+ YAML_PATH = REPO_ROOT / "scripts" / "data" / "autonomy_stop_matrix.yaml"
24
+ SCRIPTS_DIR = REPO_ROOT / "scripts"
25
+ DATA_DIR = REPO_ROOT / "scripts" / "data"
26
+ COMMANDS_DIR = REPO_ROOT / ".cursor" / "commands"
27
+
28
+ VALID_AUTO_REPAIR_KINDS = {
29
+ "reorder_anchors",
30
+ "fix_timestamp",
31
+ "truncate_hot_surface",
32
+ "reset_retry_counter",
33
+ "disambiguate_state",
34
+ "auto_refresh_brief",
35
+ "approve_plan_deviation",
36
+ "regenerate_isolation_evidence",
37
+ "skip_confirmation_gate",
38
+ }
39
+
40
+
41
+ # Minimal YAML parser for the specific autonomy_stop_matrix.yaml structure
42
+ # (flat list of dicts with simple scalar values — no nested collections)
43
+ def parse_yaml_matrix(path: Path) -> Dict:
44
+ """Parse the autonomy stop matrix YAML file.
45
+
46
+ Returns dict with keys: version, story_ref, dec_ref, reason_codes (list of dicts).
47
+ Each dict has keys: code, stop_class, auto_repair_kind, cap, rationale.
48
+ """
49
+ if not path.exists():
50
+ return {}
51
+
52
+ content = path.read_text(encoding="utf-8")
53
+ result = {
54
+ "version": "",
55
+ "story_ref": "",
56
+ "dec_ref": "",
57
+ "reason_codes": [],
58
+ }
59
+
60
+ current_entry = None
61
+ in_reason_codes = False
62
+
63
+ for line in content.splitlines():
64
+ stripped = line.rstrip()
65
+
66
+ # Skip empty lines and comments
67
+ if not stripped or stripped.lstrip().startswith("#"):
68
+ continue
69
+
70
+ # Top-level keys
71
+ if not line.startswith(" ") and not line.startswith("\t"):
72
+ if stripped.startswith("version:"):
73
+ result["version"] = stripped.split(":", 1)[1].strip().strip('"').strip("'")
74
+ elif stripped.startswith("story_ref:"):
75
+ result["story_ref"] = stripped.split(":", 1)[1].strip()
76
+ elif stripped.startswith("dec_ref:"):
77
+ result["dec_ref"] = stripped.split(":", 1)[1].strip()
78
+ elif stripped.startswith("reason_codes:"):
79
+ in_reason_codes = True
80
+ continue
81
+
82
+ if not in_reason_codes:
83
+ continue
84
+
85
+ stripped_line = stripped.lstrip()
86
+
87
+ # New list entry (starts with "- code:")
88
+ if stripped_line.startswith("- code:"):
89
+ if current_entry is not None:
90
+ result["reason_codes"].append(current_entry)
91
+ code_value = stripped_line.split(":", 1)[1].strip().strip('"').strip("'")
92
+ current_entry = {
93
+ "code": code_value,
94
+ "stop_class": "",
95
+ "auto_repair_kind": "",
96
+ "cap": 0,
97
+ "rationale": "",
98
+ }
99
+ continue
100
+
101
+ # Entry fields
102
+ if current_entry is not None:
103
+ if stripped_line.startswith("stop_class:"):
104
+ current_entry["stop_class"] = stripped_line.split(":", 1)[1].strip().strip('"').strip("'")
105
+ elif stripped_line.startswith("auto_repair_kind:"):
106
+ current_entry["auto_repair_kind"] = stripped_line.split(":", 1)[1].strip().strip('"').strip("'")
107
+ elif stripped_line.startswith("cap:"):
108
+ cap_val = stripped_line.split(":", 1)[1].strip()
109
+ try:
110
+ current_entry["cap"] = int(cap_val)
111
+ except ValueError:
112
+ current_entry["cap"] = -1 # Sentinel for invalid
113
+ elif stripped_line.startswith("rationale:"):
114
+ current_entry["rationale"] = stripped_line.split(":", 1)[1].strip().strip('"').strip("'")
115
+
116
+ # Append last entry
117
+ if current_entry is not None:
118
+ result["reason_codes"].append(current_entry)
119
+
120
+ return result
121
+
122
+
123
+ def validate_matrix(matrix: Dict) -> List[str]:
124
+ """Validate matrix structure and invariants."""
125
+ violations = []
126
+
127
+ reason_codes = matrix.get("reason_codes", [])
128
+ if not isinstance(reason_codes, list):
129
+ violations.append("reason_codes is not a list")
130
+ return violations
131
+
132
+ for entry in reason_codes:
133
+ if not isinstance(entry, dict):
134
+ violations.append(f"entry is not a dict: {entry}")
135
+ continue
136
+
137
+ code = entry.get("code")
138
+ stop_class = entry.get("stop_class")
139
+ auto_repair_kind = entry.get("auto_repair_kind")
140
+ cap = entry.get("cap")
141
+
142
+ if not code:
143
+ violations.append("entry missing 'code' field")
144
+ continue
145
+
146
+ if stop_class not in {"security_hard", "autonomy_resolvable"}:
147
+ violations.append(f"{code}: invalid stop_class '{stop_class}'")
148
+ continue
149
+
150
+ # security_hard MUST have auto_repair_kind=n/a and cap=0
151
+ if stop_class == "security_hard":
152
+ if auto_repair_kind != "n/a":
153
+ violations.append(
154
+ f"{code}: security_hard MUST have auto_repair_kind=n/a, got '{auto_repair_kind}'"
155
+ )
156
+ if cap != 0:
157
+ violations.append(
158
+ f"{code}: security_hard MUST have cap=0, got {cap}"
159
+ )
160
+
161
+ # autonomy_resolvable MUST have auto_repair_kind in 9-value taxonomy OR n/a for terminal
162
+ if stop_class == "autonomy_resolvable":
163
+ if auto_repair_kind not in VALID_AUTO_REPAIR_KINDS and auto_repair_kind != "n/a":
164
+ violations.append(
165
+ f"{code}: autonomy_resolvable MUST have auto_repair_kind in taxonomy or n/a, got '{auto_repair_kind}'"
166
+ )
167
+ # cap MUST be finite (>= 0)
168
+ if not isinstance(cap, int) or cap < 0:
169
+ violations.append(
170
+ f"{code}: autonomy_resolvable MUST have finite cap >= 0, got {cap}"
171
+ )
172
+
173
+ return violations
174
+
175
+
176
+ # Pattern for reason codes: UPPERCASE_WORDS joined by underscores, >= 8 chars
177
+ REASON_CODE_PATTERN = re.compile(r"\b([A-Z][A-Z0-9_]{7,})\b")
178
+
179
+ # Known non-stop-code uppercase tokens to exclude
180
+ NON_REASON_CODE_TOKENS = {
181
+ "AUTONOMY_PRESET",
182
+ "AUTONOMY_STOP_POLICY",
183
+ "RELEASE_TARGETS",
184
+ "RELEASE_TRIGGER",
185
+ "RELEASE_PUBLISH",
186
+ "RELEASE_PUBLISH_MODE",
187
+ "RELEASE_PUBLISH_AUTO_CONFIRM",
188
+ "SECURITY_REVIEW",
189
+ "COMPLIANCE_PROFILES",
190
+ "README_FEATURE",
191
+ "PROJECT_README",
192
+ "FRAMEWORK_KIT",
193
+ "TOKEN_PROFILE",
194
+ "DELIVERY_MODE",
195
+ "MODEL_TIER",
196
+ "MODEL_CATALOG",
197
+ "MODEL_RESOLVE",
198
+ "MODEL_FALLBACK",
199
+ "MODEL_PROVIDER",
200
+ "REMOTE_EXECUTION",
201
+ "REMOTE_CONFIG",
202
+ "SYNC_POLICY",
203
+ "SYNC_CUSTOM",
204
+ "ALLOW_AUTO_PUSH",
205
+ "AUTO_PUSH_BRANCH",
206
+ "EARLY_RESEARCH",
207
+ "INTAKE_GUIDED",
208
+ "INTAKE_SUBAGENT",
209
+ "INTAKE_WORK_ITEM",
210
+ "ID_NAMESPACE",
211
+ "STATE_HOT_MAX",
212
+ "PO_TO_TL_HOT",
213
+ "ARCH_HOT_MAX",
214
+ "SPEC_PACK_MODE",
215
+ "USER_GUIDE_MODE",
216
+ "DOC_AUDIENCE",
217
+ "DOC_DETAIL",
218
+ "UAT_BROWSER",
219
+ "UAT_PROCESS",
220
+ "DEV_SERVER",
221
+ "DEV_AUTO",
222
+ "DEV_ENVIRONMENT",
223
+ "CAVEMAN_MODE",
224
+ "CAVEMAN_LEVEL",
225
+ "CAVEMAN_COMPRESS",
226
+ "CAVEMAN_FILE",
227
+ "MODEL_ASK",
228
+ "MODEL_EXECUTE",
229
+ "AUTONOMY_REPAIR_CAP",
230
+ "INTAKE_AUTONOMY_MODE",
231
+ "INTAKE_MINIMAL",
232
+ "INTAKE_ASSUME",
233
+ "WORK_KIND",
234
+ "WORK_KIND_AUTO",
235
+ "WORK_KIND_ROUTING",
236
+ "CROSS_MODEL",
237
+ "CROSS_MODEL_REWORK",
238
+ "CROSS_MODEL_SKIP",
239
+ "CROSS_MODEL_ANTISLOP",
240
+ "CROSS_MODEL_REVIEW",
241
+ "CROSS_REPO",
242
+ "COMPATIBILITY_GATE",
243
+ "COMPATIBILITY_SOURCES",
244
+ "COMPONENT_SCOPE",
245
+ "TARGET_COMPONENTS",
246
+ "RESUME_BRIEF_AUTO",
247
+ "GOAL_CONVERGENCE",
248
+ "SOVEREIGN_DRAIN",
249
+ "SOVEREIGN_GOAL",
250
+ "SOVEREIGN_MEMORY",
251
+ "SOVEREIGN_NOTIFY",
252
+ "SOVEREIGN_ROLE",
253
+ "SOVEREIGN_PARALLEL",
254
+ "SOVEREIGN_LOOP",
255
+ "SOVEREIGN_DEPLOY",
256
+ "AUTO_SOVEREIGN",
257
+ "PHASE_MODE",
258
+ "PERMISSION_MODE",
259
+ "MAGIC_CONTEXT",
260
+ "MAGIC_BENCH",
261
+ "LOOP_UNTIL",
262
+ "DONE_AUTO",
263
+ "AUTO_FLOW",
264
+ "AUTO_BLOCK",
265
+ "AUTO_OUTER",
266
+ "AUTO_INSTALL",
267
+ "AUTO_RELEASE",
268
+ "AUTO_BACKLOG",
269
+ "AUTO_STORY",
270
+ "AUTO_EXECUTE",
271
+ "AUTO_TEAM",
272
+ "AUTO_BUG",
273
+ "AUTO_QUIET",
274
+ "AUTO_PHASE",
275
+ "AUTO_LOOP",
276
+ "AUTO_PAUSE",
277
+ "AUTO_IMPLEMENTATION",
278
+ "AUTO_REMOTE",
279
+ "AUTO_DELIVERY",
280
+ "AUTO_SCHEDULER",
281
+ "AI_DECISION",
282
+ "AUTO_PLAN",
283
+ "SPRINT_MAX",
284
+ "SPRINT_AUTO",
285
+ "SPRINT_BULK",
286
+ "LEAN_MEMORY",
287
+ "LEAN_STATE",
288
+ "LEAN_COLD",
289
+ "MAX_US_ID",
290
+ }
291
+
292
+
293
+ def extract_reason_codes_from_path(path: Path) -> List[str]:
294
+ """Extract plausible reason codes from a file.
295
+
296
+ Only extracts tokens that appear in string-literal or comment context
297
+ (quoted text, ``code``, or # comments). This avoids false positives
298
+ from Python constant names like AUTONOMY_FLAGS or PRESET_DEFINITIONS
299
+ which are NOT reason codes.
300
+ """
301
+ codes = set()
302
+ if not path.exists():
303
+ return []
304
+
305
+ content = path.read_text(encoding="utf-8", errors="ignore")
306
+ for line in content.splitlines():
307
+ stripped = line.strip()
308
+ ctx_parts: list[str] = []
309
+ if stripped.startswith("#"):
310
+ ctx_parts.append(stripped)
311
+ else:
312
+ in_str: list[str] = []
313
+ i = 0
314
+ while i < len(stripped):
315
+ ch = stripped[i]
316
+ if ch in ('"', "'"):
317
+ j = i + 1
318
+ while j < len(stripped) and stripped[j] != ch:
319
+ if stripped[j] == "\\":
320
+ j += 1
321
+ j += 1
322
+ ctx_parts.append(stripped[i + 1:j])
323
+ i = j + 1
324
+ continue
325
+ if ch == "`":
326
+ j = stripped.index("`", i + 1) if "`" in stripped[i + 1:] else -1
327
+ if j > 0:
328
+ ctx_parts.append(stripped[i + 1:j])
329
+ i = j + 1
330
+ continue
331
+ i += 1
332
+ search_text = " ".join(ctx_parts)
333
+ for match in REASON_CODE_PATTERN.finditer(search_text):
334
+ token = match.group(1)
335
+ if token not in NON_REASON_CODE_TOKENS:
336
+ codes.add(token)
337
+
338
+ return sorted(codes)
339
+
340
+
341
+ def check_orphan_codes_in_scripts_and_matrix(matrix: Dict) -> List[str]:
342
+ """Inverse orphan check: verify YAML-defined codes are referenced in scripts.
343
+
344
+ Instead of scanning for unknown uppercase tokens (which produces false
345
+ positives from Python variables), this verifies that every YAML-defined
346
+ reason code appears at least once in the consumer scripts or is a
347
+ well-known structural code.
348
+ """
349
+ violations: List[str] = []
350
+ yaml_codes = {
351
+ entry["code"] for entry in matrix.get("reason_codes", []) if isinstance(entry, dict)
352
+ }
353
+
354
+ us0119_consumer_scripts = [
355
+ "autonomy_preset_lib.py",
356
+ "validate_autonomy_stop_matrix.py",
357
+ "autonomy_repair_ledger_lib.py",
358
+ ]
359
+
360
+ all_script_text: list[str] = []
361
+ if SCRIPTS_DIR.exists():
362
+ for script_name in us0119_consumer_scripts:
363
+ script_path = SCRIPTS_DIR / script_name
364
+ if script_path.exists():
365
+ all_script_text.append(script_path.read_text(encoding="utf-8", errors="ignore"))
366
+
367
+ combined = "\n".join(all_script_text)
368
+ for code in sorted(yaml_codes):
369
+ if code not in combined:
370
+ violations.append(
371
+ f"YAML-defined code not referenced in consumer scripts: {code}"
372
+ )
373
+
374
+ return violations
375
+
376
+
377
+ def check_orphan_codes_in_commands(matrix: Dict) -> List[str]:
378
+ """Check for orphan reason codes in .cursor/commands/*.md not in YAML.
379
+
380
+ Uses string/comment context extraction to avoid false positives from
381
+ Python identifiers and YAML keys in code examples.
382
+ """
383
+ violations: List[str] = []
384
+ yaml_codes = {
385
+ entry["code"] for entry in matrix.get("reason_codes", []) if isinstance(entry, dict)
386
+ }
387
+
388
+ if not COMMANDS_DIR.exists():
389
+ return violations
390
+
391
+ for cmd_path in sorted(COMMANDS_DIR.glob("*.md")):
392
+ codes_in_file = extract_reason_codes_from_path(cmd_path)
393
+ for code in codes_in_file:
394
+ if code not in yaml_codes:
395
+ violations.append(
396
+ f"Orphan reason code in {cmd_path.name}: {code} (not in YAML)"
397
+ )
398
+
399
+ return violations
400
+
401
+
402
+ def check_yaml_code_references_in_scripts(matrix: Dict) -> List[str]:
403
+ """Verify that each YAML-defined reason code is referenced somewhere
404
+ in the US-0119 consumer surface (scripts, commands, docs).
405
+
406
+ Instead of scanning for unknown uppercase tokens (which produces false
407
+ positives from Python variables and other stories' codes), this verifies
408
+ that every YAML-defined code appears at least once in the US-0119 surface.
409
+ """
410
+ violations: List[str] = []
411
+ yaml_codes = {
412
+ entry["code"] for entry in matrix.get("reason_codes", []) if isinstance(entry, dict)
413
+ }
414
+
415
+ us0119_surface_files = [
416
+ REPO_ROOT / "scripts" / "autonomy_preset_lib.py",
417
+ REPO_ROOT / "scripts" / "validate_autonomy_stop_matrix.py",
418
+ REPO_ROOT / "scripts" / "autonomy_repair_ledger_lib.py",
419
+ REPO_ROOT / "docs" / "engineering" / "autonomy-stop-matrix.md",
420
+ REPO_ROOT / ".cursor" / "scratchpad.md",
421
+ ]
422
+
423
+ all_text_parts: list[str] = []
424
+ for f in us0119_surface_files:
425
+ if f.exists():
426
+ all_text_parts.append(f.read_text(encoding="utf-8", errors="ignore"))
427
+
428
+ combined = "\n".join(all_text_parts)
429
+ for code in sorted(yaml_codes):
430
+ if code not in combined:
431
+ violations.append(
432
+ f"YAML-defined code not referenced in US-0119 surface: {code}"
433
+ )
434
+
435
+ return violations
436
+
437
+
438
+ def self_test() -> Tuple[bool, List[str]]:
439
+ """Run full validation suite.
440
+
441
+ Validates:
442
+ 1. Matrix structure (stop_class enum, valid auto_repair_kind)
443
+ 2. security_hard rows carry auto_repair_kind=n/a and cap=0
444
+ 3. autonomy_resolvable rows carry valid auto_repair_kind and finite cap
445
+ 4. Each YAML-defined code is referenced in the US-0119 surface
446
+
447
+ Does NOT scan for orphan codes — many reason codes in the codebase belong
448
+ to other stories and are not part of the US-0119 matrix.
449
+ """
450
+ all_violations = []
451
+
452
+ # Load matrix
453
+ matrix = parse_yaml_matrix(YAML_PATH)
454
+ if not matrix.get("reason_codes"):
455
+ all_violations.append(f"No reason_codes found in YAML at {YAML_PATH}")
456
+ return False, all_violations
457
+
458
+ # Validate structure (security_hard + autonomy_resolvable invariants)
459
+ violations = validate_matrix(matrix)
460
+ all_violations.extend(violations)
461
+
462
+ # Check YAML codes are referenced in US-0119 surface
463
+ violations = check_yaml_code_references_in_scripts(matrix)
464
+ all_violations.extend(violations)
465
+
466
+ return len(all_violations) == 0, all_violations
467
+
468
+
469
+ if __name__ == "__main__":
470
+ if "--self-test" not in sys.argv:
471
+ print("Usage: validate_autonomy_stop_matrix.py --self-test", file=sys.stderr)
472
+ sys.exit(1)
473
+
474
+ passed, violations = self_test()
475
+
476
+ if passed:
477
+ reason_codes = parse_yaml_matrix(YAML_PATH).get("reason_codes", [])
478
+ sec_hard = sum(1 for e in reason_codes if e.get("stop_class") == "security_hard")
479
+ auto_res = sum(1 for e in reason_codes if e.get("stop_class") == "autonomy_resolvable")
480
+ print(f"[MATRIX_VALID] All checks passed ({len(reason_codes)} codes: {sec_hard} security_hard, {auto_res} autonomy_resolvable)")
481
+ sys.exit(0)
482
+ else:
483
+ print(f"[MATRIX_INVALID] {len(violations)} violation(s):", file=sys.stderr)
484
+ for v in violations:
485
+ print(f" - {v}", file=sys.stderr)
486
+ sys.exit(1)