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,304 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Validator: US-0120 closure-verification.md schema
|
|
3
|
+
|
|
4
|
+
Required fields (Q6 LOCKED, .md format):
|
|
5
|
+
- story_id (US-XXXX pattern)
|
|
6
|
+
- closure_date (ISO-8601 UTC)
|
|
7
|
+
- closure_role (qe|curator)
|
|
8
|
+
- pre_closure_status (OPEN)
|
|
9
|
+
- post_closure_status (DONE)
|
|
10
|
+
- release_evidence_refs[] (array of paths)
|
|
11
|
+
- isolation_evidence{} (object)
|
|
12
|
+
- runtime_proof{} (object)
|
|
13
|
+
|
|
14
|
+
Optional fields:
|
|
15
|
+
- normalization_notes
|
|
16
|
+
- backward_compat_note
|
|
17
|
+
|
|
18
|
+
Exit 0 / 1.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import argparse
|
|
22
|
+
import json
|
|
23
|
+
import re
|
|
24
|
+
import sys
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
STORY_ID_RE = re.compile(r'^US-\d{4}$')
|
|
28
|
+
ISO_DATE_RE = re.compile(r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$')
|
|
29
|
+
|
|
30
|
+
REQUIRED_FIELDS = [
|
|
31
|
+
'story_id',
|
|
32
|
+
'closure_date',
|
|
33
|
+
'closure_role',
|
|
34
|
+
'pre_closure_status',
|
|
35
|
+
'post_closure_status',
|
|
36
|
+
'release_evidence_refs',
|
|
37
|
+
'isolation_evidence',
|
|
38
|
+
'runtime_proof',
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
OPTIONAL_FIELDS = [
|
|
42
|
+
'normalization_notes',
|
|
43
|
+
'backward_compat_note',
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def parse_md_frontmatter(text: str) -> dict:
|
|
48
|
+
"""Parse YAML-like frontmatter from .md file."""
|
|
49
|
+
if not text.startswith('---'):
|
|
50
|
+
return {}
|
|
51
|
+
|
|
52
|
+
parts = text.split('---', 2)
|
|
53
|
+
if len(parts) < 3:
|
|
54
|
+
return {}
|
|
55
|
+
|
|
56
|
+
frontmatter = {}
|
|
57
|
+
for line in parts[1].strip().splitlines():
|
|
58
|
+
line = line.strip()
|
|
59
|
+
if ':' in line:
|
|
60
|
+
key, _, value = line.partition(':')
|
|
61
|
+
key = key.strip()
|
|
62
|
+
value = value.strip()
|
|
63
|
+
|
|
64
|
+
# Try to parse as JSON for arrays and objects
|
|
65
|
+
if value.startswith('[') or value.startswith('{'):
|
|
66
|
+
try:
|
|
67
|
+
value = json.loads(value)
|
|
68
|
+
except json.JSONDecodeError:
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
# Strip quotes
|
|
72
|
+
if isinstance(value, str) and len(value) >= 2 and value[0] == value[-1] and value[0] in ('"', "'"):
|
|
73
|
+
value = value[1:-1]
|
|
74
|
+
|
|
75
|
+
frontmatter[key] = value
|
|
76
|
+
|
|
77
|
+
return frontmatter
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def validate_story_id(value: str) -> bool:
|
|
81
|
+
return bool(STORY_ID_RE.match(value))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def validate_closure_date(value: str) -> bool:
|
|
85
|
+
return bool(ISO_DATE_RE.match(value))
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def validate_closure_role(value: str) -> bool:
|
|
89
|
+
return value in ('qe', 'curator')
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def validate_pre_status(value: str) -> bool:
|
|
93
|
+
return value == 'OPEN'
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def validate_post_status(value: str) -> bool:
|
|
97
|
+
return value == 'DONE'
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def validate_release_evidence_refs(value) -> bool:
|
|
101
|
+
if isinstance(value, list):
|
|
102
|
+
return all(isinstance(ref, str) and ref.strip() for ref in value)
|
|
103
|
+
if isinstance(value, str):
|
|
104
|
+
try:
|
|
105
|
+
parsed = json.loads(value)
|
|
106
|
+
return isinstance(parsed, list) and all(isinstance(r, str) for r in parsed)
|
|
107
|
+
except:
|
|
108
|
+
return False
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def validate_isolation_evidence(value) -> bool:
|
|
113
|
+
if isinstance(value, dict):
|
|
114
|
+
required_keys = {'phase_id', 'role', 'fresh_context_marker', 'timestamp', 'evidence_ref'}
|
|
115
|
+
return required_keys.issubset(value.keys())
|
|
116
|
+
if isinstance(value, str):
|
|
117
|
+
try:
|
|
118
|
+
parsed = json.loads(value)
|
|
119
|
+
return isinstance(parsed, dict)
|
|
120
|
+
except:
|
|
121
|
+
return False
|
|
122
|
+
return False
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def validate_runtime_proof(value) -> bool:
|
|
126
|
+
if isinstance(value, dict):
|
|
127
|
+
required_keys = {'runtime_proof_id', 'proof_hash', 'proof_ttl'}
|
|
128
|
+
return required_keys.issubset(value.keys())
|
|
129
|
+
if isinstance(value, str):
|
|
130
|
+
try:
|
|
131
|
+
parsed = json.loads(value)
|
|
132
|
+
return isinstance(parsed, dict)
|
|
133
|
+
except:
|
|
134
|
+
return False
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
VALIDATORS = {
|
|
139
|
+
'story_id': validate_story_id,
|
|
140
|
+
'closure_date': validate_closure_date,
|
|
141
|
+
'closure_role': validate_closure_role,
|
|
142
|
+
'pre_closure_status': validate_pre_status,
|
|
143
|
+
'post_closure_status': validate_post_status,
|
|
144
|
+
'release_evidence_refs': validate_release_evidence_refs,
|
|
145
|
+
'isolation_evidence': validate_isolation_evidence,
|
|
146
|
+
'runtime_proof': validate_runtime_proof,
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def validate(file_path: str, silent: bool = False) -> tuple[bool, list[str]]:
|
|
151
|
+
"""Validate closure-verification.md file. Returns (pass, errors)."""
|
|
152
|
+
path = Path(file_path)
|
|
153
|
+
|
|
154
|
+
if not path.exists():
|
|
155
|
+
return False, [f"File not found: {file_path}"]
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
text = path.read_text(encoding='utf-8')
|
|
159
|
+
except Exception as e:
|
|
160
|
+
return False, [f"Failed to read file: {e}"]
|
|
161
|
+
|
|
162
|
+
frontmatter = parse_md_frontmatter(text)
|
|
163
|
+
|
|
164
|
+
errors = []
|
|
165
|
+
|
|
166
|
+
# Check required fields exist
|
|
167
|
+
for field in REQUIRED_FIELDS:
|
|
168
|
+
if field not in frontmatter:
|
|
169
|
+
errors.append(f"Missing required field: {field}")
|
|
170
|
+
continue
|
|
171
|
+
|
|
172
|
+
value = frontmatter[field]
|
|
173
|
+
|
|
174
|
+
# Check optional fields don't need validation
|
|
175
|
+
if field in OPTIONAL_FIELDS:
|
|
176
|
+
continue
|
|
177
|
+
|
|
178
|
+
validator = VALIDATORS.get(field)
|
|
179
|
+
if validator and not validator(value):
|
|
180
|
+
errors.append(f"Invalid value for {field}: {value}")
|
|
181
|
+
|
|
182
|
+
# Check for optional unrecognized fields? No, schema allows extras for extensibility
|
|
183
|
+
|
|
184
|
+
if not silent:
|
|
185
|
+
if errors:
|
|
186
|
+
print(f"[VALIDATE_CLOSURE_VERIFICATION_FAIL] {file_path}", file=sys.stderr)
|
|
187
|
+
for err in errors:
|
|
188
|
+
print(f" - {err}", file=sys.stderr)
|
|
189
|
+
else:
|
|
190
|
+
print(f"[VALIDATE_CLOSURE_VERIFICATION_OK] {file_path}")
|
|
191
|
+
|
|
192
|
+
return len(errors) == 0, errors
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def main():
|
|
196
|
+
parser = argparse.ArgumentParser(description='Validate closure-verification.md (US-0120)')
|
|
197
|
+
parser.add_argument('file', nargs='?', help='Path to closure-verification.md')
|
|
198
|
+
parser.add_argument('--self-test', action='store_true', help='Self-test validator schema enforcement')
|
|
199
|
+
parser.add_argument('--silent', action='store_true', help='Silent mode (exit code only)')
|
|
200
|
+
|
|
201
|
+
args = parser.parse_args()
|
|
202
|
+
|
|
203
|
+
if args.self_test:
|
|
204
|
+
# Self-test: validate schema enforcement
|
|
205
|
+
test_cases = [
|
|
206
|
+
# Valid case
|
|
207
|
+
('valid', {
|
|
208
|
+
'story_id': 'US-0120',
|
|
209
|
+
'closure_date': '2026-07-07T22:00:00Z',
|
|
210
|
+
'closure_role': 'qe',
|
|
211
|
+
'pre_closure_status': 'OPEN',
|
|
212
|
+
'post_closure_status': 'DONE',
|
|
213
|
+
'release_evidence_refs': ['handoffs/releases/S0120-release-notes.md'],
|
|
214
|
+
'isolation_evidence': {
|
|
215
|
+
'phase_id': 'closure',
|
|
216
|
+
'role': 'qe',
|
|
217
|
+
'fresh_context_marker': 'qe-US0120-closure-20260707T220000Z-fresh',
|
|
218
|
+
'timestamp': '2026-07-07T22:00:00Z',
|
|
219
|
+
'evidence_ref': 'sprints/S0120/closure-verification.md'
|
|
220
|
+
},
|
|
221
|
+
'runtime_proof': {
|
|
222
|
+
'runtime_proof_id': 'rp-auto-20260707-execute-qe-20260707T220000Z-US-0120',
|
|
223
|
+
'proof_hash': 'a' * 64,
|
|
224
|
+
'proof_ttl': '2026-07-07T23:00:00Z'
|
|
225
|
+
}
|
|
226
|
+
}, True),
|
|
227
|
+
|
|
228
|
+
# Invalid story_id
|
|
229
|
+
('invalid_story_id', {
|
|
230
|
+
'story_id': 'US-abc',
|
|
231
|
+
'closure_date': '2026-07-07T22:00:00Z',
|
|
232
|
+
'closure_role': 'qe',
|
|
233
|
+
'pre_closure_status': 'OPEN',
|
|
234
|
+
'post_closure_status': 'DONE',
|
|
235
|
+
'release_evidence_refs': ['path'],
|
|
236
|
+
'isolation_evidence': {'phase_id': 'x'},
|
|
237
|
+
'runtime_proof': {'runtime_proof_id': 'x'}
|
|
238
|
+
}, False),
|
|
239
|
+
|
|
240
|
+
# Invalid role
|
|
241
|
+
('invalid_role', {
|
|
242
|
+
'story_id': 'US-0120',
|
|
243
|
+
'closure_date': '2026-07-07T22:00:00Z',
|
|
244
|
+
'closure_role': 'dev',
|
|
245
|
+
'pre_closure_status': 'OPEN',
|
|
246
|
+
'post_closure_status': 'DONE',
|
|
247
|
+
'release_evidence_refs': ['path'],
|
|
248
|
+
'isolation_evidence': {'phase_id': 'x'},
|
|
249
|
+
'runtime_proof': {'runtime_proof_id': 'x'}
|
|
250
|
+
}, False),
|
|
251
|
+
|
|
252
|
+
# Missing required field
|
|
253
|
+
('missing_field', {
|
|
254
|
+
'story_id': 'US-0120',
|
|
255
|
+
'closure_date': '2026-07-07T22:00:00Z'
|
|
256
|
+
}, False),
|
|
257
|
+
]
|
|
258
|
+
|
|
259
|
+
all_pass = True
|
|
260
|
+
for name, mock_data, expected_pass in test_cases:
|
|
261
|
+
# Test validation logic directly
|
|
262
|
+
result = True
|
|
263
|
+
errors_list = []
|
|
264
|
+
|
|
265
|
+
# Check story_id
|
|
266
|
+
if 'story_id' in mock_data:
|
|
267
|
+
if not validate_story_id(mock_data['story_id']):
|
|
268
|
+
result = False
|
|
269
|
+
|
|
270
|
+
# Check closure_role
|
|
271
|
+
if 'closure_role' in mock_data:
|
|
272
|
+
if not validate_closure_role(mock_data['closure_role']):
|
|
273
|
+
result = False
|
|
274
|
+
|
|
275
|
+
# Check missing required fields
|
|
276
|
+
for field in REQUIRED_FIELDS:
|
|
277
|
+
if field not in mock_data:
|
|
278
|
+
errors_list.append(f"Missing required field: {field}")
|
|
279
|
+
|
|
280
|
+
if errors_list:
|
|
281
|
+
result = False
|
|
282
|
+
|
|
283
|
+
if result != expected_pass:
|
|
284
|
+
print(f"SELF_TEST_FAIL {name}: expected={expected_pass} got={result}", file=sys.stderr)
|
|
285
|
+
all_pass = False
|
|
286
|
+
else:
|
|
287
|
+
print(f"SELF_TEST_OK {name}")
|
|
288
|
+
|
|
289
|
+
if all_pass:
|
|
290
|
+
print("[VALIDATE_CLOSURE_VERIFICATION_SELF_TEST_OK]")
|
|
291
|
+
return 0
|
|
292
|
+
else:
|
|
293
|
+
print("[VALIDATE_CLOSURE_VERIFICATION_SELF_TEST_FAIL]", file=sys.stderr)
|
|
294
|
+
return 1
|
|
295
|
+
|
|
296
|
+
if not args.file:
|
|
297
|
+
parser.error("file argument required (or use --self-test)")
|
|
298
|
+
|
|
299
|
+
passed, errors = validate(args.file, silent=args.silent)
|
|
300
|
+
return 0 if passed else 1
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
if __name__ == '__main__':
|
|
304
|
+
sys.exit(main())
|