its-magic 0.1.2-9 → 0.1.3-0

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 (237) hide show
  1. package/README.md +1602 -755
  2. package/bin/its-magic.js +121 -11
  3. package/bin/postinstall.js +93 -0
  4. package/installer.ps1 +759 -0
  5. package/installer.py +1036 -0
  6. package/installer.sh +649 -0
  7. package/package.json +18 -14
  8. package/scripts/check_intake_template_parity.py +312 -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 +523 -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/CHANGELOG.md +11 -0
  65. package/template/README.md +1602 -0
  66. package/template/decisions/DEC-0001.md +11 -0
  67. package/template/decisions/DEC-0002.md +11 -0
  68. package/template/docs/developer/README.md +44 -0
  69. package/template/docs/engineering/architecture.md +9 -0
  70. package/template/docs/engineering/artifact-ordering-policy.md +48 -0
  71. package/template/docs/engineering/artifact-ownership-policy.md +57 -0
  72. package/template/docs/engineering/auto-orchestration-reference.md +1211 -0
  73. package/{docs → template/docs}/engineering/codebase-map.md +0 -5
  74. package/template/docs/engineering/compatibility-report.md +20 -0
  75. package/template/docs/engineering/compatibility-signals.md +7 -0
  76. package/template/docs/engineering/component-scope-report.md +16 -0
  77. package/template/docs/engineering/component-scope.md +19 -0
  78. package/template/docs/engineering/context/installer-owned-paths.manifest +96 -0
  79. package/template/docs/engineering/context/readme-section-affinity.json +30 -0
  80. package/template/docs/engineering/decisions.md +16 -0
  81. package/template/docs/engineering/legacy-drift-audit.md +21 -0
  82. package/template/docs/engineering/manifests/components/api-gateway.manifest.yaml +12 -0
  83. package/template/docs/engineering/manifests/registry.manifest.yaml +20 -0
  84. package/template/docs/engineering/manifests/repo.manifest.yaml +11 -0
  85. package/template/docs/engineering/phase-context.md +16 -0
  86. package/template/docs/engineering/release-targets.json +123 -0
  87. package/template/docs/engineering/research.md +29 -0
  88. package/template/docs/engineering/runbook.md +2391 -0
  89. package/template/docs/engineering/runtime-connectivity.md +81 -0
  90. package/template/docs/engineering/security-review.md +33 -0
  91. package/template/docs/engineering/spec-pack/README.md +20 -0
  92. package/template/docs/engineering/state-archive/README.md +15 -0
  93. package/template/docs/engineering/state.md +20 -0
  94. package/template/docs/engineering/status-normalization-report.md +19 -0
  95. package/template/docs/engineering/token-cost-parity-manifest.md +16 -0
  96. package/template/docs/engineering/us-0084-remote-e2e.md +44 -0
  97. package/template/docs/product/acceptance.md +1 -0
  98. package/template/docs/product/backlog.md +5 -0
  99. package/template/docs/product/vision.md +11 -0
  100. package/template/docs/user-guides/README.md +40 -0
  101. package/{handoffs → template/handoffs}/dev_to_qa.md +0 -3
  102. package/{handoffs → template/handoffs}/po_to_tl.md +0 -3
  103. package/{handoffs → template/handoffs}/qa_to_dev.md +1 -2
  104. package/template/handoffs/release_notes.md +51 -0
  105. package/template/handoffs/release_queue.md +47 -0
  106. package/template/handoffs/release_to_dev.md +31 -0
  107. package/template/handoffs/releases/Sxxxx-release-notes.md +62 -0
  108. package/template/handoffs/releases/vX.Y.Z-release-notes.md.example +21 -0
  109. package/template/handoffs/resume_brief.md +14 -0
  110. package/{handoffs → template/handoffs}/tl_to_dev.md +0 -2
  111. package/template/handoffs/token_cost_runs/README.md +26 -0
  112. package/template/its_magic/.its-magic-version +1 -0
  113. package/template/its_magic/README.md +9 -0
  114. package/template/scripts/auto_outer_driver.py +521 -0
  115. package/template/scripts/caveman_compress_input.py +903 -0
  116. package/template/scripts/check_downstream_ci_guard.py +67 -0
  117. package/template/scripts/check_intake_template_parity.py +312 -0
  118. package/template/scripts/check_token_cost_parity.py +69 -0
  119. package/template/scripts/dev_environment_lib.py +601 -0
  120. package/template/scripts/doc_profile_lib.py +415 -0
  121. package/template/scripts/downstream_ci_guard_lib.py +222 -0
  122. package/template/scripts/enforce-triad-hot-surface.py +753 -0
  123. package/template/scripts/guard_installer_publish.py +88 -0
  124. package/template/scripts/intake_bug_resume_brief_refresh.py +303 -0
  125. package/template/scripts/intake_bug_routing_guard.py +67 -0
  126. package/template/scripts/intake_evidence_lib.py +802 -0
  127. package/template/scripts/intake_evidence_validate.py +73 -0
  128. package/template/scripts/materialize_codebase_map.py +184 -0
  129. package/template/scripts/pack_json_validate.py +130 -0
  130. package/template/scripts/project_readme_coverage_lib.py +417 -0
  131. package/template/scripts/readme_feature_coverage_lib.py +608 -0
  132. package/{scripts → template/scripts}/release-all.sh +27 -4
  133. package/template/scripts/release_changelog_backfill.py +153 -0
  134. package/template/scripts/release_changelog_lib.py +544 -0
  135. package/template/scripts/release_changelog_validate.py +134 -0
  136. package/template/scripts/remote_config_summary.py +243 -0
  137. package/template/scripts/sync_push_gates.py +198 -0
  138. package/template/scripts/token_cost_compare.py +40 -0
  139. package/template/scripts/token_cost_lib.py +108 -0
  140. package/template/scripts/uat_probe_lib.py +868 -0
  141. package/template/scripts/validate-and-push.ps1 +280 -0
  142. package/template/scripts/validate-and-push.sh +243 -0
  143. package/template/scripts/validate_doc_profile.py +103 -0
  144. package/template/scripts/validate_project_readme_coverage.py +151 -0
  145. package/template/scripts/validate_readme_feature_coverage.py +140 -0
  146. package/template/sprints/S0001/progress.md +1 -0
  147. package/template/sprints/S0001/qa-findings.md +9 -0
  148. package/template/sprints/S0001/release-findings.md +24 -0
  149. package/template/sprints/S0001/sprint.md +9 -0
  150. package/template/sprints/S0001/summary.md +7 -0
  151. package/template/sprints/S0001/tasks.md +1 -0
  152. package/template/sprints/S0001/uat.json +6 -0
  153. package/template/sprints/S0001/uat.md +5 -0
  154. package/template/sprints/quick/Q0001/summary.md +1 -0
  155. package/{sprints → template/sprints}/quick/Q0001/task.json +0 -1
  156. package/.cursor/agents/curator.mdc +0 -21
  157. package/.cursor/agents/dev.mdc +0 -20
  158. package/.cursor/agents/po.mdc +0 -19
  159. package/.cursor/agents/qa.mdc +0 -19
  160. package/.cursor/agents/release.mdc +0 -19
  161. package/.cursor/agents/tech-lead.mdc +0 -21
  162. package/.cursor/commands/gsd-architecture.md +0 -29
  163. package/.cursor/commands/gsd-auto.md +0 -27
  164. package/.cursor/commands/gsd-discovery.md +0 -27
  165. package/.cursor/commands/gsd-execute.md +0 -32
  166. package/.cursor/commands/gsd-intake.md +0 -28
  167. package/.cursor/commands/gsd-map-codebase.md +0 -25
  168. package/.cursor/commands/gsd-milestone-complete.md +0 -24
  169. package/.cursor/commands/gsd-milestone-start.md +0 -26
  170. package/.cursor/commands/gsd-pause.md +0 -25
  171. package/.cursor/commands/gsd-plan-verify.md +0 -26
  172. package/.cursor/commands/gsd-qa.md +0 -28
  173. package/.cursor/commands/gsd-quick.md +0 -24
  174. package/.cursor/commands/gsd-refresh-context.md +0 -26
  175. package/.cursor/commands/gsd-release.md +0 -29
  176. package/.cursor/commands/gsd-research.md +0 -28
  177. package/.cursor/commands/gsd-resume.md +0 -26
  178. package/.cursor/commands/gsd-sprint-plan.md +0 -30
  179. package/.cursor/commands/gsd-verify-work.md +0 -25
  180. package/.cursor/hooks.json +0 -26
  181. package/.cursor/plans/cursor-gsd-team-kit_8cfee9b8.plan.md +0 -57
  182. package/.cursor/remote.json +0 -18
  183. package/.cursor/rules/gsd-core.mdc +0 -18
  184. package/.cursor/rules/gsd-handoffs.mdc +0 -10
  185. package/.cursor/rules/gsd-quality.mdc +0 -15
  186. package/.cursor/scratchpad.md +0 -34
  187. package/.github/workflows/ci.yml +0 -47
  188. package/decisions/DEC-0001.md +0 -21
  189. package/decisions/DEC-0002.md +0 -21
  190. package/docs/engineering/architecture.md +0 -354
  191. package/docs/engineering/decisions.md +0 -6
  192. package/docs/engineering/research.md +0 -11
  193. package/docs/engineering/runbook.md +0 -32
  194. package/docs/engineering/state.md +0 -33
  195. package/docs/product/acceptance.md +0 -6
  196. package/docs/product/backlog.md +0 -7
  197. package/docs/product/vision.md +0 -46
  198. package/gsd-installer.ps1 +0 -189
  199. package/gsd-installer.py +0 -195
  200. package/gsd-installer.sh +0 -201
  201. package/handoffs/release_notes.md +0 -14
  202. package/handoffs/resume_brief.md +0 -8
  203. package/milestones/M0001/milestone.json +0 -7
  204. package/milestones/M0001/phases.json +0 -9
  205. package/milestones/M0001/progress.md +0 -3
  206. package/milestones/M0001/summary.md +0 -3
  207. package/scripts/generate-release-notes.ps1 +0 -74
  208. package/scripts/generate-release-notes.sh +0 -63
  209. package/scripts/release-all.ps1 +0 -423
  210. package/sprints/S0001/progress.md +0 -4
  211. package/sprints/S0001/qa-findings.md +0 -113
  212. package/sprints/S0001/sprint.md +0 -70
  213. package/sprints/S0001/summary.md +0 -46
  214. package/sprints/S0001/tasks.md +0 -35
  215. package/sprints/S0001/uat.json +0 -8
  216. package/sprints/S0001/uat.md +0 -8
  217. package/sprints/quick/Q0001/summary.md +0 -3
  218. /package/{.cursor/rules/gsd-escalation.mdc → template/.cursor/rules/escalation.mdc} +0 -0
  219. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/acceptance.json +0 -0
  220. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/acceptance.md +0 -0
  221. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/architecture.json +0 -0
  222. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/architecture.md +0 -0
  223. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/decision.json +0 -0
  224. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/decision.md +0 -0
  225. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/handoff.json +0 -0
  226. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/handoff.md +0 -0
  227. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/milestone.json +0 -0
  228. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/phase-context.json +0 -0
  229. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/plan-verify.json +0 -0
  230. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/sprint.json +0 -0
  231. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/sprint.md +0 -0
  232. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/story.json +0 -0
  233. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/story.md +0 -0
  234. /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/uat.json +0 -0
  235. /package/{docs → template/docs}/engineering/context/phase-template.json +0 -0
  236. /package/{docs → template/docs}/engineering/dependencies.json +0 -0
  237. /package/{sprints → template/sprints}/S0001/plan-verify.json +0 -0
