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.
Files changed (29) hide show
  1. package/bin/postinstall.js +72 -0
  2. package/installer.py +55 -0
  3. package/package.json +1 -1
  4. package/scripts/check_intake_template_parity.py +90 -0
  5. package/template/.cursor/agents/curator.mdc +1 -0
  6. package/template/.cursor/agents/po.mdc +1 -0
  7. package/template/.cursor/agents/release.mdc +1 -0
  8. package/template/.cursor/commands/release.md +18 -0
  9. package/template/.cursor/model-catalog.local.example.cursor-only.json +9 -0
  10. package/template/.cursor/model-catalog.local.example.json +9 -0
  11. package/template/.cursor/model-catalog.local.example.level-1-easy.json +9 -0
  12. package/template/.cursor/model-catalog.local.example.level-2-complex.json +9 -0
  13. package/template/.cursor/model-catalog.local.example.level-3-mega.json +9 -0
  14. package/template/.cursor/model-catalog.local.example.level-4-super.json +9 -0
  15. package/template/.cursor/model-catalog.local.example.role-based-balanced.json +18 -0
  16. package/template/.cursor/model-catalog.local.example.role-based-highend.json +18 -0
  17. package/template/.cursor/scratchpad.local.example.md +55 -0
  18. package/template/.cursor/scratchpad.md +62 -0
  19. package/template/CHANGELOG.md +11 -0
  20. package/template/docs/engineering/runbook.md +274 -6
  21. package/template/handoffs/releases/vX.Y.Z-release-notes.md.example +21 -0
  22. package/template/scripts/check_intake_template_parity.py +90 -0
  23. package/template/scripts/dev_environment_lib.py +138 -0
  24. package/template/scripts/model_tier_lib.py +680 -0
  25. package/template/scripts/model_tier_validate.py +368 -0
  26. package/template/scripts/release-all.sh +249 -0
  27. package/template/scripts/release_changelog_backfill.py +153 -0
  28. package/template/scripts/release_changelog_lib.py +544 -0
  29. 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())