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.ps1 ADDED
@@ -0,0 +1,759 @@
1
+ Param(
2
+ [string]$Target,
3
+ [ValidateSet("missing","overwrite","interactive","upgrade")]
4
+ [string]$Mode,
5
+ [switch]$Backup,
6
+ [switch]$Create,
7
+ [switch]$CleanRepo,
8
+ [switch]$Yes,
9
+ [switch]$Help,
10
+ [switch]$Version
11
+ )
12
+
13
+ $ErrorActionPreference = "Stop"
14
+
15
+ function Normalize-PathSafe($Path) {
16
+ return [System.IO.Path]::GetFullPath($Path)
17
+ }
18
+
19
+ function Ensure-Parent($Path) {
20
+ $parent = Split-Path -Parent $Path
21
+ if ($parent -and -not (Test-Path $parent)) {
22
+ New-Item -ItemType Directory -Path $parent -Force | Out-Null
23
+ }
24
+ }
25
+
26
+ function Get-ManifestSection($ManifestPath, $SectionName) {
27
+ $lines = Get-Content -Path $ManifestPath
28
+ $inSection = $false
29
+ $items = New-Object System.Collections.Generic.List[string]
30
+ foreach ($raw in $lines) {
31
+ $line = $raw.TrimEnd("`r").Trim()
32
+ if ([string]::IsNullOrWhiteSpace($line)) { continue }
33
+ if ($line.StartsWith("#")) { continue }
34
+ if ($line.StartsWith("[") -and $line.EndsWith("]")) {
35
+ $inSection = ($line -eq "[$SectionName]")
36
+ continue
37
+ }
38
+ if ($inSection) { $items.Add($line) }
39
+ }
40
+ return $items
41
+ }
42
+
43
+ function Load-InstallerOwnershipManifest($SourceRoot, $ScriptRoot) {
44
+ $candidates = @(
45
+ (Join-Path $SourceRoot "docs/engineering/context/installer-owned-paths.manifest"),
46
+ (Join-Path $ScriptRoot "docs/engineering/context/installer-owned-paths.manifest")
47
+ ) | Select-Object -Unique
48
+
49
+ foreach ($candidate in $candidates) {
50
+ if (-not (Test-Path $candidate -PathType Leaf)) { continue }
51
+ $installPaths = Get-ManifestSection -ManifestPath $candidate -SectionName "install_include_paths"
52
+ $cleanPaths = Get-ManifestSection -ManifestPath $candidate -SectionName "clean_paths"
53
+ if ($installPaths.Count -eq 0 -or $cleanPaths.Count -eq 0) {
54
+ throw "[INSTALL_MANIFEST_ERROR] $candidate is missing required sections or entries."
55
+ }
56
+ return [PSCustomObject]@{
57
+ install_include_paths = @($installPaths)
58
+ clean_paths = @($cleanPaths)
59
+ manifest_path = $candidate
60
+ }
61
+ }
62
+
63
+ throw "[INSTALL_SOURCE_ERROR] installer-owned-paths.manifest not found. Reinstall its-magic to restore template assets."
64
+ }
65
+
66
+ function List-SourceFiles($SourceRoot, $IncludePaths) {
67
+ $files = New-Object System.Collections.Generic.List[string]
68
+ foreach ($rel in $IncludePaths) {
69
+ $src = Join-Path $SourceRoot $rel
70
+ if (Test-Path $src -PathType Leaf) {
71
+ $files.Add($rel)
72
+ } elseif (Test-Path $src -PathType Container) {
73
+ Get-ChildItem -Path $src -Recurse -File | ForEach-Object {
74
+ $relPath = $_.FullName.Substring($SourceRoot.Length).TrimStart("\","/")
75
+ $files.Add($relPath)
76
+ }
77
+ }
78
+ }
79
+ return $files | Select-Object -Unique | Sort-Object
80
+ }
81
+
82
+ function Backup-Files($TargetRoot, $RelPaths) {
83
+ $timestamp = (Get-Date).ToUniversalTime().ToString("yyyyMMdd-HHmmssZ")
84
+ $backupRoot = Join-Path $TargetRoot ("backups\" + $timestamp)
85
+ foreach ($rel in $RelPaths) {
86
+ $src = Join-Path $TargetRoot $rel
87
+ if (Test-Path $src -PathType Leaf) {
88
+ $dst = Join-Path $backupRoot $rel
89
+ Ensure-Parent $dst
90
+ Copy-Item -Path $src -Destination $dst -Force
91
+ }
92
+ }
93
+ return $backupRoot
94
+ }
95
+
96
+ function Choose-Mode {
97
+ Write-Host "Select install mode:"
98
+ Write-Host "1) missing-only (copy only files that do not exist)"
99
+ Write-Host "2) overwrite-all (replace existing files)"
100
+ Write-Host "3) interactive (prompt per file)"
101
+ Write-Host "4) upgrade (update framework files, preserve user data)"
102
+ $choice = Read-Host "Enter 1, 2, 3, or 4"
103
+ switch ($choice) {
104
+ "1" { return "missing" }
105
+ "2" { return "overwrite" }
106
+ "4" { return "upgrade" }
107
+ Default { return "interactive" }
108
+ }
109
+ }
110
+
111
+ function Classify-File($RelPath) {
112
+ $normalized = $RelPath -replace '\\','/'
113
+
114
+ $mixedFiles = @('README.md')
115
+ if ($mixedFiles -contains $normalized) { return 'mixed' }
116
+
117
+ $frameworkPrefixes = @(
118
+ '.cursor/commands/',
119
+ '.cursor/rules/',
120
+ '.cursor/agents/',
121
+ '.cursor/skills/',
122
+ '.cursor/hooks/',
123
+ '.github/workflows/',
124
+ 'scripts/validate-and-push',
125
+ 'scripts/sync_push_gates',
126
+ 'docs/engineering/context/',
127
+ 'its_magic/'
128
+ )
129
+ $frameworkExact = @(
130
+ '.cursor/hooks.json',
131
+ '.cursor/scratchpad.local.example.md',
132
+ 'its_magic/.its-magic-version',
133
+ 'its_magic/README.md'
134
+ )
135
+ foreach ($p in $frameworkPrefixes) {
136
+ if ($normalized.StartsWith($p)) { return 'framework' }
137
+ }
138
+ if ($frameworkExact -contains $normalized) { return 'framework' }
139
+
140
+ $userDataPrefixes = @(
141
+ 'docs/product/',
142
+ 'docs/engineering/',
143
+ 'docs/user-guides/',
144
+ 'sprints/',
145
+ 'handoffs/',
146
+ 'decisions/'
147
+ )
148
+ foreach ($p in $userDataPrefixes) {
149
+ if ($normalized.StartsWith($p)) { return 'user-data' }
150
+ }
151
+
152
+ return 'framework'
153
+ }
154
+
155
+ function Read-InstalledVersion($TargetRoot) {
156
+ $primary = Join-Path $TargetRoot "its_magic\.its-magic-version"
157
+ if (Test-Path $primary -PathType Leaf) {
158
+ return (Get-Content -Path $primary -Raw).Trim()
159
+ }
160
+ $legacy = Join-Path $TargetRoot ".its-magic-version"
161
+ if (Test-Path $legacy -PathType Leaf) {
162
+ return (Get-Content -Path $legacy -Raw).Trim()
163
+ }
164
+ return "unknown"
165
+ }
166
+
167
+ function Write-InstalledVersion($TargetRoot, $Ver) {
168
+ $primary = Join-Path $TargetRoot "its_magic\.its-magic-version"
169
+ Ensure-Parent $primary
170
+ Set-Content -Path $primary -Value $Ver -NoNewline
171
+
172
+ $legacy = Join-Path $TargetRoot ".its-magic-version"
173
+ if (Test-Path $legacy -PathType Leaf) {
174
+ Remove-Item -Path $legacy -Force -ErrorAction SilentlyContinue
175
+ }
176
+ }
177
+
178
+ function Sync-RootReadmeToItsMagic($TargetRoot) {
179
+ $rootReadme = Join-Path $TargetRoot "README.md"
180
+ if (-not (Test-Path $rootReadme -PathType Leaf)) { return $false }
181
+ $itsMagicReadme = Join-Path $TargetRoot "its_magic\README.md"
182
+ Ensure-Parent $itsMagicReadme
183
+ Copy-Item -Path $rootReadme -Destination $itsMagicReadme -Force
184
+ return $true
185
+ }
186
+
187
+ function Read-RunbookKeyValue($RunbookPath, $Key) {
188
+ if (-not (Test-Path $RunbookPath -PathType Leaf)) { return "" }
189
+ $needle = "${Key}:"
190
+ foreach ($raw in (Get-Content -Path $RunbookPath)) {
191
+ if ($raw.StartsWith($needle)) {
192
+ return $raw.Substring($needle.Length).Trim()
193
+ }
194
+ }
195
+ return ""
196
+ }
197
+
198
+ function Write-RunbookKeyValue($RunbookPath, $Key, $Value) {
199
+ if (-not (Test-Path $RunbookPath -PathType Leaf)) { return $false }
200
+ $needle = "${Key}:"
201
+ $lines = Get-Content -Path $RunbookPath
202
+ $changed = $false
203
+ for ($i = 0; $i -lt $lines.Count; $i++) {
204
+ if ($lines[$i].StartsWith($needle)) {
205
+ $lines[$i] = "$needle $Value"
206
+ $changed = $true
207
+ break
208
+ }
209
+ }
210
+ if ($changed) {
211
+ Set-Content -Path $RunbookPath -Value $lines
212
+ }
213
+ return $changed
214
+ }
215
+
216
+ function Test-PackageHasScript($TargetRoot, $ScriptName) {
217
+ $pkgPath = Join-Path $TargetRoot "package.json"
218
+ if (-not (Test-Path $pkgPath -PathType Leaf)) { return $false }
219
+ try {
220
+ $pkg = Get-Content -Path $pkgPath -Raw | ConvertFrom-Json
221
+ if ($null -eq $pkg.scripts) { return $false }
222
+ $value = $pkg.scripts.$ScriptName
223
+ return -not [string]::IsNullOrWhiteSpace([string]$value)
224
+ } catch {
225
+ return $false
226
+ }
227
+ }
228
+
229
+ function Get-DetectedRunbookDefaults($TargetRoot) {
230
+ $defaults = @{
231
+ TEST_COMMAND = ""
232
+ LINT_COMMAND = ""
233
+ TYPECHECK_COMMAND = ""
234
+ }
235
+
236
+ $testsSh = Join-Path $TargetRoot "tests\run-tests.sh"
237
+ $pkgPath = Join-Path $TargetRoot "package.json"
238
+ $goMod = Join-Path $TargetRoot "go.mod"
239
+ $pyproject = Join-Path $TargetRoot "pyproject.toml"
240
+ $requirements = Join-Path $TargetRoot "requirements.txt"
241
+ $setupPy = Join-Path $TargetRoot "setup.py"
242
+
243
+ $hasPkg = Test-Path $pkgPath -PathType Leaf
244
+ $hasGo = Test-Path $goMod -PathType Leaf
245
+ $hasPy = (Test-Path $pyproject -PathType Leaf) -or (Test-Path $requirements -PathType Leaf) -or (Test-Path $setupPy -PathType Leaf)
246
+
247
+ if ($hasPkg -and (Test-PackageHasScript $TargetRoot "test")) {
248
+ $defaults.TEST_COMMAND = "npm run test"
249
+ if (Test-PackageHasScript $TargetRoot "lint") { $defaults.LINT_COMMAND = "npm run lint" }
250
+ if (Test-PackageHasScript $TargetRoot "typecheck") { $defaults.TYPECHECK_COMMAND = "npm run typecheck" }
251
+ return $defaults
252
+ }
253
+
254
+ if ($hasGo) {
255
+ $defaults.TEST_COMMAND = "go test ./..."
256
+ return $defaults
257
+ }
258
+
259
+ if ($hasPy) {
260
+ $defaults.TEST_COMMAND = "python -m pytest"
261
+ return $defaults
262
+ }
263
+
264
+ if (Test-Path $testsSh -PathType Leaf) {
265
+ $defaults.TEST_COMMAND = "sh tests/run-tests.sh"
266
+ return $defaults
267
+ }
268
+
269
+ return $defaults
270
+ }
271
+
272
+ function Test-BootstrapCommandValid($TargetRoot, $Key, $Command) {
273
+ if ([string]::IsNullOrWhiteSpace($Command)) { return [PSCustomObject]@{ valid = $false; reason = "${Key}_UNDETECTED" } }
274
+
275
+ if ($Command.StartsWith("npm run ")) {
276
+ $npm = Get-Command "npm" -ErrorAction SilentlyContinue
277
+ if (-not $npm) { return [PSCustomObject]@{ valid = $false; reason = "NPM_NOT_FOUND" } }
278
+ $script = $Command.Substring("npm run ".Length).Trim()
279
+ if (-not (Test-PackageHasScript $TargetRoot $script)) { return [PSCustomObject]@{ valid = $false; reason = "NPM_SCRIPT_MISSING:$script" } }
280
+ return [PSCustomObject]@{ valid = $true; reason = "OK" }
281
+ }
282
+
283
+ if ($Command -eq "python -m pytest") {
284
+ $python = Get-Command "python" -ErrorAction SilentlyContinue
285
+ if (-not $python) { return [PSCustomObject]@{ valid = $false; reason = "PYTHON_NOT_FOUND" } }
286
+ return [PSCustomObject]@{ valid = $true; reason = "OK" }
287
+ }
288
+
289
+ if ($Command.StartsWith("go test")) {
290
+ $go = Get-Command "go" -ErrorAction SilentlyContinue
291
+ if (-not $go) { return [PSCustomObject]@{ valid = $false; reason = "GO_NOT_FOUND" } }
292
+ if (-not (Test-Path (Join-Path $TargetRoot "go.mod") -PathType Leaf)) {
293
+ return [PSCustomObject]@{ valid = $false; reason = "GO_MOD_MISSING" }
294
+ }
295
+ return [PSCustomObject]@{ valid = $true; reason = "OK" }
296
+ }
297
+
298
+ if ($Command.StartsWith("powershell ")) {
299
+ if (-not (Test-Path (Join-Path $TargetRoot "tests\run-tests.ps1") -PathType Leaf)) {
300
+ return [PSCustomObject]@{ valid = $false; reason = "RUN_TESTS_PS1_MISSING" }
301
+ }
302
+ return [PSCustomObject]@{ valid = $true; reason = "OK" }
303
+ }
304
+
305
+ if ($Command.StartsWith("sh ")) {
306
+ $sh = Get-Command "sh" -ErrorAction SilentlyContinue
307
+ if (-not $sh) { return [PSCustomObject]@{ valid = $false; reason = "SH_NOT_FOUND" } }
308
+ if (-not (Test-Path (Join-Path $TargetRoot "tests\run-tests.sh") -PathType Leaf)) {
309
+ return [PSCustomObject]@{ valid = $false; reason = "RUN_TESTS_SH_MISSING" }
310
+ }
311
+ return [PSCustomObject]@{ valid = $true; reason = "OK" }
312
+ }
313
+
314
+ $exe = ($Command -split ' ')[0]
315
+ if (-not (Get-Command $exe -ErrorAction SilentlyContinue)) {
316
+ return [PSCustomObject]@{ valid = $false; reason = "EXECUTABLE_NOT_FOUND:$exe" }
317
+ }
318
+ return [PSCustomObject]@{ valid = $true; reason = "OK" }
319
+ }
320
+
321
+ function Invoke-RunbookBootstrap($TargetRoot) {
322
+ $runbookPath = Join-Path $TargetRoot "docs\engineering\runbook.md"
323
+ if (-not (Test-Path $runbookPath -PathType Leaf)) {
324
+ return [PSCustomObject]@{ ok = $true; notes = @() }
325
+ }
326
+
327
+ $defaults = Get-DetectedRunbookDefaults $TargetRoot
328
+ $notes = New-Object System.Collections.Generic.List[string]
329
+ $applied = New-Object System.Collections.Generic.List[string]
330
+
331
+ foreach ($key in @("TEST_COMMAND","LINT_COMMAND","TYPECHECK_COMMAND")) {
332
+ $current = Read-RunbookKeyValue -RunbookPath $runbookPath -Key $key
333
+ if (-not [string]::IsNullOrWhiteSpace($current)) { continue }
334
+
335
+ $candidate = [string]$defaults[$key]
336
+ if ([string]::IsNullOrWhiteSpace($candidate)) {
337
+ if ($key -eq "TEST_COMMAND") {
338
+ $notes.Add("[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).")
339
+ }
340
+ continue
341
+ }
342
+
343
+ $valid = Test-BootstrapCommandValid -TargetRoot $TargetRoot -Key $key -Command $candidate
344
+ if ($valid.valid) {
345
+ if (Write-RunbookKeyValue -RunbookPath $runbookPath -Key $key -Value $candidate) {
346
+ $applied.Add("$key=$candidate")
347
+ }
348
+ } elseif ($key -eq "TEST_COMMAND") {
349
+ $notes.Add("[RUNBOOK_BOOTSTRAP_ERROR] TEST_COMMAND_INVALID:$($valid.reason). Fix: set a valid TEST_COMMAND in docs/engineering/runbook.md.")
350
+ }
351
+ }
352
+
353
+ if ($applied.Count -gt 0) {
354
+ $notes.Add("[RUNBOOK_BOOTSTRAP] Applied defaults: $($applied -join ', ')")
355
+ }
356
+
357
+ $finalTest = Read-RunbookKeyValue -RunbookPath $runbookPath -Key "TEST_COMMAND"
358
+ $ok = -not [string]::IsNullOrWhiteSpace($finalTest)
359
+ return [PSCustomObject]@{
360
+ ok = $ok
361
+ notes = @($notes)
362
+ }
363
+ }
364
+
365
+ function Files-ContentEqual($PathA, $PathB) {
366
+ $a = Get-Content -Path $PathA -Raw -ErrorAction SilentlyContinue
367
+ $b = Get-Content -Path $PathB -Raw -ErrorAction SilentlyContinue
368
+ return $a -eq $b
369
+ }
370
+
371
+ function Prompt-YesNo($Label, $Default = $false) {
372
+ $suffix = if ($Default) { "Y/n" } else { "y/N" }
373
+ $value = (Read-Host "$Label [$suffix]").ToLowerInvariant()
374
+ if ([string]::IsNullOrWhiteSpace($value)) { return $Default }
375
+ return @("y","yes") -contains $value
376
+ }
377
+
378
+ function Get-AppVersion($SourceRoot) {
379
+ try {
380
+ $pkgPath = Join-Path $SourceRoot "package.json"
381
+ if (Test-Path $pkgPath -PathType Leaf) {
382
+ $pkg = Get-Content -Path $pkgPath -Raw | ConvertFrom-Json
383
+ if ($pkg.version) { return [string]$pkg.version }
384
+ }
385
+ } catch {}
386
+ return "unknown"
387
+ }
388
+
389
+ function Invoke-ScratchpadPostinstall {
390
+ param(
391
+ [string]$TargetRoot,
392
+ [string]$Mode
393
+ )
394
+ $installerPy = Join-Path $scriptDir "installer.py"
395
+ if (-not (Test-Path $installerPy -PathType Leaf)) {
396
+ Write-Host "[SCRATCHPAD_POSTINSTALL_ERROR] installer.py missing next to installer.ps1."
397
+ exit 1
398
+ }
399
+ $py = Get-Command python -ErrorAction SilentlyContinue
400
+ if (-not $py) {
401
+ Write-Host "[SCRATCHPAD_POSTINSTALL_ERROR] PYTHON_NOT_FOUND: Python is required for scratchpad materialization/validation (Model B). Fix: install Python 3 and re-run."
402
+ exit 1
403
+ }
404
+ & python $installerPy --scratchpad-postinstall --target $TargetRoot --mode $Mode
405
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
406
+ }
407
+
408
+ function Invoke-InstallCompletenessValidation {
409
+ param(
410
+ [string]$TargetRoot
411
+ )
412
+ $installerPy = Join-Path $scriptDir "installer.py"
413
+ if (-not (Test-Path $installerPy -PathType Leaf)) {
414
+ Write-Host "[INSTALL_COMPLETENESS_FAILED] installer.py missing next to installer.ps1."
415
+ exit 1
416
+ }
417
+ $py = Get-Command python -ErrorAction SilentlyContinue
418
+ if (-not $py) {
419
+ Write-Host "[INSTALL_COMPLETENESS_FAILED] PYTHON_NOT_FOUND: Python is required for deterministic installer completeness validation."
420
+ exit 1
421
+ }
422
+ & python $installerPy --validate-install-completeness --target $TargetRoot
423
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
424
+ }
425
+
426
+ function Show-ItsMagicBanner([switch]$IncludeInstallMessage) {
427
+ $prev = [Console]::OutputEncoding
428
+ [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
429
+ $b64 = 'ICDilojilojilZfilojilojilojilojilojilojilojilojilZfilojilojilojilojilojilojilojilZcgICAgICDilojilojilojilZcgICDilojilojilojilZcg4paI4paI4paI4paI4paI4pWXICDilojilojilojilojilojilojilZcg4paI4paI4pWXIOKWiOKWiOKWiOKWiOKWiOKWiOKVlwogIOKWiOKWiOKVkeKVmuKVkOKVkOKWiOKWiOKVlOKVkOKVkOKVneKWiOKWiOKVlOKVkOKVkOKVkOKVkOKVnSAgICAgIOKWiOKWiOKWiOKWiOKVlyDilojilojilojilojilZHilojilojilZTilZDilZDilojilojilZfilojilojilZTilZDilZDilZDilZDilZ0g4paI4paI4pWR4paI4paI4pWU4pWQ4pWQ4pWQ4pWQ4pWdCiAg4paI4paI4pWRICAg4paI4paI4pWRICAg4paI4paI4paI4paI4paI4paI4paI4pWX4paI4paI4paI4paI4paI4pWX4paI4paI4pWU4paI4paI4paI4paI4pWU4paI4paI4pWR4paI4paI4paI4paI4paI4paI4paI4pWR4paI4paI4pWRICDilojilojilojilZfilojilojilZHilojilojilZEgICAgIAogIOKWiOKWiOKVkSAgIOKWiOKWiOKVkSAgIOKVmuKVkOKVkOKVkOKVkOKWiOKWiOKVkeKVmuKVkOKVkOKVkOKVkOKVneKWiOKWiOKVkeKVmuKWiOKWiOKVlOKVneKWiOKWiOKVkeKWiOKWiOKVlOKVkOKVkOKWiOKWiOKVkeKWiOKWiOKVkSAgIOKWiOKWiOKVkeKWiOKWiOKVkeKWiOKWiOKVkSAgICAgCiAg4paI4paI4pWRICAg4paI4paI4pWRICAg4paI4paI4paI4paI4paI4paI4paI4pWRICAgICAg4paI4paI4pWRIOKVmuKVkOKVnSDilojilojilZHilojilojilZEgIOKWiOKWiOKVkeKVmuKWiOKWiOKWiOKWiOKWiOKWiOKVlOKVneKWiOKWiOKVkeKVmuKWiOKWiOKWiOKWiOKWiOKWiOKVlwogIOKVmuKVkOKVnSAgIOKVmuKVkOKVnSAgIOKVmuKVkOKVkOKVkOKVkOKVkOKVkOKVnSAgICAgIOKVmuKVkOKVnSAgICAg4pWa4pWQ4pWd4pWa4pWQ4pWdICDilZrilZDilZ0g4pWa4pWQ4pWQ4pWQ4pWQ4pWQ4pWdIOKVmuKVkOKVnSDilZrilZDilZDilZDilZDilZDilZ0='
430
+ $art = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($b64))
431
+ $lines = $art -split "`n"
432
+ $colors = @('Magenta','Magenta','Magenta','Cyan','Cyan','Cyan')
433
+ Write-Host ""
434
+ for ($i = 0; $i -lt $lines.Count; $i++) {
435
+ Write-Host $lines[$i] -ForegroundColor $colors[$i]
436
+ }
437
+ Write-Host ""
438
+ Write-Host " AI dev team" -ForegroundColor Yellow
439
+ if ($IncludeInstallMessage) {
440
+ Write-Host " Installation complete!" -ForegroundColor Green
441
+ }
442
+ Write-Host ""
443
+ [Console]::OutputEncoding = $prev
444
+ }
445
+
446
+ function Show-ItsMagicHelp($VersionString, $RepoUrl) {
447
+ Show-ItsMagicBanner
448
+ Write-Host "its-magic v$VersionString"
449
+ Write-Host "Repository: $RepoUrl"
450
+ Write-Host ""
451
+ Write-Host "Install AI dev team workflow files into any Cursor repository."
452
+ Write-Host ""
453
+ Write-Host "Usage:"
454
+ Write-Host " its-magic --target <path> [--mode <mode>] [--backup] [--create]"
455
+ Write-Host " its-magic --clean-repo [--target <path>] [--yes]"
456
+ Write-Host " its-magic --help | --version"
457
+ Write-Host ""
458
+ Write-Host "Install options:"
459
+ Write-Host " --target <path> Path to the repository where workflow files are installed."
460
+ Write-Host " If omitted you will be prompted interactively."
461
+ Write-Host " --mode <mode> How to handle files that already exist in the target:"
462
+ Write-Host " missing Only copy files that do not exist yet (default)."
463
+ Write-Host " Safe for repos that already have some workflow files."
464
+ Write-Host " overwrite Replace every file, even if it already exists."
465
+ Write-Host " Combine with --backup to keep a snapshot first."
466
+ Write-Host " interactive Ask per file whether to overwrite or skip."
467
+ Write-Host " upgrade Update framework files while preserving user data."
468
+ Write-Host " Use after updating its-magic to a newer version."
469
+ Write-Host " --backup Before overwriting, save existing files to backups/<timestamp>/."
470
+ Write-Host " Ignored when mode is 'missing' (nothing gets replaced)."
471
+ Write-Host " --create Create the target directory if it does not exist."
472
+ Write-Host " Note: installer bootstraps runbook TEST/LINT/TYPECHECK commands from"
473
+ Write-Host " OS+stack detection; unresolved TEST_COMMAND fails fast with"
474
+ Write-Host " [RUNBOOK_BOOTSTRAP_ERROR] diagnostics."
475
+ Write-Host " Note: scratchpad Model B: .cursor/scratchpad.md is"
476
+ Write-Host " materialized when missing; Python 3 on PATH is required for validation."
477
+ Write-Host " Recovery: python installer.py --scratchpad-postinstall --target <repo> --mode missing"
478
+ Write-Host ""
479
+ Write-Host "Clean options:"
480
+ Write-Host " --clean-repo Remove all its-magic workflow artifacts from the target repo"
481
+ Write-Host " (owned paths from installer manifest, including .cursor,"
482
+ Write-Host " docs/product, docs/engineering, docs/user-guides, sprints,"
483
+ Write-Host " handoffs, decisions, workflow scripts, CI files, and"
484
+ Write-Host " installer metadata under its_magic/ (legacy .its-magic-version"
485
+ Write-Host " is also removed when present). Your own source code is never touched."
486
+ Write-Host " --target <path> Repo to clean (default: current directory)."
487
+ Write-Host " --yes Skip the confirmation prompt."
488
+ Write-Host ""
489
+ Write-Host "Info:"
490
+ Write-Host " --help Show this help and exit."
491
+ Write-Host " --version Print the installed version and exit."
492
+ Write-Host ""
493
+ Write-Host "Examples:"
494
+ Write-Host " its-magic --target . --mode missing Safe first-time setup"
495
+ Write-Host " its-magic --target . --mode upgrade Update framework, keep user data"
496
+ Write-Host " its-magic --target . --mode overwrite --backup Replace all files, keep backup"
497
+ Write-Host " its-magic --clean-repo --target . --yes Remove workflow artifacts silently"
498
+ Write-Host ""
499
+ }
500
+
501
+ $scriptDir = Normalize-PathSafe (Split-Path -Parent $MyInvocation.MyCommand.Path)
502
+ $sourceRoot = Join-Path $scriptDir "template"
503
+ $repoUrl = "https://github.com/fl0wm0ti0n/its-magic"
504
+ $appVersion = Get-AppVersion $scriptDir
505
+ $noArgs = $PSBoundParameters.Count -eq 0
506
+
507
+ if ($Version) {
508
+ Write-Host "its-magic v$appVersion"
509
+ exit 0
510
+ }
511
+
512
+ if ($Help -or $noArgs) {
513
+ Show-ItsMagicHelp -VersionString $appVersion -RepoUrl $repoUrl
514
+ exit 0
515
+ }
516
+
517
+ if (-not (Test-Path $sourceRoot -PathType Container)) {
518
+ Write-Host "[INSTALL_SOURCE_ERROR] template directory is missing. Reinstall its-magic package."
519
+ exit 1
520
+ }
521
+
522
+ $ownershipManifest = Load-InstallerOwnershipManifest -SourceRoot $sourceRoot -ScriptRoot $scriptDir
523
+ $includePaths = @($ownershipManifest.install_include_paths)
524
+ $cleanPaths = @($ownershipManifest.clean_paths)
525
+
526
+ if ($CleanRepo) {
527
+ if (-not $Target) { $Target = "." }
528
+ $targetRoot = Normalize-PathSafe $Target
529
+ if (-not (Test-Path $targetRoot -PathType Container)) {
530
+ Write-Host "Target directory does not exist."
531
+ exit 1
532
+ }
533
+ if (-not $Yes) {
534
+ $proceed = Prompt-YesNo "Clean its-magic workflow artifacts in $targetRoot?" $false
535
+ if (-not $proceed) {
536
+ Write-Host "Aborted."
537
+ exit 1
538
+ }
539
+ }
540
+ foreach ($rel in $cleanPaths) {
541
+ $fullPath = Join-Path $targetRoot $rel
542
+ if (Test-Path $fullPath) {
543
+ if (Test-Path $fullPath -PathType Container) {
544
+ Remove-Item -Path $fullPath -Recurse -Force
545
+ } else {
546
+ Remove-Item -Path $fullPath -Force
547
+ }
548
+ Write-Host "Removed: $rel"
549
+ }
550
+ }
551
+ Write-Host "Clean completed."
552
+ exit 0
553
+ }
554
+
555
+ if (-not $Target) {
556
+ $Target = Read-Host "Target repository path"
557
+ }
558
+ $targetRoot = Normalize-PathSafe $Target
559
+
560
+ if (-not (Test-Path $targetRoot -PathType Container)) {
561
+ if ($Create -or (Prompt-YesNo "Target missing. Create?" $false)) {
562
+ New-Item -ItemType Directory -Path $targetRoot -Force | Out-Null
563
+ } else {
564
+ Write-Host "Target directory does not exist."
565
+ exit 1
566
+ }
567
+ }
568
+
569
+ $mode = if ($Mode) { $Mode } else { Choose-Mode }
570
+ $backupEnabled = $Backup.IsPresent
571
+ if (($mode -eq "overwrite" -or $mode -eq "interactive") -and -not $backupEnabled) {
572
+ $backupEnabled = Prompt-YesNo "Backup existing files before overwrite?" $false
573
+ }
574
+
575
+ $files = List-SourceFiles $sourceRoot $includePaths
576
+ if ($files.Count -eq 0) {
577
+ Write-Host "No source files found to install."
578
+ exit 1
579
+ }
580
+
581
+ if ($backupEnabled -and $mode -eq "overwrite") {
582
+ $overwriteCandidates = @()
583
+ foreach ($rel in $files) {
584
+ $dst = Join-Path $targetRoot $rel
585
+ if (Test-Path $dst -PathType Leaf) { $overwriteCandidates += $rel }
586
+ }
587
+ if ($overwriteCandidates.Count -gt 0) {
588
+ $backupRoot = Backup-Files $targetRoot $overwriteCandidates
589
+ Write-Host "Backup created at: $backupRoot"
590
+ }
591
+ }
592
+
593
+ if ($mode -eq "upgrade") {
594
+ $oldVersion = Read-InstalledVersion $targetRoot
595
+ Write-Host ""
596
+ Write-Host "Upgrading from v$oldVersion to v$appVersion" -ForegroundColor Cyan
597
+ Write-Host ""
598
+
599
+ if ($backupEnabled) {
600
+ $backupCandidates = @()
601
+ foreach ($rel in $files) {
602
+ $dst = Join-Path $targetRoot $rel
603
+ $cat = Classify-File $rel
604
+ if ($cat -eq 'framework' -and (Test-Path $dst -PathType Leaf)) {
605
+ $backupCandidates += $rel
606
+ }
607
+ }
608
+ if ($backupCandidates.Count -gt 0) {
609
+ $backupRoot = Backup-Files $targetRoot $backupCandidates
610
+ Write-Host "Backup created at: $backupRoot"
611
+ }
612
+ }
613
+
614
+ $added = New-Object System.Collections.Generic.List[string]
615
+ $updated = New-Object System.Collections.Generic.List[string]
616
+ $unchanged = 0
617
+ $preserved = 0
618
+ $review = New-Object System.Collections.Generic.List[string]
619
+ $scratchpadExampleRel = '.cursor/scratchpad.local.example.md'
620
+ $scratchpadExampleStatus = 'not-seen'
621
+
622
+ foreach ($rel in $files) {
623
+ $src = Join-Path $sourceRoot $rel
624
+ $dst = Join-Path $targetRoot $rel
625
+ $exists = Test-Path $dst -PathType Leaf
626
+ $cat = Classify-File $rel
627
+
628
+ if (-not $exists) {
629
+ Ensure-Parent $dst
630
+ Copy-Item -Path $src -Destination $dst -Force
631
+ $added.Add($rel)
632
+ if ($rel -eq $scratchpadExampleRel) { $scratchpadExampleStatus = 'added' }
633
+ continue
634
+ }
635
+
636
+ if ($cat -eq 'framework') {
637
+ if (Files-ContentEqual $src $dst) {
638
+ $unchanged++
639
+ if ($rel -eq $scratchpadExampleRel) { $scratchpadExampleStatus = 'unchanged' }
640
+ } else {
641
+ Ensure-Parent $dst
642
+ Copy-Item -Path $src -Destination $dst -Force
643
+ $updated.Add($rel)
644
+ if ($rel -eq $scratchpadExampleRel) { $scratchpadExampleStatus = 'updated' }
645
+ }
646
+ continue
647
+ }
648
+
649
+ if ($cat -eq 'user-data') {
650
+ $preserved++
651
+ continue
652
+ }
653
+
654
+ if ($cat -eq 'mixed') {
655
+ $preserved++
656
+ if (-not (Files-ContentEqual $src $dst)) {
657
+ $review.Add($rel)
658
+ }
659
+ continue
660
+ }
661
+ }
662
+
663
+ Invoke-ScratchpadPostinstall -TargetRoot $targetRoot -Mode "upgrade"
664
+ Invoke-InstallCompletenessValidation -TargetRoot $targetRoot
665
+
666
+ Write-InstalledVersion $targetRoot $appVersion
667
+ Sync-RootReadmeToItsMagic $targetRoot | Out-Null
668
+ $runbookBootstrap = Invoke-RunbookBootstrap -TargetRoot $targetRoot
669
+ foreach ($note in $runbookBootstrap.notes) { Write-Host $note }
670
+ if (-not $runbookBootstrap.ok) { exit 1 }
671
+
672
+ Show-ItsMagicBanner
673
+ Write-Host "Upgrade complete: v$oldVersion -> v$appVersion" -ForegroundColor Green
674
+ Write-Host ""
675
+ if ($added.Count -gt 0) {
676
+ Write-Host " Added (new): $($added.Count) files" -ForegroundColor Green
677
+ foreach ($f in $added) { Write-Host " $f" }
678
+ }
679
+ if ($updated.Count -gt 0) {
680
+ Write-Host " Updated (framework): $($updated.Count) files" -ForegroundColor Yellow
681
+ foreach ($f in $updated) { Write-Host " $f" }
682
+ }
683
+ Write-Host " Unchanged: $unchanged files"
684
+ Write-Host " Preserved (user): $preserved files"
685
+ if ($scratchpadExampleStatus -eq 'not-seen') { $scratchpadExampleStatus = 'not-in-manifest' }
686
+ Write-Host " Scratchpad example: $scratchpadExampleStatus (.cursor/scratchpad.local.example.md)"
687
+ Write-Host " Scratchpad layers: post-install refreshed example-first, then baseline (see [SCRATCHPAD_LAYER] lines)."
688
+ if (Test-Path (Join-Path $targetRoot '.cursor/scratchpad.local.md') -PathType Leaf) {
689
+ Write-Host " User local file: preserved (.cursor/scratchpad.local.md)"
690
+ }
691
+ if ($review.Count -gt 0) {
692
+ Write-Host ""
693
+ Write-Host " Review recommended: $($review.Count) files" -ForegroundColor Magenta
694
+ foreach ($f in $review) {
695
+ Write-Host " $f"
696
+ }
697
+ Write-Host " Check .cursor/scratchpad.local.example.md for new flags." -ForegroundColor DarkGray
698
+ }
699
+ Write-Host ""
700
+ Write-Host "Repository: $repoUrl"
701
+ Write-Host ""
702
+ exit 0
703
+ }
704
+
705
+ foreach ($rel in $files) {
706
+ $src = Join-Path $sourceRoot $rel
707
+ $dst = Join-Path $targetRoot $rel
708
+ $exists = Test-Path $dst -PathType Leaf
709
+
710
+ if ($mode -eq "missing") {
711
+ if ($exists) { continue }
712
+ Ensure-Parent $dst
713
+ Copy-Item -Path $src -Destination $dst -Force
714
+ continue
715
+ }
716
+
717
+ if ($mode -eq "overwrite") {
718
+ Ensure-Parent $dst
719
+ Copy-Item -Path $src -Destination $dst -Force
720
+ continue
721
+ }
722
+
723
+ if ($mode -eq "interactive") {
724
+ if (-not $exists) {
725
+ Ensure-Parent $dst
726
+ Copy-Item -Path $src -Destination $dst -Force
727
+ continue
728
+ }
729
+ $answer = (Read-Host "File exists: $rel | [o]verwrite [s]kip [q]uit").ToLowerInvariant()
730
+ if ($answer -eq "q") {
731
+ Write-Host "Aborted."
732
+ exit 1
733
+ }
734
+ if ($answer -eq "o") {
735
+ if ($backupEnabled) {
736
+ $backupRoot = Backup-Files $targetRoot @($rel)
737
+ Write-Host "Backed up: $rel -> $backupRoot"
738
+ }
739
+ Ensure-Parent $dst
740
+ Copy-Item -Path $src -Destination $dst -Force
741
+ }
742
+ }
743
+ }
744
+
745
+ Invoke-ScratchpadPostinstall -TargetRoot $targetRoot -Mode $mode
746
+ Invoke-InstallCompletenessValidation -TargetRoot $targetRoot
747
+
748
+ Write-InstalledVersion $targetRoot $appVersion
749
+ Sync-RootReadmeToItsMagic $targetRoot | Out-Null
750
+ $runbookBootstrap = Invoke-RunbookBootstrap -TargetRoot $targetRoot
751
+ foreach ($note in $runbookBootstrap.notes) { Write-Host $note }
752
+ if (-not $runbookBootstrap.ok) { exit 1 }
753
+
754
+ Show-ItsMagicBanner -IncludeInstallMessage
755
+ Write-Host "its-magic v$appVersion"
756
+ Write-Host "Repository: $repoUrl"
757
+ Write-Host ""
758
+ exit 0
759
+