package/installer.sh ADDED
@@ -0,0 +1,649 @@
1
+ #!/usr/bin/env sh
2
+ set -e
3
+
4
+ # BUG-0004: keep startup shell options POSIX-safe for /bin/sh execution.
5
+ # Do not use bash-only "set" flags in this unconditional startup path.
6
+
7
+ SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
8
+ SOURCE_ROOT="$SCRIPT_DIR/template"
9
+ MANIFEST_NAME="docs/engineering/context/installer-owned-paths.manifest"
10
+ REPO_URL="https://github.com/fl0wm0ti0n/its-magic"
11
+ APP_VERSION=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$SCRIPT_DIR/package.json" 2>/dev/null | head -n 1)
12
+ [ -z "$APP_VERSION" ] && APP_VERSION="unknown"
13
+
14
+ show_banner() {
15
+ printf "\n"
16
+ printf "\033[1;35m ██╗████████╗███████╗ ███╗ ███╗ █████╗ ██████╗ ██╗ ██████╗\033[0m\n"
17
+ printf "\033[1;35m ██║╚══██╔══╝██╔════╝ ████╗ ████║██╔══██╗██╔════╝ ██║██╔════╝\033[0m\n"
18
+ printf "\033[1;35m ██║ ██║ ███████╗█████╗██╔████╔██║███████║██║ ███╗██║██║ \033[0m\n"
19
+ printf "\033[1;36m ██║ ██║ ╚════██║╚════╝██║╚██╔╝██║██╔══██║██║ ██║██║██║ \033[0m\n"
20
+ printf "\033[1;36m ██║ ██║ ███████║ ██║ ╚═╝ ██║██║ ██║╚██████╔╝██║╚██████╗\033[0m\n"
21
+ printf "\033[1;36m ╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═════╝\033[0m\n"
22
+ printf "\n"
23
+ printf "\033[1;33m AI dev team\033[0m\n"
24
+ printf "\n"
25
+ }
26
+
27
+ show_help() {
28
+ show_banner
29
+ printf "its-magic v%s\n" "$APP_VERSION"
30
+ printf "Repository: %s\n\n" "$REPO_URL"
31
+ printf "Install AI dev team workflow files into any Cursor repository.\n\n"
32
+ printf "Usage:\n"
33
+ printf " its-magic --target <path> [--mode <mode>] [--backup] [--create]\n"
34
+ printf " its-magic --clean-repo [--target <path>] [--yes]\n"
35
+ printf " its-magic --help | --version\n\n"
36
+ printf "Install options:\n"
37
+ printf " --target <path> Path to the repository where workflow files are installed.\n"
38
+ printf " If omitted you will be prompted interactively.\n"
39
+ printf " --mode <mode> How to handle files that already exist in the target:\n"
40
+ printf " missing Only copy files that do not exist yet (default).\n"
41
+ printf " Safe for repos that already have some workflow files.\n"
42
+ printf " overwrite Replace every file, even if it already exists.\n"
43
+ printf " Combine with --backup to keep a snapshot first.\n"
44
+ printf " interactive Ask per file whether to overwrite or skip.\n"
45
+ printf " upgrade Update framework files while preserving user data.\n"
46
+ printf " Use after updating its-magic to a newer version.\n"
47
+ printf " --backup Before overwriting, save existing files to backups/<timestamp>/.\n"
48
+ printf " Ignored when mode is 'missing' (nothing gets replaced).\n"
49
+ printf " --create Create the target directory if it does not exist.\n\n"
50
+ printf " Note: installer bootstraps runbook TEST/LINT/TYPECHECK commands from\n"
51
+ printf " OS+stack detection; unresolved TEST_COMMAND fails fast with\n"
52
+ printf " [RUNBOOK_BOOTSTRAP_ERROR] diagnostics.\n"
53
+ printf " Note: scratchpad Model B: .cursor/scratchpad.md is\n"
54
+ printf " materialized when missing; Python 3 on PATH is required for validation.\n"
55
+ printf " Recovery: python installer.py --scratchpad-postinstall --target <repo> --mode missing\n\n"
56
+ printf "Clean options:\n"
57
+ printf " --clean-repo Remove all its-magic workflow artifacts from the target repo\n"
58
+ printf " (owned paths from installer manifest, including .cursor,\n"
59
+ printf " docs/product, docs/engineering, docs/user-guides, sprints,\n"
60
+ printf " handoffs, decisions, workflow scripts, CI files, and\n"
61
+ printf " installer metadata under its_magic/ (legacy .its-magic-version\n"
62
+ printf " is also removed when present). Your own source code is never touched.\n"
63
+ printf " --target <path> Repo to clean (default: current directory).\n"
64
+ printf " --yes Skip the confirmation prompt.\n\n"
65
+ printf "Info:\n"
66
+ printf " --help, -h Show this help and exit.\n"
67
+ printf " --version, -v Print the installed version and exit.\n\n"
68
+ printf "Examples:\n"
69
+ printf " its-magic --target . --mode missing Safe first-time setup\n"
70
+ printf " its-magic --target . --mode upgrade Update framework, keep user data\n"
71
+ printf " its-magic --target . --mode overwrite --backup Replace all files, keep backup\n"
72
+ printf " its-magic --clean-repo --target . --yes Remove workflow artifacts silently\n\n"
73
+ }
74
+
75
+ ensure_parent() {
76
+ dir=$(dirname "$1")
77
+ [ -d "$dir" ] || mkdir -p "$dir"
78
+ }
79
+
80
+ list_source_files() {
81
+ source_root="$1"
82
+ shift
83
+ for rel in "$@"; do
84
+ src="$source_root/$rel"
85
+ if [ -f "$src" ]; then
86
+ echo "$rel"
87
+ elif [ -d "$src" ]; then
88
+ find "$src" -type f | sed "s|^$source_root/||"
89
+ fi
90
+ done | sort -u
91
+ }
92
+
93
+ get_manifest_paths() {
94
+ section="$1"
95
+ # BUG-0008: strip trailing CR so CRLF manifests (Windows-published npm tarballs)
96
+ # still match [section] headers under POSIX awk on Linux.
97
+ awk -v s="$section" '
98
+ BEGIN { in_section=0 }
99
+ {
100
+ sub(/\r$/, "")
101
+ }
102
+ /^[[:space:]]*#/ { next }
103
+ /^[[:space:]]*$/ { next }
104
+ /^\[/ {
105
+ in_section = ($0 == "[" s "]")
106
+ next
107
+ }
108
+ { if (in_section) print $0 }
109
+ ' "$OWNERSHIP_MANIFEST"
110
+ }
111
+
112
+ backup_files() {
113
+ target_root="$1"
114
+ shift
115
+ timestamp=$(date -u +"%Y%m%d-%H%M%SZ")
116
+ backup_root="$target_root/backups/$timestamp"
117
+ for rel in "$@"; do
118
+ src="$target_root/$rel"
119
+ if [ -f "$src" ]; then
120
+ dst="$backup_root/$rel"
121
+ ensure_parent "$dst"
122
+ cp -p "$src" "$dst"
123
+ fi
124
+ done
125
+ echo "$backup_root"
126
+ }
127
+
128
+ choose_mode() {
129
+ printf "%s\n" "Select install mode:"
130
+ printf "%s\n" "1) missing-only (copy only files that do not exist)"
131
+ printf "%s\n" "2) overwrite-all (replace existing files)"
132
+ printf "%s\n" "3) interactive (prompt per file)"
133
+ printf "%s\n" "4) upgrade (update framework files, preserve user data)"
134
+ printf "%s" "Enter 1, 2, 3, or 4: "
135
+ read -r choice
136
+ case "$choice" in
137
+ 1) echo "missing" ;;
138
+ 2) echo "overwrite" ;;
139
+ 4) echo "upgrade" ;;
140
+ *) echo "interactive" ;;
141
+ esac
142
+ }
143
+
144
+ scratchpad_postinstall() {
145
+ target_root="$1"
146
+ mode="$2"
147
+ installer_py="$SCRIPT_DIR/installer.py"
148
+ if [ ! -f "$installer_py" ]; then
149
+ printf "%s\n" "[SCRATCHPAD_POSTINSTALL_ERROR] installer.py missing next to installer.sh."
150
+ exit 1
151
+ fi
152
+ if command -v python3 >/dev/null 2>&1; then
153
+ python3 "$installer_py" --scratchpad-postinstall --target "$target_root" --mode "$mode" || exit $?
154
+ elif command -v python >/dev/null 2>&1; then
155
+ python "$installer_py" --scratchpad-postinstall --target "$target_root" --mode "$mode" || exit $?
156
+ else
157
+ printf "%s\n" "[SCRATCHPAD_POSTINSTALL_ERROR] PYTHON_NOT_FOUND: Python 3 is required for scratchpad materialization/validation (Model B)."
158
+ exit 1
159
+ fi
160
+ }
161
+
162
+ validate_install_completeness() {
163
+ target_root="$1"
164
+ installer_py="$SCRIPT_DIR/installer.py"
165
+ if [ ! -f "$installer_py" ]; then
166
+ printf "%s\n" "[INSTALL_COMPLETENESS_FAILED] installer.py missing next to installer.sh."
167
+ exit 1
168
+ fi
169
+ if command -v python3 >/dev/null 2>&1; then
170
+ python3 "$installer_py" --validate-install-completeness --target "$target_root" || exit $?
171
+ elif command -v python >/dev/null 2>&1; then
172
+ python "$installer_py" --validate-install-completeness --target "$target_root" || exit $?
173
+ else
174
+ printf "%s\n" "[INSTALL_COMPLETENESS_FAILED] PYTHON_NOT_FOUND: Python is required for deterministic installer completeness validation."
175
+ exit 1
176
+ fi
177
+ }
178
+
179
+ classify_file() {
180
+ rel="$1"
181
+ case "$rel" in
182
+ README.md) echo "mixed" ;;
183
+ .cursor/commands/*|.cursor/rules/*|.cursor/agents/*|.cursor/skills/*) echo "framework" ;;
184
+ .cursor/hooks/*|.cursor/hooks.json|.cursor/scratchpad.local.example.md) echo "framework" ;;
185
+ .github/workflows/*|scripts/validate-and-push*|scripts/sync_push_gates.py|docs/engineering/context/*|its_magic/*) echo "framework" ;;
186
+ .its-magic-version|its_magic/.its-magic-version|its_magic/README.md) echo "framework" ;;
187
+ docs/product/*|docs/engineering/*|docs/user-guides/*) echo "user-data" ;;
188
+ sprints/*|handoffs/*|decisions/*) echo "user-data" ;;
189
+ *) echo "framework" ;;
190
+ esac
191
+ }
192
+
193
+ read_installed_version() {
194
+ primary="$1/its_magic/.its-magic-version"
195
+ legacy="$1/.its-magic-version"
196
+ if [ -f "$primary" ]; then
197
+ cat "$primary" | tr -d '\n'
198
+ return 0
199
+ fi
200
+ if [ -f "$legacy" ]; then
201
+ cat "$legacy" | tr -d '\n'
202
+ return 0
203
+ fi
204
+ printf "unknown"
205
+ }
206
+
207
+ write_installed_version() {
208
+ vf="$1/its_magic/.its-magic-version"
209
+ ensure_parent "$vf"
210
+ printf "%s" "$2" > "$vf"
211
+ legacy="$1/.its-magic-version"
212
+ [ -f "$legacy" ] && rm -f "$legacy"
213
+ return 0
214
+ }
215
+
216
+ sync_root_readme_to_its_magic() {
217
+ target_root="$1"
218
+ [ -f "$target_root/README.md" ] || return 1
219
+ dst="$target_root/its_magic/README.md"
220
+ ensure_parent "$dst"
221
+ cp -p "$target_root/README.md" "$dst"
222
+ return 0
223
+ }
224
+
225
+ read_runbook_key() {
226
+ runbook_path="$1"
227
+ key="$2"
228
+ [ -f "$runbook_path" ] || { printf ""; return; }
229
+ awk -F: -v k="$key" '$1==k { sub(/^[[:space:]]*/, "", $2); print $2; exit }' "$runbook_path"
230
+ }
231
+
232
+ write_runbook_key() {
233
+ runbook_path="$1"
234
+ key="$2"
235
+ value="$3"
236
+ [ -f "$runbook_path" ] || return 1
237
+ tmp="$runbook_path.tmp.$$"
238
+ awk -v k="$key" -v v="$value" '
239
+ BEGIN { changed=0 }
240
+ index($0, k":") == 1 && changed==0 { print k": "v; changed=1; next }
241
+ { print $0 }
242
+ END { if (changed==0) exit 2 }
243
+ ' "$runbook_path" > "$tmp" || { rm -f "$tmp"; return 1; }
244
+ mv "$tmp" "$runbook_path"
245
+ return 0
246
+ }
247
+
248
+ package_has_script() {
249
+ target_root="$1"
250
+ script_name="$2"
251
+ pkg="$target_root/package.json"
252
+ [ -f "$pkg" ] || return 1
253
+ command -v node >/dev/null 2>&1 || return 1
254
+ node -e "const fs=require('fs');const p=JSON.parse(fs.readFileSync(process.argv[1],'utf8'));const s=(p.scripts||{})[process.argv[2]];process.exit((typeof s==='string'&&s.trim())?0:1);" "$pkg" "$script_name" >/dev/null 2>&1
255
+ }
256
+
257
+ detect_runbook_defaults() {
258
+ target_root="$1"
259
+ TEST_CANDIDATE=""
260
+ LINT_CANDIDATE=""
261
+ TYPECHECK_CANDIDATE=""
262
+ if [ -f "$target_root/package.json" ] && package_has_script "$target_root" "test"; then
263
+ TEST_CANDIDATE="npm run test"
264
+ package_has_script "$target_root" "lint" && LINT_CANDIDATE="npm run lint"
265
+ package_has_script "$target_root" "typecheck" && TYPECHECK_CANDIDATE="npm run typecheck"
266
+ return 0
267
+ fi
268
+ if [ -f "$target_root/go.mod" ]; then
269
+ TEST_CANDIDATE="go test ./..."
270
+ return 0
271
+ fi
272
+ if [ -f "$target_root/pyproject.toml" ] || [ -f "$target_root/requirements.txt" ] || [ -f "$target_root/setup.py" ]; then
273
+ TEST_CANDIDATE="python -m pytest"
274
+ return 0
275
+ fi
276
+ if [ -f "$target_root/tests/run-tests.sh" ]; then
277
+ TEST_CANDIDATE="sh tests/run-tests.sh"
278
+ return 0
279
+ fi
280
+ }
281
+
282
+ validate_bootstrap_command() {
283
+ target_root="$1"
284
+ key="$2"
285
+ cmd="$3"
286
+ [ -n "$cmd" ] || { BOOTSTRAP_VALID="false"; BOOTSTRAP_REASON="${key}_UNDETECTED"; return 0; }
287
+ case "$cmd" in
288
+ "npm run "*)
289
+ command -v npm >/dev/null 2>&1 || { BOOTSTRAP_VALID="false"; BOOTSTRAP_REASON="NPM_NOT_FOUND"; return 0; }
290
+ script_name=$(printf "%s" "$cmd" | sed 's/^npm run[[:space:]]\+//')
291
+ package_has_script "$target_root" "$script_name" || { BOOTSTRAP_VALID="false"; BOOTSTRAP_REASON="NPM_SCRIPT_MISSING:$script_name"; return 0; }
292
+ BOOTSTRAP_VALID="true"; BOOTSTRAP_REASON="OK"; return 0
293
+ ;;
294
+ "python -m pytest")
295
+ command -v python >/dev/null 2>&1 || { BOOTSTRAP_VALID="false"; BOOTSTRAP_REASON="PYTHON_NOT_FOUND"; return 0; }
296
+ BOOTSTRAP_VALID="true"; BOOTSTRAP_REASON="OK"; return 0
297
+ ;;
298
+ "go test "*)
299
+ command -v go >/dev/null 2>&1 || { BOOTSTRAP_VALID="false"; BOOTSTRAP_REASON="GO_NOT_FOUND"; return 0; }
300
+ [ -f "$target_root/go.mod" ] || { BOOTSTRAP_VALID="false"; BOOTSTRAP_REASON="GO_MOD_MISSING"; return 0; }
301
+ BOOTSTRAP_VALID="true"; BOOTSTRAP_REASON="OK"; return 0
302
+ ;;
303
+ "sh "*)
304
+ command -v sh >/dev/null 2>&1 || { BOOTSTRAP_VALID="false"; BOOTSTRAP_REASON="SH_NOT_FOUND"; return 0; }
305
+ [ -f "$target_root/tests/run-tests.sh" ] || { BOOTSTRAP_VALID="false"; BOOTSTRAP_REASON="RUN_TESTS_SH_MISSING"; return 0; }
306
+ BOOTSTRAP_VALID="true"; BOOTSTRAP_REASON="OK"; return 0
307
+ ;;
308
+ esac
309
+ exe=$(printf "%s" "$cmd" | awk '{print $1}')
310
+ command -v "$exe" >/dev/null 2>&1 || { BOOTSTRAP_VALID="false"; BOOTSTRAP_REASON="EXECUTABLE_NOT_FOUND:$exe"; return 0; }
311
+ BOOTSTRAP_VALID="true"; BOOTSTRAP_REASON="OK"
312
+ }
313
+
314
+ bootstrap_runbook_commands() {
315
+ target_root="$1"
316
+ runbook="$target_root/docs/engineering/runbook.md"
317
+ [ -f "$runbook" ] || { BOOTSTRAP_OK="true"; BOOTSTRAP_NOTES=""; return 0; }
318
+ BOOTSTRAP_NOTES=""
319
+ APPLIED=""
320
+ detect_runbook_defaults "$target_root"
321
+ for key in TEST_COMMAND LINT_COMMAND TYPECHECK_COMMAND; do
322
+ current=$(read_runbook_key "$runbook" "$key")
323
+ [ -n "$current" ] && continue
324
+ candidate=""
325
+ [ "$key" = "TEST_COMMAND" ] && candidate="$TEST_CANDIDATE"
326
+ [ "$key" = "LINT_COMMAND" ] && candidate="$LINT_CANDIDATE"
327
+ [ "$key" = "TYPECHECK_COMMAND" ] && candidate="$TYPECHECK_CANDIDATE"
328
+ if [ -z "$candidate" ]; then
329
+ if [ "$key" = "TEST_COMMAND" ]; then
330
+ BOOTSTRAP_NOTES="${BOOTSTRAP_NOTES}[RUNBOOK_BOOTSTRAP_ERROR] TEST_COMMAND_UNRESOLVED: could not detect a valid baseline test command. Fix: define TEST_COMMAND in docs/engineering/runbook.md or add detectable stack markers (package.json scripts.test, pyproject.toml, go.mod)."$'\n'
331
+ fi
332
+ continue
333
+ fi
334
+ validate_bootstrap_command "$target_root" "$key" "$candidate"
335
+ if [ "$BOOTSTRAP_VALID" = "true" ]; then
336
+ if write_runbook_key "$runbook" "$key" "$candidate"; then
337
+ if [ -z "$APPLIED" ]; then APPLIED="$key=$candidate"; else APPLIED="$APPLIED, $key=$candidate"; fi
338
+ fi
339
+ elif [ "$key" = "TEST_COMMAND" ]; then
340
+ BOOTSTRAP_NOTES="${BOOTSTRAP_NOTES}[RUNBOOK_BOOTSTRAP_ERROR] TEST_COMMAND_INVALID:$BOOTSTRAP_REASON. Fix: set a valid TEST_COMMAND in docs/engineering/runbook.md."$'\n'
341
+ fi
342
+ done
343
+ [ -n "$APPLIED" ] && BOOTSTRAP_NOTES="${BOOTSTRAP_NOTES}[RUNBOOK_BOOTSTRAP] Applied defaults: $APPLIED"$'\n'
344
+ final_test=$(read_runbook_key "$runbook" "TEST_COMMAND")
345
+ if [ -n "$final_test" ]; then BOOTSTRAP_OK="true"; else BOOTSTRAP_OK="false"; fi
346
+ }
347
+
348
+ prompt_yes_no() {
349
+ label="$1"
350
+ default="$2"
351
+ suffix="y/N"
352
+ [ "$default" = "true" ] && suffix="Y/n"
353
+ printf "%s [%s]: " "$label" "$suffix"
354
+ read -r value
355
+ value=$(printf "%s" "$value" | tr 'A-Z' 'a-z')
356
+ if [ -z "$value" ]; then
357
+ [ "$default" = "true" ] && return 0 || return 1
358
+ fi
359
+ [ "$value" = "y" ] || [ "$value" = "yes" ]
360
+ }
361
+
362
+ TARGET=""
363
+ MODE=""
364
+ BACKUP="false"
365
+ CREATE="false"
366
+ CLEAN_REPO="false"
367
+ ASSUME_YES="false"
368
+ SHOW_HELP="false"
369
+ SHOW_VERSION="false"
370
+
371
+ if [ $# -eq 0 ]; then
372
+ SHOW_HELP="true"
373
+ fi
374
+
375
+ while [ $# -gt 0 ]; do
376
+ case "$1" in
377
+ --target) TARGET="$2"; shift 2 ;;
378
+ --mode) MODE="$2"; shift 2 ;;
379
+ --backup) BACKUP="true"; shift 1 ;;
380
+ --create) CREATE="true"; shift 1 ;;
381
+ --clean-repo) CLEAN_REPO="true"; shift 1 ;;
382
+ --yes) ASSUME_YES="true"; shift 1 ;;
383
+ --help|-h) SHOW_HELP="true"; shift 1 ;;
384
+ --version|-v) SHOW_VERSION="true"; shift 1 ;;
385
+ *) shift 1 ;;
386
+ esac
387
+ done
388
+
389
+ if [ "$SHOW_VERSION" = "true" ]; then
390
+ printf "its-magic v%s\n" "$APP_VERSION"
391
+ exit 0
392
+ fi
393
+
394
+ if [ "$SHOW_HELP" = "true" ]; then
395
+ show_help
396
+ exit 0
397
+ fi
398
+
399
+ if [ ! -d "$SOURCE_ROOT" ]; then
400
+ printf "%s\n" "[INSTALL_SOURCE_ERROR] template directory is missing. Reinstall its-magic package."
401
+ exit 1
402
+ fi
403
+
404
+ OWNERSHIP_MANIFEST="$SOURCE_ROOT/$MANIFEST_NAME"
405
+ if [ ! -f "$OWNERSHIP_MANIFEST" ]; then
406
+ FALLBACK_MANIFEST="$SCRIPT_DIR/$MANIFEST_NAME"
407
+ if [ -f "$FALLBACK_MANIFEST" ]; then
408
+ OWNERSHIP_MANIFEST="$FALLBACK_MANIFEST"
409
+ else
410
+ printf "%s\n" "[INSTALL_SOURCE_ERROR] installer-owned-paths.manifest not found. Reinstall its-magic package."
411
+ exit 1
412
+ fi
413
+ fi
414
+
415
+ if [ "$CLEAN_REPO" = "true" ]; then
416
+ if [ -z "$TARGET" ]; then
417
+ TARGET="."
418
+ fi
419
+ if [ ! -d "$TARGET" ]; then
420
+ printf "%s\n" "Target directory does not exist."
421
+ exit 1
422
+ fi
423
+ TARGET_ROOT=$(cd "$TARGET" && pwd)
424
+ if [ "$ASSUME_YES" != "true" ]; then
425
+ if ! prompt_yes_no "Clean its-magic workflow artifacts in $TARGET_ROOT?" "false"; then
426
+ printf "%s\n" "Aborted."
427
+ exit 1
428
+ fi
429
+ fi
430
+ CLEAN_PATHS=$(get_manifest_paths "clean_paths")
431
+ if [ -z "$CLEAN_PATHS" ]; then
432
+ printf "%s\n" "[INSTALL_MANIFEST_ERROR] clean_paths section is empty in $OWNERSHIP_MANIFEST"
433
+ exit 1
434
+ fi
435
+ for rel in $CLEAN_PATHS; do
436
+ path="$TARGET_ROOT/$rel"
437
+ if [ -e "$path" ]; then
438
+ rm -rf "$path"
439
+ printf "%s\n" "Removed: $rel"
440
+ fi
441
+ done
442
+ printf "%s\n" "Clean completed."
443
+ exit 0
444
+ fi
445
+
446
+ if [ -z "$TARGET" ]; then
447
+ printf "%s" "Target repository path: "
448
+ read -r TARGET
449
+ fi
450
+
451
+ if [ ! -d "$TARGET" ]; then
452
+ if [ "$CREATE" = "true" ] || prompt_yes_no "Target missing. Create?" "false"; then
453
+ mkdir -p "$TARGET"
454
+ else
455
+ printf "%s\n" "Target directory does not exist."
456
+ exit 1
457
+ fi
458
+ fi
459
+ TARGET_ROOT=$(cd "$TARGET" && pwd)
460
+
461
+ if [ -z "$MODE" ]; then
462
+ MODE=$(choose_mode)
463
+ fi
464
+
465
+ if [ "$MODE" = "overwrite" ] || [ "$MODE" = "interactive" ]; then
466
+ if [ "$BACKUP" = "false" ]; then
467
+ if prompt_yes_no "Backup existing files before overwrite?" "false"; then
468
+ BACKUP="true"
469
+ fi
470
+ fi
471
+ fi
472
+
473
+ INCLUDE_PATHS=$(get_manifest_paths "install_include_paths")
474
+ if [ -z "$INCLUDE_PATHS" ]; then
475
+ printf "%s\n" "[INSTALL_MANIFEST_ERROR] install_include_paths section is empty in $OWNERSHIP_MANIFEST"
476
+ exit 1
477
+ fi
478
+
479
+ FILES=$(list_source_files "$SOURCE_ROOT" $INCLUDE_PATHS)
480
+ if [ -z "$FILES" ]; then
481
+ printf "%s\n" "No source files found to install."
482
+ exit 1
483
+ fi
484
+
485
+ if [ "$BACKUP" = "true" ] && [ "$MODE" = "overwrite" ]; then
486
+ overwrite_candidates=""
487
+ for rel in $FILES; do
488
+ [ -f "$TARGET_ROOT/$rel" ] && overwrite_candidates="$overwrite_candidates $rel"
489
+ done
490
+ if [ -n "$overwrite_candidates" ]; then
491
+ backup_root=$(backup_files "$TARGET_ROOT" $overwrite_candidates)
492
+ printf "%s\n" "Backup created at: $backup_root"
493
+ fi
494
+ fi
495
+
496
+ if [ "$MODE" = "upgrade" ]; then
497
+ OLD_VER=$(read_installed_version "$TARGET_ROOT")
498
+ printf "\n\033[1;36mUpgrading from v%s to v%s\033[0m\n\n" "$OLD_VER" "$APP_VERSION"
499
+
500
+ if [ "$BACKUP" = "true" ]; then
501
+ backup_candidates=""
502
+ for rel in $FILES; do
503
+ cat=$(classify_file "$rel")
504
+ [ "$cat" = "framework" ] && [ -f "$TARGET_ROOT/$rel" ] && backup_candidates="$backup_candidates $rel"
505
+ done
506
+ if [ -n "$backup_candidates" ]; then
507
+ backup_root=$(backup_files "$TARGET_ROOT" $backup_candidates)
508
+ printf "%s\n" "Backup created at: $backup_root"
509
+ fi
510
+ fi
511
+
512
+ count_added=0; list_added=""
513
+ count_updated=0; list_updated=""
514
+ count_unchanged=0
515
+ count_preserved=0
516
+ count_review=0; list_review=""
517
+ scratchpad_example_rel=".cursor/scratchpad.local.example.md"
518
+ scratchpad_example_status="not-seen"
519
+
520
+ for rel in $FILES; do
521
+ src="$SOURCE_ROOT/$rel"
522
+ dst="$TARGET_ROOT/$rel"
523
+ cat=$(classify_file "$rel")
524
+
525
+ if [ ! -f "$dst" ]; then
526
+ ensure_parent "$dst"
527
+ cp -p "$src" "$dst"
528
+ count_added=$((count_added + 1))
529
+ list_added="$list_added $rel"
530
+ [ "$rel" = "$scratchpad_example_rel" ] && scratchpad_example_status="added"
531
+ continue
532
+ fi
533
+
534
+ if [ "$cat" = "framework" ]; then
535
+ if cmp -s "$src" "$dst"; then
536
+ count_unchanged=$((count_unchanged + 1))
537
+ [ "$rel" = "$scratchpad_example_rel" ] && scratchpad_example_status="unchanged"
538
+ else
539
+ ensure_parent "$dst"
540
+ cp -p "$src" "$dst"
541
+ count_updated=$((count_updated + 1))
542
+ list_updated="$list_updated $rel"
543
+ [ "$rel" = "$scratchpad_example_rel" ] && scratchpad_example_status="updated"
544
+ fi
545
+ continue
546
+ fi
547
+
548
+ if [ "$cat" = "user-data" ]; then
549
+ count_preserved=$((count_preserved + 1))
550
+ continue
551
+ fi
552
+
553
+ if [ "$cat" = "mixed" ]; then
554
+ count_preserved=$((count_preserved + 1))
555
+ if ! cmp -s "$src" "$dst"; then
556
+ count_review=$((count_review + 1))
557
+ list_review="$list_review $rel"
558
+ fi
559
+ continue
560
+ fi
561
+ done
562
+
563
+ scratchpad_postinstall "$TARGET_ROOT" "upgrade"
564
+ validate_install_completeness "$TARGET_ROOT"
565
+
566
+ write_installed_version "$TARGET_ROOT" "$APP_VERSION"
567
+ sync_root_readme_to_its_magic "$TARGET_ROOT" || true
568
+ bootstrap_runbook_commands "$TARGET_ROOT"
569
+ [ -n "$BOOTSTRAP_NOTES" ] && printf "%s" "$BOOTSTRAP_NOTES"
570
+ [ "$BOOTSTRAP_OK" = "true" ] || exit 1
571
+
572
+ show_banner
573
+ printf "\033[1;32mUpgrade complete: v%s -> v%s\033[0m\n\n" "$OLD_VER" "$APP_VERSION"
574
+ if [ "$count_added" -gt 0 ]; then
575
+ printf " \033[1;32mAdded (new): %s files\033[0m\n" "$count_added"
576
+ for f in $list_added; do printf " %s\n" "$f"; done
577
+ fi
578
+ if [ "$count_updated" -gt 0 ]; then
579
+ printf " \033[1;33mUpdated (framework): %s files\033[0m\n" "$count_updated"
580
+ for f in $list_updated; do printf " %s\n" "$f"; done
581
+ fi
582
+ printf " Unchanged: %s files\n" "$count_unchanged"
583
+ printf " Preserved (user): %s files\n" "$count_preserved"
584
+ [ "$scratchpad_example_status" = "not-seen" ] && scratchpad_example_status="not-in-manifest"
585
+ printf " Scratchpad example: %s (.cursor/scratchpad.local.example.md)\n" "$scratchpad_example_status"
586
+ printf " Scratchpad layers: post-install refreshed example-first, then baseline (see [SCRATCHPAD_LAYER] lines).\n"
587
+ [ -f "$TARGET_ROOT/.cursor/scratchpad.local.md" ] && printf " User local file: preserved (.cursor/scratchpad.local.md)\n"
588
+ if [ "$count_review" -gt 0 ]; then
589
+ printf "\n \033[1;35mReview recommended: %s files\033[0m\n" "$count_review"
590
+ for f in $list_review; do printf " %s\n" "$f"; done
591
+ printf " Check .cursor/scratchpad.local.example.md for new flags.\n"
592
+ fi
593
+ printf "\nRepository: %s\n\n" "$REPO_URL"
594
+ exit 0
595
+ fi
596
+
597
+ for rel in $FILES; do
598
+ src="$SOURCE_ROOT/$rel"
599
+ dst="$TARGET_ROOT/$rel"
600
+ if [ "$MODE" = "missing" ]; then
601
+ [ -f "$dst" ] && continue
602
+ ensure_parent "$dst"
603
+ cp -p "$src" "$dst"
604
+ continue
605
+ fi
606
+ if [ "$MODE" = "overwrite" ]; then
607
+ ensure_parent "$dst"
608
+ cp -p "$src" "$dst"
609
+ continue
610
+ fi
611
+ if [ "$MODE" = "interactive" ]; then
612
+ if [ ! -f "$dst" ]; then
613
+ ensure_parent "$dst"
614
+ cp -p "$src" "$dst"
615
+ continue
616
+ fi
617
+ printf "%s" "File exists: $rel | [o]verwrite [s]kip [q]uit: "
618
+ read -r answer
619
+ answer=$(printf "%s" "$answer" | tr 'A-Z' 'a-z')
620
+ if [ "$answer" = "q" ]; then
621
+ printf "%s\n" "Aborted."
622
+ exit 1
623
+ fi
624
+ if [ "$answer" = "o" ]; then
625
+ if [ "$BACKUP" = "true" ]; then
626
+ backup_root=$(backup_files "$TARGET_ROOT" "$rel")
627
+ printf "%s\n" "Backed up: $rel -> $backup_root"
628
+ fi
629
+ ensure_parent "$dst"
630
+ cp -p "$src" "$dst"
631
+ fi
632
+ fi
633
+ done
634
+
635
+ scratchpad_postinstall "$TARGET_ROOT" "$MODE"
636
+ validate_install_completeness "$TARGET_ROOT"
637
+
638
+ write_installed_version "$TARGET_ROOT" "$APP_VERSION"
639
+ sync_root_readme_to_its_magic "$TARGET_ROOT" || true
640
+ bootstrap_runbook_commands "$TARGET_ROOT"
641
+ [ -n "$BOOTSTRAP_NOTES" ] && printf "%s" "$BOOTSTRAP_NOTES"
642
+ [ "$BOOTSTRAP_OK" = "true" ] || exit 1
643
+
644
+ show_banner
645
+ printf "its-magic v%s\n" "$APP_VERSION"
646
+ printf "Repository: %s\n\n" "$REPO_URL"
647
+ printf "\033[1;32m Installation complete!\033[0m\n\n"
648
+ exit 0
649
+