its-magic 0.1.2 → 0.1.3-1
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/bin/postinstall.js +72 -0
- package/installer.py +55 -0
- package/package.json +1 -1
- package/scripts/check_intake_template_parity.py +90 -0
- package/template/.cursor/agents/curator.mdc +1 -0
- package/template/.cursor/agents/po.mdc +1 -0
- package/template/.cursor/agents/release.mdc +1 -0
- package/template/.cursor/commands/release.md +18 -0
- package/template/.cursor/model-catalog.local.example.cursor-only.json +9 -0
- package/template/.cursor/model-catalog.local.example.json +9 -0
- package/template/.cursor/model-catalog.local.example.level-1-easy.json +9 -0
- package/template/.cursor/model-catalog.local.example.level-2-complex.json +9 -0
- package/template/.cursor/model-catalog.local.example.level-3-mega.json +9 -0
- package/template/.cursor/model-catalog.local.example.level-4-super.json +9 -0
- package/template/.cursor/model-catalog.local.example.role-based-balanced.json +18 -0
- package/template/.cursor/model-catalog.local.example.role-based-highend.json +18 -0
- package/template/.cursor/scratchpad.local.example.md +55 -0
- package/template/.cursor/scratchpad.md +62 -0
- package/template/CHANGELOG.md +11 -0
- package/template/docs/engineering/runbook.md +274 -6
- package/template/handoffs/releases/vX.Y.Z-release-notes.md.example +21 -0
- package/template/scripts/check_intake_template_parity.py +90 -0
- package/template/scripts/dev_environment_lib.py +138 -0
- package/template/scripts/model_tier_lib.py +680 -0
- package/template/scripts/model_tier_validate.py +368 -0
- package/template/scripts/release-all.sh +249 -0
- package/template/scripts/release_changelog_backfill.py +153 -0
- package/template/scripts/release_changelog_lib.py +544 -0
- package/template/scripts/release_changelog_validate.py +134 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Validate release changelog / version-doc consistency (US-0100 / DEC-0085).
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import os
|
|
10
|
+
import re
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
14
|
+
_REPO_ROOT = os.path.normpath(os.path.join(_SCRIPT_DIR, ".."))
|
|
15
|
+
if _REPO_ROOT not in sys.path:
|
|
16
|
+
sys.path.insert(0, _REPO_ROOT)
|
|
17
|
+
if _SCRIPT_DIR not in sys.path:
|
|
18
|
+
sys.path.insert(0, _SCRIPT_DIR)
|
|
19
|
+
|
|
20
|
+
import release_changelog_lib as rcl # noqa: E402
|
|
21
|
+
|
|
22
|
+
REMEDIATION = {
|
|
23
|
+
rcl.RELEASE_CHANGELOG_VERSION_MISSING: "Set explicit semver on queue row or pass valid --semver.",
|
|
24
|
+
rcl.RELEASE_CHANGELOG_DUPLICATE_VERSION: "Resolve fingerprint mismatch; do not reuse semver for different work items.",
|
|
25
|
+
rcl.RELEASE_CHANGELOG_WORK_ITEM_GAP: "Run derive/build_version_doc or complete sprint notes + backlog refs.",
|
|
26
|
+
rcl.RELEASE_CHANGELOG_ORDER_INVALID: "Reorder CHANGELOG.md semver sections newest-first.",
|
|
27
|
+
rcl.RELEASE_CHANGELOG_UNRELEASED_MISSING: "Add mandatory ## [Unreleased] header at top of CHANGELOG.md.",
|
|
28
|
+
rcl.RELEASE_CHANGELOG_QUEUE_DRIFT: "Re-run bind_queue_release_version or reconcile queue release_version.",
|
|
29
|
+
rcl.RELEASE_CHANGELOG_VERSION_DOC_MISSING: "Run build_version_doc or release_changelog_backfill before gh -F attach.",
|
|
30
|
+
rcl.RELEASE_CHANGELOG_SPRINT_ORPHAN: "Include released sprint in semver doc or [Unreleased] via backfill.",
|
|
31
|
+
rcl.RELEASE_CHANGELOG_BACKFILL_AMBIGUOUS: "Fix manifest collision; one semver intent per coalesce group.",
|
|
32
|
+
rcl.RELEASE_CHANGELOG_IDEMPOTENCY_VIOLATION: "Remove duplicate work-item bullets for same version; re-run derive.",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _emit(code: str, detail: str = "") -> None:
|
|
37
|
+
msg = code if not detail else f"{code}: {detail}"
|
|
38
|
+
print(msg, file=sys.stderr)
|
|
39
|
+
rem = REMEDIATION.get(code, "See docs/engineering/runbook.md § Version-scoped release docs.")
|
|
40
|
+
print(f"Remediation: {rem}", file=sys.stderr)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def validate(repo_root: str, enforce: bool) -> int:
|
|
44
|
+
errors: list[str] = []
|
|
45
|
+
cl_path = rcl.changelog_path(repo_root)
|
|
46
|
+
if not os.path.isfile(cl_path):
|
|
47
|
+
errors.append(rcl.RELEASE_CHANGELOG_UNRELEASED_MISSING)
|
|
48
|
+
else:
|
|
49
|
+
text = rcl.read_utf8(cl_path)
|
|
50
|
+
if "## [Unreleased]" not in text:
|
|
51
|
+
errors.append(rcl.RELEASE_CHANGELOG_UNRELEASED_MISSING)
|
|
52
|
+
# semver order newest-first (skip Unreleased)
|
|
53
|
+
versions: list[str] = []
|
|
54
|
+
for m in re.finditer(r"^##\s+\[(.+?)\]", text, re.MULTILINE):
|
|
55
|
+
v = m.group(1)
|
|
56
|
+
if v.lower() == "unreleased":
|
|
57
|
+
continue
|
|
58
|
+
try:
|
|
59
|
+
versions.append(rcl.normalize_semver(v))
|
|
60
|
+
except rcl.ReleaseChangelogError:
|
|
61
|
+
errors.append(rcl.RELEASE_CHANGELOG_VERSION_MISSING)
|
|
62
|
+
# Simple order check: compare as strings after normalization (pre-release aware minimal)
|
|
63
|
+
if len(versions) > 1:
|
|
64
|
+
for i in range(len(versions) - 1):
|
|
65
|
+
if versions[i] < versions[i + 1]:
|
|
66
|
+
errors.append(rcl.RELEASE_CHANGELOG_ORDER_INVALID)
|
|
67
|
+
break
|
|
68
|
+
|
|
69
|
+
rows = rcl.parse_queue_rows(repo_root)
|
|
70
|
+
for row in rows:
|
|
71
|
+
if row.status != "released":
|
|
72
|
+
continue
|
|
73
|
+
rv = row.release_version.strip()
|
|
74
|
+
if not rv:
|
|
75
|
+
continue
|
|
76
|
+
try:
|
|
77
|
+
norm = rcl.normalize_semver(rv)
|
|
78
|
+
except rcl.ReleaseChangelogError:
|
|
79
|
+
errors.append(rcl.RELEASE_CHANGELOG_VERSION_MISSING)
|
|
80
|
+
continue
|
|
81
|
+
vdoc = rcl.version_doc_path(repo_root, norm)
|
|
82
|
+
if not os.path.isfile(vdoc):
|
|
83
|
+
errors.append(rcl.RELEASE_CHANGELOG_VERSION_DOC_MISSING)
|
|
84
|
+
section = rcl.extract_changelog_section(norm, repo_root)
|
|
85
|
+
if section is None:
|
|
86
|
+
errors.append(rcl.RELEASE_CHANGELOG_VERSION_MISSING)
|
|
87
|
+
# work item gap: story_refs should appear in version doc
|
|
88
|
+
if os.path.isfile(vdoc):
|
|
89
|
+
doc_text = rcl.read_utf8(vdoc)
|
|
90
|
+
for token in re.split(r"[,;\s]+", row.story_refs):
|
|
91
|
+
token = token.strip()
|
|
92
|
+
if token and token not in doc_text:
|
|
93
|
+
errors.append(rcl.RELEASE_CHANGELOG_WORK_ITEM_GAP)
|
|
94
|
+
|
|
95
|
+
# queue drift: coalesce groups vs bound semver
|
|
96
|
+
groups = rcl.coalesce_sprints_by_semver(rows, repo_root)
|
|
97
|
+
for semver, sids in groups.items():
|
|
98
|
+
for sid in sids:
|
|
99
|
+
row = next((r for r in rows if r.sprint_id == sid), None)
|
|
100
|
+
if row and row.release_version.strip():
|
|
101
|
+
try:
|
|
102
|
+
if rcl.normalize_semver(row.release_version) != semver:
|
|
103
|
+
errors.append(rcl.RELEASE_CHANGELOG_QUEUE_DRIFT)
|
|
104
|
+
except rcl.ReleaseChangelogError:
|
|
105
|
+
errors.append(rcl.RELEASE_CHANGELOG_QUEUE_DRIFT)
|
|
106
|
+
|
|
107
|
+
seen_errors = sorted(set(errors))
|
|
108
|
+
for code in seen_errors:
|
|
109
|
+
_emit(code)
|
|
110
|
+
if seen_errors and enforce:
|
|
111
|
+
return 1
|
|
112
|
+
if not seen_errors:
|
|
113
|
+
print("[RELEASE_CHANGELOG_VALIDATE_OK]")
|
|
114
|
+
return 0
|
|
115
|
+
if not enforce:
|
|
116
|
+
print("[RELEASE_CHANGELOG_VALIDATE_WARN]")
|
|
117
|
+
return 0
|
|
118
|
+
return 1
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def main() -> int:
|
|
122
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
123
|
+
parser.add_argument("--repo", default=_REPO_ROOT, help="Repository root")
|
|
124
|
+
parser.add_argument(
|
|
125
|
+
"--enforce",
|
|
126
|
+
action="store_true",
|
|
127
|
+
help="Exit non-zero on any fail code (release gate / release-all.sh).",
|
|
128
|
+
)
|
|
129
|
+
args = parser.parse_args()
|
|
130
|
+
return validate(os.path.abspath(args.repo), args.enforce)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
if __name__ == "__main__":
|
|
134
|
+
raise SystemExit(main())
|