its-magic 0.1.2-9 → 0.1.2

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 (232) hide show
  1. package/README.md +1602 -755
  2. package/bin/its-magic.js +121 -11
  3. package/bin/postinstall.js +21 -0
  4. package/installer.ps1 +759 -0
  5. package/installer.py +981 -0
  6. package/installer.sh +649 -0
  7. package/package.json +18 -14
  8. package/scripts/check_intake_template_parity.py +284 -0
  9. package/scripts/doc_profile_lib.py +415 -0
  10. package/scripts/guard_installer_publish.py +88 -0
  11. package/scripts/intake_bug_routing_guard.py +67 -0
  12. package/scripts/intake_evidence_lib.py +802 -0
  13. package/scripts/intake_evidence_validate.py +73 -0
  14. package/scripts/materialize_codebase_map.py +184 -0
  15. package/scripts/remote_config_summary.py +243 -0
  16. package/template/.cursor/agents/curator.mdc +35 -0
  17. package/template/.cursor/agents/dev.mdc +29 -0
  18. package/template/.cursor/agents/po.mdc +141 -0
  19. package/template/.cursor/agents/qa.mdc +28 -0
  20. package/template/.cursor/agents/release.mdc +28 -0
  21. package/template/.cursor/agents/security.mdc +98 -0
  22. package/template/.cursor/agents/tech-lead.mdc +57 -0
  23. package/template/.cursor/commands/architecture.md +127 -0
  24. package/template/.cursor/commands/ask.md +55 -0
  25. package/template/.cursor/commands/auto.md +533 -0
  26. package/template/.cursor/commands/discovery.md +47 -0
  27. package/template/.cursor/commands/execute.md +343 -0
  28. package/template/.cursor/commands/intake.md +323 -0
  29. package/template/.cursor/commands/map-codebase.md +46 -0
  30. package/template/.cursor/commands/memory-audit.md +175 -0
  31. package/template/.cursor/commands/milestone-complete.md +51 -0
  32. package/template/.cursor/commands/milestone-start.md +59 -0
  33. package/template/.cursor/commands/pause.md +64 -0
  34. package/{.cursor/commands/gsd-phase-context.md → template/.cursor/commands/phase-context.md} +6 -2
  35. package/template/.cursor/commands/plan-verify.md +34 -0
  36. package/template/.cursor/commands/qa.md +226 -0
  37. package/template/.cursor/commands/quick.md +41 -0
  38. package/template/.cursor/commands/refresh-context.md +82 -0
  39. package/template/.cursor/commands/release.md +505 -0
  40. package/template/.cursor/commands/research.md +49 -0
  41. package/template/.cursor/commands/resume.md +67 -0
  42. package/template/.cursor/commands/security-review.md +81 -0
  43. package/template/.cursor/commands/sprint-plan.md +103 -0
  44. package/template/.cursor/commands/status-reconcile.md +95 -0
  45. package/template/.cursor/commands/verify-work.md +152 -0
  46. package/template/.cursor/dev-environment.json.example +22 -0
  47. package/{.cursor → template/.cursor}/hooks/README.md +3 -2
  48. package/{.cursor/hooks/gsd-hook.py → template/.cursor/hooks/hook.py} +15 -6
  49. package/template/.cursor/hooks.json +26 -0
  50. package/template/.cursor/remote.json +31 -0
  51. package/template/.cursor/rules/caveman.mdc +184 -0
  52. package/template/.cursor/rules/coding-standards.mdc +39 -0
  53. package/template/.cursor/rules/core.mdc +111 -0
  54. package/template/.cursor/rules/handoffs.mdc +27 -0
  55. package/template/.cursor/rules/quality.mdc +49 -0
  56. package/template/.cursor/scratchpad.local.example.md +323 -0
  57. package/template/.cursor/scratchpad.md +324 -0
  58. package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/SKILL.md +2 -2
  59. package/template/.cursorignore +6 -0
  60. package/template/.env.example +28 -0
  61. package/template/.github/workflows/ci.yml +194 -0
  62. package/{.github → template/.github}/workflows/deploy.yml +5 -2
  63. package/template/.its-magic-version +1 -0
  64. package/template/README.md +1602 -0
  65. package/template/decisions/DEC-0001.md +11 -0
  66. package/template/decisions/DEC-0002.md +11 -0
  67. package/template/docs/developer/README.md +44 -0
  68. package/template/docs/engineering/architecture.md +9 -0
  69. package/template/docs/engineering/artifact-ordering-policy.md +48 -0
  70. package/template/docs/engineering/artifact-ownership-policy.md +57 -0
  71. package/template/docs/engineering/auto-orchestration-reference.md +1211 -0
  72. package/{docs → template/docs}/engineering/codebase-map.md +0 -5
  73. package/template/docs/engineering/compatibility-report.md +20 -0
  74. package/template/docs/engineering/compatibility-signals.md +7 -0
  75. package/template/docs/engineering/component-scope-report.md +16 -0
  76. package/template/docs/engineering/component-scope.md +19 -0
  77. package/template/docs/engineering/context/installer-owned-paths.manifest +96 -0
  78. package/template/docs/engineering/context/readme-section-affinity.json +30 -0
  79. package/template/docs/engineering/decisions.md +16 -0
  80. package/template/docs/engineering/legacy-drift-audit.md +21 -0
  81. package/template/docs/engineering/manifests/components/api-gateway.manifest.yaml +12 -0
  82. package/template/docs/engineering/manifests/registry.manifest.yaml +20 -0
  83. package/template/docs/engineering/manifests/repo.manifest.yaml +11 -0
  84. package/template/docs/engineering/phase-context.md +16 -0
  85. package/template/docs/engineering/release-targets.json +123 -0
  86. package/template/docs/engineering/research.md +29 -0
  87. package/template/docs/engineering/runbook.md +2320 -0
  88. package/template/docs/engineering/runtime-connectivity.md +81 -0
  89. package/template/docs/engineering/security-review.md +33 -0
  90. package/template/docs/engineering/spec-pack/README.md +20 -0
  91. package/template/docs/engineering/state-archive/README.md +15 -0
  92. package/template/docs/engineering/state.md +20 -0
  93. package/template/docs/engineering/status-normalization-report.md +19 -0
  94. package/template/docs/engineering/token-cost-parity-manifest.md +16 -0
  95. package/template/docs/engineering/us-0084-remote-e2e.md +44 -0
  96. package/template/docs/product/acceptance.md +1 -0
  97. package/template/docs/product/backlog.md +5 -0
  98. package/template/docs/product/vision.md +11 -0
  99. package/template/docs/user-guides/README.md +40 -0
  100. package/{handoffs → template/handoffs}/dev_to_qa.md +0 -3
  101. package/{handoffs → template/handoffs}/po_to_tl.md +0 -3
  102. package/{handoffs → template/handoffs}/qa_to_dev.md +1 -2
  103. package/template/handoffs/release_notes.md +51 -0
  104. package/template/handoffs/release_queue.md +47 -0
  105. package/template/handoffs/release_to_dev.md +31 -0
  106. package/template/handoffs/releases/Sxxxx-release-notes.md +62 -0
  107. package/template/handoffs/resume_brief.md +14 -0
  108. package/{handoffs → template/handoffs}/tl_to_dev.md +0 -2
  109. package/template/handoffs/token_cost_runs/README.md +26 -0
  110. package/template/its_magic/.its-magic-version +1 -0
  111. package/template/its_magic/README.md +9 -0
  112. package/template/scripts/auto_outer_driver.py +521 -0
  113. package/template/scripts/caveman_compress_input.py +903 -0
  114. package/template/scripts/check_downstream_ci_guard.py +67 -0
  115. package/template/scripts/check_intake_template_parity.py +284 -0
  116. package/template/scripts/check_token_cost_parity.py +69 -0
  117. package/template/scripts/dev_environment_lib.py +463 -0
  118. package/template/scripts/doc_profile_lib.py +415 -0
  119. package/template/scripts/downstream_ci_guard_lib.py +222 -0
  120. package/template/scripts/enforce-triad-hot-surface.py +753 -0
  121. package/template/scripts/guard_installer_publish.py +88 -0
  122. package/template/scripts/intake_bug_resume_brief_refresh.py +303 -0
  123. package/template/scripts/intake_bug_routing_guard.py +67 -0
  124. package/template/scripts/intake_evidence_lib.py +802 -0
  125. package/template/scripts/intake_evidence_validate.py +73 -0
  126. package/template/scripts/materialize_codebase_map.py +184 -0
  127. package/template/scripts/pack_json_validate.py +130 -0
  128. package/template/scripts/project_readme_coverage_lib.py +417 -0
  129. package/template/scripts/readme_feature_coverage_lib.py +608 -0
  130. package/template/scripts/remote_config_summary.py +243 -0
  131. package/template/scripts/sync_push_gates.py +198 -0
  132. package/template/scripts/token_cost_compare.py +40 -0
  133. package/template/scripts/token_cost_lib.py +108 -0
  134. package/template/scripts/uat_probe_lib.py +868 -0
  135. package/template/scripts/validate-and-push.ps1 +280 -0
  136. package/template/scripts/validate-and-push.sh +243 -0
  137. package/template/scripts/validate_doc_profile.py +103 -0
  138. package/template/scripts/validate_project_readme_coverage.py +151 -0
  139. package/template/scripts/validate_readme_feature_coverage.py +140 -0
  140. package/template/sprints/S0001/progress.md +1 -0
  141. package/template/sprints/S0001/qa-findings.md +9 -0
  142. package/template/sprints/S0001/release-findings.md +24 -0
  143. package/template/sprints/S0001/sprint.md +9 -0
  144. package/template/sprints/S0001/summary.md +7 -0
  145. package/template/sprints/S0001/tasks.md +1 -0
  146. package/template/sprints/S0001/uat.json +6 -0
  147. package/template/sprints/S0001/uat.md +5 -0
  148. package/template/sprints/quick/Q0001/summary.md +1 -0
  149. package/{sprints → template/sprints}/quick/Q0001/task.json +0 -1
  150. package/.cursor/agents/curator.mdc +0 -21
  151. package/.cursor/agents/dev.mdc +0 -20
  152. package/.cursor/agents/po.mdc +0 -19
  153. package/.cursor/agents/qa.mdc +0 -19
  154. package/.cursor/agents/release.mdc +0 -19
  155. package/.cursor/agents/tech-lead.mdc +0 -21
  156. package/.cursor/commands/gsd-architecture.md +0 -29
  157. package/.cursor/commands/gsd-auto.md +0 -27
  158. package/.cursor/commands/gsd-discovery.md +0 -27
  159. package/.cursor/commands/gsd-execute.md +0 -32
  160. package/.cursor/commands/gsd-intake.md +0 -28
  161. package/.cursor/commands/gsd-map-codebase.md +0 -25
  162. package/.cursor/commands/gsd-milestone-complete.md +0 -24
  163. package/.cursor/commands/gsd-milestone-start.md +0 -26
  164. package/.cursor/commands/gsd-pause.md +0 -25
  165. package/.cursor/commands/gsd-plan-verify.md +0 -26
  166. package/.cursor/commands/gsd-qa.md +0 -28
  167. package/.cursor/commands/gsd-quick.md +0 -24
  168. package/.cursor/commands/gsd-refresh-context.md +0 -26
  169. package/.cursor/commands/gsd-release.md +0 -29
  170. package/.cursor/commands/gsd-research.md +0 -28
  171. package/.cursor/commands/gsd-resume.md +0 -26
  172. package/.cursor/commands/gsd-sprint-plan.md +0 -30
  173. package/.cursor/commands/gsd-verify-work.md +0 -25
  174. package/.cursor/hooks.json +0 -26
  175. package/.cursor/plans/cursor-gsd-team-kit_8cfee9b8.plan.md +0 -57
  176. package/.cursor/remote.json +0 -18
  177. package/.cursor/rules/gsd-core.mdc +0 -18
  178. package/.cursor/rules/gsd-handoffs.mdc +0 -10
  179. package/.cursor/rules/gsd-quality.mdc +0 -15
  180. package/.cursor/scratchpad.md +0 -34
  181. package/.github/workflows/ci.yml +0 -47
  182. package/decisions/DEC-0001.md +0 -21
  183. package/decisions/DEC-0002.md +0 -21
  184. package/docs/engineering/architecture.md +0 -354
  185. package/docs/engineering/decisions.md +0 -6
  186. package/docs/engineering/research.md +0 -11
  187. package/docs/engineering/runbook.md +0 -32
  188. package/docs/engineering/state.md +0 -33
  189. package/docs/product/acceptance.md +0 -6
  190. package/docs/product/backlog.md +0 -7
  191. package/docs/product/vision.md +0 -46
  192. package/gsd-installer.ps1 +0 -189
  193. package/gsd-installer.py +0 -195
  194. package/gsd-installer.sh +0 -201
  195. package/handoffs/release_notes.md +0 -14
  196. package/handoffs/resume_brief.md +0 -8
  197. package/milestones/M0001/milestone.json +0 -7
  198. package/milestones/M0001/phases.json +0 -9
  199. package/milestones/M0001/progress.md +0 -3
  200. package/milestones/M0001/summary.md +0 -3
  201. package/scripts/generate-release-notes.ps1 +0 -74
  202. package/scripts/generate-release-notes.sh +0 -63
  203. package/scripts/release-all.ps1 +0 -423
  204. package/scripts/release-all.sh +0 -226
  205. package/sprints/S0001/progress.md +0 -4
  206. package/sprints/S0001/qa-findings.md +0 -113
  207. package/sprints/S0001/sprint.md +0 -70
  208. package/sprints/S0001/summary.md +0 -46
  209. package/sprints/S0001/tasks.md +0 -35
  210. package/sprints/S0001/uat.json +0 -8
  211. package/sprints/S0001/uat.md +0 -8
  212. package/sprints/quick/Q0001/summary.md +0 -3
  213. /package/{.cursor/rules/gsd-escalation.mdc → template/.cursor/rules/escalation.mdc} +0 -0
  214. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/acceptance.json +0 -0
  215. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/acceptance.md +0 -0
  216. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/architecture.json +0 -0
  217. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/architecture.md +0 -0
  218. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/decision.json +0 -0
  219. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/decision.md +0 -0
  220. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/handoff.json +0 -0
  221. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/handoff.md +0 -0
  222. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/milestone.json +0 -0
  223. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/phase-context.json +0 -0
  224. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/plan-verify.json +0 -0
  225. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/sprint.json +0 -0
  226. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/sprint.md +0 -0
  227. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/story.json +0 -0
  228. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/story.md +0 -0
  229. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/uat.json +0 -0
  230. /package/{docs → template/docs}/engineering/context/phase-template.json +0 -0
  231. /package/{docs → template/docs}/engineering/dependencies.json +0 -0
  232. /package/{sprints → template/sprints}/S0001/plan-verify.json +0 -0
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Validate intake_evidence JSON bundles (US-0078 / US-0083 / DEC-0060 / DEC-0067).
4
+
5
+ Used by PO workflow preflight and CI fixtures.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import json
12
+ import os
13
+ import sys
14
+
15
+ _SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
16
+ _REPO_ROOT = os.path.normpath(os.path.join(_SCRIPT_DIR, ".."))
17
+ if _SCRIPT_DIR not in sys.path:
18
+ sys.path.insert(0, _SCRIPT_DIR)
19
+
20
+ import intake_evidence_lib # noqa: E402
21
+
22
+
23
+ def main() -> int:
24
+ p = argparse.ArgumentParser(description="Validate intake_evidence JSON (US-0078/US-0083).")
25
+ p.add_argument("--file", help="Path to JSON file containing one intake_evidence object.")
26
+ p.add_argument(
27
+ "--stdin",
28
+ action="store_true",
29
+ help="Read JSON object from stdin.",
30
+ )
31
+ p.add_argument(
32
+ "--self-test",
33
+ action="store_true",
34
+ help="Run library sanity checks and exit.",
35
+ )
36
+ args = p.parse_args()
37
+
38
+ if args.self_test:
39
+ intake_evidence_lib.self_test()
40
+ print("[INTAKE_EVIDENCE_SELF_TEST_OK]")
41
+ return 0
42
+
43
+ raw = ""
44
+ if args.file:
45
+ with open(os.path.abspath(args.file), encoding="utf-8") as f:
46
+ raw = f.read()
47
+ elif args.stdin:
48
+ raw = sys.stdin.read()
49
+ else:
50
+ print("error: specify --file, --stdin, or --self-test", file=sys.stderr)
51
+ return 2
52
+
53
+ try:
54
+ bundle = json.loads(raw)
55
+ except json.JSONDecodeError as e:
56
+ print(f"error: invalid JSON: {e}", file=sys.stderr)
57
+ return 2
58
+
59
+ if not isinstance(bundle, dict):
60
+ print("error: root must be a JSON object", file=sys.stderr)
61
+ return 2
62
+
63
+ r = intake_evidence_lib.validate_intake_evidence(bundle)
64
+ if r.ok:
65
+ print("[INTAKE_EVIDENCE_VALIDATION_OK]")
66
+ return 0
67
+
68
+ print(intake_evidence_lib.format_blocked_message(r), file=sys.stderr)
69
+ return 1
70
+
71
+
72
+ if __name__ == "__main__":
73
+ raise SystemExit(main())
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/env python3
2
+ """Idempotent codebase map bootstrap (US-0082 / DEC-0065).
3
+
4
+ Writes only docs/engineering/codebase-map.md and docs/engineering/dependencies.json
5
+ per /map-codebase contract. Does not append docs/engineering/state.md.
6
+
7
+ Diagnostics on stdout use deterministic tokens: [CODEBASE_MAP_OK] ... and CODEBASE_MAP_BLOCKED:...
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import json
14
+ import os
15
+ from pathlib import Path
16
+
17
+ BOOTSTRAP_SENTINEL = "<!-- its-magic:codebase-map-bootstrap v1 -->"
18
+
19
+ REMEDIATION = (
20
+ "Remediation: run `/map-codebase` for a full pass; see "
21
+ "`docs/engineering/runbook.md` (Codebase map bootstrap) and "
22
+ "`docs/engineering/architecture.md` (# US-0082)."
23
+ )
24
+
25
+
26
+ def _bootstrap_map_markdown() -> str:
27
+ lines = [
28
+ "# Codebase Map",
29
+ "",
30
+ BOOTSTRAP_SENTINEL,
31
+ "",
32
+ "This is a **bootstrap** codebase map created by the lifecycle materializer "
33
+ "(**US-0082** / **DEC-0065**). It satisfies the “map exists” contract for fresh repos.",
34
+ "",
35
+ "For a full repository analysis, run **`/map-codebase`** (explicit/manual).",
36
+ "",
37
+ "## Stack",
38
+ "",
39
+ "| Aspect | Detail |",
40
+ "|--------|--------|",
41
+ "| (pending) | Run `/map-codebase` or edit this table after local analysis |",
42
+ "",
43
+ "## Entry Points",
44
+ "",
45
+ "| Entry | File | Purpose |",
46
+ "|-------|------|---------|",
47
+ "| (pending) | — | Run `/map-codebase` to populate |",
48
+ "",
49
+ "## Next steps",
50
+ "",
51
+ "1. Run **`/map-codebase`** for a deep pass, **or**",
52
+ "2. Edit this file directly with project-specific structure.",
53
+ "",
54
+ ]
55
+ return "\n".join(lines)
56
+
57
+
58
+ def _bootstrap_dependencies_obj() -> dict:
59
+ return {"libraries": [], "runtime": [], "tooling": []}
60
+
61
+
62
+ def _bootstrap_dependencies_text() -> str:
63
+ return json.dumps(_bootstrap_dependencies_obj(), sort_keys=True, indent=2) + "\n"
64
+
65
+
66
+ def _normalize_newlines(text: str) -> str:
67
+ return text.replace("\r\n", "\n").replace("\r", "\n")
68
+
69
+
70
+ def _json_stable(obj: object) -> str:
71
+ return json.dumps(obj, sort_keys=True, separators=(",", ":"))
72
+
73
+
74
+ def sync_map(map_path: Path, desired: str) -> tuple[str, bool]:
75
+ """Returns (status_token, content_mutated)."""
76
+ if not map_path.is_file():
77
+ map_path.write_text(desired, encoding="utf-8", newline="\n")
78
+ return "created", True
79
+ existing = _normalize_newlines(map_path.read_text(encoding="utf-8"))
80
+ if BOOTSTRAP_SENTINEL in existing:
81
+ if existing == desired:
82
+ return "noop", False
83
+ map_path.write_text(desired, encoding="utf-8", newline="\n")
84
+ return "refreshed_bootstrap", True
85
+ return "preserved_existing", False
86
+
87
+
88
+ def sync_dependencies(deps_path: Path, map_allows_bootstrap_deps: bool) -> tuple[str, bool]:
89
+ """When map_allows_bootstrap_deps, deps may be created/updated to bootstrap empty schema."""
90
+ desired_txt = _bootstrap_dependencies_text()
91
+ want_obj = _bootstrap_dependencies_obj()
92
+ want_stable = _json_stable(want_obj)
93
+
94
+ if not deps_path.is_file():
95
+ deps_path.write_text(desired_txt, encoding="utf-8", newline="\n")
96
+ return "deps_created", True
97
+
98
+ raw = deps_path.read_text(encoding="utf-8")
99
+ try:
100
+ cur = json.loads(raw)
101
+ except json.JSONDecodeError:
102
+ if map_allows_bootstrap_deps:
103
+ deps_path.write_text(desired_txt, encoding="utf-8", newline="\n")
104
+ return "deps_repaired", True
105
+ return "deps_preserved_existing", False
106
+
107
+ if _json_stable(cur) == want_stable:
108
+ return "deps_noop", False
109
+
110
+ if map_allows_bootstrap_deps:
111
+ deps_path.write_text(desired_txt, encoding="utf-8", newline="\n")
112
+ return "deps_refreshed", True
113
+ return "deps_preserved_existing", False
114
+
115
+
116
+ def main(argv: list[str] | None = None) -> int:
117
+ p = argparse.ArgumentParser(description="Materialize idempotent codebase map bootstrap.")
118
+ p.add_argument("--repo", default=".", help="Repository root")
119
+ p.add_argument(
120
+ "--trigger",
121
+ choices=("architecture", "map-codebase", "refresh-context"),
122
+ default="architecture",
123
+ help="Lifecycle trigger label (stdout diagnostics only)",
124
+ )
125
+ p.add_argument("--dry-run", action="store_true", help="Print actions; do not write files")
126
+ p.add_argument(
127
+ "--simulate-block",
128
+ metavar="SUBREASON",
129
+ default=None,
130
+ help="Emit CODEBASE_MAP_BLOCKED:SUBREASON and exit 2 (tests)",
131
+ )
132
+ p.add_argument(
133
+ "--check-present",
134
+ action="store_true",
135
+ help="Read-only: exit 0 if codebase-map.md exists, else CODEBASE_MAP_MISSING + exit 2",
136
+ )
137
+ args = p.parse_args(argv)
138
+
139
+ if args.simulate_block:
140
+ print(f"[CODEBASE_MAP_BLOCKED:{args.simulate_block}] trigger={args.trigger}")
141
+ print(REMEDIATION)
142
+ return 2
143
+
144
+ if os.environ.get("CODEBASE_MAP_LIFECYCLE_SKIP", "").strip() in ("1", "true", "yes"):
145
+ print(f"[CODEBASE_MAP_BLOCKED:policy_skip] trigger={args.trigger}")
146
+ print(REMEDIATION)
147
+ return 2
148
+
149
+ root = Path(args.repo).resolve()
150
+ eng = root / "docs" / "engineering"
151
+ map_path = eng / "codebase-map.md"
152
+ deps_path = eng / "dependencies.json"
153
+
154
+ if args.check_present:
155
+ if map_path.is_file():
156
+ print(f"[CODEBASE_MAP_OK] check_present trigger={args.trigger} path={map_path}")
157
+ return 0
158
+ print("[CODEBASE_MAP_MISSING]")
159
+ print(REMEDIATION)
160
+ return 2
161
+
162
+ desired_map = _bootstrap_map_markdown()
163
+
164
+ if args.dry_run:
165
+ print(f"[CODEBASE_MAP_OK] dry_run trigger={args.trigger} map={map_path} deps={deps_path}")
166
+ return 0
167
+
168
+ eng.mkdir(parents=True, exist_ok=True)
169
+
170
+ map_status, _ = sync_map(map_path, desired_map)
171
+ print(f"[CODEBASE_MAP_OK] {map_status} trigger={args.trigger} path={map_path}")
172
+
173
+ map_text = _normalize_newlines(map_path.read_text(encoding="utf-8"))
174
+ map_allows_bootstrap_deps = BOOTSTRAP_SENTINEL in map_text
175
+
176
+ dep_status, _ = sync_dependencies(deps_path, map_allows_bootstrap_deps)
177
+ if dep_status not in ("deps_noop", "deps_preserved_existing"):
178
+ print(f"[CODEBASE_MAP_OK] {dep_status} trigger={args.trigger} path={deps_path}")
179
+
180
+ return 0
181
+
182
+
183
+ if __name__ == "__main__":
184
+ raise SystemExit(main())
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env python3
2
+ """Validate work/<story_id>/pack.json schema v1 (US-0096 / DEC-0082)."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import sys
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ REQUIRED_FIELDS = (
13
+ "schema_version",
14
+ "story_id",
15
+ "delivery_mode",
16
+ "status",
17
+ "ac",
18
+ "tasks",
19
+ "refs",
20
+ "deltas",
21
+ "memory_layer",
22
+ )
23
+
24
+ VALID_DELIVERY_MODES = frozenset({"standard", "ultra_lean", "mega_quick"})
25
+ VALID_MEMORY_LAYER = "pack"
26
+
27
+
28
+ def validate_pack(data: Any, *, expected_story_id: str | None = None) -> list[str]:
29
+ """Return PACK_* reason codes; empty list means valid."""
30
+ codes: list[str] = []
31
+ if not isinstance(data, dict):
32
+ return ["PACK_INVALID_ROOT"]
33
+
34
+ for field in REQUIRED_FIELDS:
35
+ if field not in data:
36
+ codes.append(f"PACK_MISSING_REQUIRED_FIELD:{field}")
37
+
38
+ if codes:
39
+ return codes
40
+
41
+ if data.get("schema_version") != "1":
42
+ codes.append("PACK_SCHEMA_VERSION_INVALID")
43
+
44
+ story_id = data.get("story_id")
45
+ if not isinstance(story_id, str) or not story_id.strip():
46
+ codes.append("PACK_STORY_ID_INVALID")
47
+ elif expected_story_id and story_id != expected_story_id:
48
+ codes.append("PACK_STORY_ID_MISMATCH")
49
+
50
+ delivery_mode = data.get("delivery_mode")
51
+ if delivery_mode not in VALID_DELIVERY_MODES:
52
+ codes.append("PACK_DELIVERY_MODE_INVALID")
53
+
54
+ if not isinstance(data.get("status"), str) or not str(data["status"]).strip():
55
+ codes.append("PACK_STATUS_INVALID")
56
+
57
+ for list_field in ("ac", "tasks", "refs", "deltas"):
58
+ if not isinstance(data.get(list_field), list):
59
+ codes.append(f"PACK_INVALID_FIELD_TYPE:{list_field}")
60
+
61
+ if data.get("memory_layer") != VALID_MEMORY_LAYER:
62
+ codes.append("PACK_MEMORY_LAYER_INVALID")
63
+
64
+ return codes
65
+
66
+
67
+ def main() -> int:
68
+ p = argparse.ArgumentParser(description="Validate pack.json schema v1 (US-0096).")
69
+ p.add_argument("--file", type=Path, help="Path to pack.json")
70
+ p.add_argument(
71
+ "--story-id",
72
+ help="Expected story_id (e.g. US-0096); mismatch → PACK_STORY_ID_MISMATCH",
73
+ )
74
+ p.add_argument(
75
+ "--self-test",
76
+ action="store_true",
77
+ help="Run built-in fixture checks and exit.",
78
+ )
79
+ args = p.parse_args()
80
+
81
+ if args.self_test:
82
+ valid = {
83
+ "schema_version": "1",
84
+ "story_id": "US-0096",
85
+ "delivery_mode": "ultra_lean",
86
+ "status": "OPEN",
87
+ "ac": ["AC-1"],
88
+ "tasks": [{"id": "T-001", "status": "pending"}],
89
+ "refs": ["decisions/DEC-0082.md"],
90
+ "deltas": [],
91
+ "memory_layer": "pack",
92
+ }
93
+ if validate_pack(valid):
94
+ print("PACK_SELF_TEST_FAIL: valid fixture rejected", file=sys.stderr)
95
+ return 1
96
+ invalid = dict(valid)
97
+ del invalid["story_id"]
98
+ if "PACK_MISSING_REQUIRED_FIELD:story_id" not in validate_pack(invalid):
99
+ print("PACK_SELF_TEST_FAIL: missing field not detected", file=sys.stderr)
100
+ return 1
101
+ print("[PACK_JSON_SELF_TEST_OK]")
102
+ return 0
103
+
104
+ if not args.file:
105
+ print("error: specify --file or --self-test", file=sys.stderr)
106
+ return 2
107
+
108
+ path = args.file.resolve()
109
+ if not path.is_file():
110
+ print(f"PACK_FILE_NOT_FOUND: {path}", file=sys.stderr)
111
+ return 2
112
+
113
+ try:
114
+ data = json.loads(path.read_text(encoding="utf-8"))
115
+ except json.JSONDecodeError as exc:
116
+ print(f"PACK_INVALID_JSON: {exc}", file=sys.stderr)
117
+ return 1
118
+
119
+ codes = validate_pack(data, expected_story_id=args.story_id)
120
+ if codes:
121
+ for code in codes:
122
+ print(code, file=sys.stderr)
123
+ return 1
124
+
125
+ print("[PACK_JSON_VALIDATION_OK]")
126
+ return 0
127
+
128
+
129
+ if __name__ == "__main__":
130
+ raise SystemExit(main())