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,280 @@
1
+ # -------------------------------------------------------------------
2
+ # validate-and-push.ps1 — local test-fix-push loop
3
+ #
4
+ # Part of the its-magic quality chain:
5
+ # Cursor AI loop → validate-and-push → CI auto-fix (GitHub)
6
+ #
7
+ # Reads merged scratchpad (installer merge / DEC-0055) for sync policy and
8
+ # runbook keys for commands. Opt-in push only when policy allows; reason codes
9
+ # on stdout/stderr. See docs/engineering/runbook.md (executable wiring: DEC-0058).
10
+ # -------------------------------------------------------------------
11
+
12
+ Param(
13
+ [int]$MaxAttempts = 5,
14
+ [string]$Branch,
15
+ [switch]$NoCommit,
16
+ [switch]$DryRun
17
+ )
18
+
19
+ $ErrorActionPreference = "Stop"
20
+ $root = Resolve-Path (Join-Path $PSScriptRoot "..")
21
+
22
+ function Log-Info { param($Msg) Write-Host "[info] $Msg" -ForegroundColor Cyan }
23
+ function Log-Pass { param($Msg) Write-Host "[pass] $Msg" -ForegroundColor Green }
24
+ function Log-Fail { param($Msg) Write-Host "[fail] $Msg" -ForegroundColor Red }
25
+ function Log-Warn { param($Msg) Write-Host "[warn] $Msg" -ForegroundColor Yellow }
26
+
27
+ function Resolve-PythonExe {
28
+ foreach ($name in @("python", "python3")) {
29
+ $cmd = Get-Command $name -ErrorAction SilentlyContinue
30
+ if ($cmd) { return $cmd.Source }
31
+ }
32
+ return $null
33
+ }
34
+
35
+ function Invoke-GateJson {
36
+ param(
37
+ [string]$PythonExe,
38
+ [string]$GateScript,
39
+ [string]$SubCommand,
40
+ [string]$RepoRoot,
41
+ [string]$BranchName
42
+ )
43
+ $outFile = [System.IO.Path]::GetTempFileName()
44
+ $errFile = [System.IO.Path]::GetTempFileName()
45
+ try {
46
+ $p = Start-Process -FilePath $PythonExe `
47
+ -ArgumentList @($GateScript, $SubCommand, "--root", $RepoRoot, "--branch", $BranchName) `
48
+ -Wait -PassThru -NoNewWindow `
49
+ -RedirectStandardOutput $outFile -RedirectStandardError $errFile
50
+ $stderr = if (Test-Path $errFile) { Get-Content -Raw -Path $errFile } else { "" }
51
+ $stdout = if (Test-Path $outFile) { Get-Content -Raw -Path $outFile } else { "" }
52
+ if ($stderr.Trim().Length -gt 0) { Write-Host $stderr.TrimEnd() }
53
+ return @{ ExitCode = $p.ExitCode; StdOut = $stdout.Trim() }
54
+ }
55
+ finally {
56
+ Remove-Item -Path $outFile, $errFile -ErrorAction SilentlyContinue
57
+ }
58
+ }
59
+
60
+ function Read-RunbookKey {
61
+ param([string]$Key)
62
+ $runbook = Join-Path $root "docs\engineering\runbook.md"
63
+ if (-not (Test-Path $runbook -PathType Leaf)) { return "" }
64
+ $content = Get-Content -Path $runbook -Raw
65
+ $m = [regex]::Match($content, "(?m)^${Key}:[ \t]*(.+)$")
66
+ if (-not $m.Success) { return "" }
67
+ $val = $m.Groups[1].Value.Trim()
68
+ if ($val -in @("", "...", "<...>", "TODO")) { return "" }
69
+ return $val
70
+ }
71
+
72
+ $py = Resolve-PythonExe
73
+ if (-not $py) {
74
+ Log-Fail "reason_code=PYTHON_NOT_ON_PATH"
75
+ Log-Warn "Install Python 3 and ensure it is on PATH for merged scratchpad policy gates."
76
+ exit 1
77
+ }
78
+
79
+ $gateScript = Join-Path $root "scripts\sync_push_gates.py"
80
+ if (-not (Test-Path $gateScript -PathType Leaf)) {
81
+ Log-Fail "reason_code=SYNC_GATE_SCRIPT_MISSING"
82
+ exit 1
83
+ }
84
+
85
+ if (-not $Branch) {
86
+ $Branch = (git -C $root rev-parse --abbrev-ref HEAD 2>$null)
87
+ if (-not $Branch) { $Branch = "main" }
88
+ }
89
+
90
+ Log-Info "validate-and-push loop"
91
+ Log-Info "Branch: $Branch | Max attempts: $MaxAttempts"
92
+ if ($DryRun) { Log-Info "Dry-run: no git push will run." }
93
+ Write-Host ""
94
+
95
+ $pol = Invoke-GateJson -PythonExe $py -GateScript $gateScript -SubCommand "policy" -RepoRoot $root -BranchName $Branch
96
+ try {
97
+ $polObj = $pol.StdOut | ConvertFrom-Json
98
+ } catch {
99
+ Log-Fail "reason_code=SYNC_POLICY_PARSE_FAILED"
100
+ exit 1
101
+ }
102
+ if (-not $polObj.ok) {
103
+ Log-Fail ("reason_code=" + $polObj.reason_code)
104
+ exit 1
105
+ }
106
+
107
+ $TestCmd = Read-RunbookKey "TEST_COMMAND"
108
+ $LintCmd = Read-RunbookKey "LINT_COMMAND"
109
+ $TypecheckCmd = Read-RunbookKey "TYPECHECK_COMMAND"
110
+ $LintFixCmd = Read-RunbookKey "LINT_FIX_COMMAND"
111
+ $FormatCmd = Read-RunbookKey "FORMAT_COMMAND"
112
+ $timeoutRaw = Read-RunbookKey "TEST_TIMEOUT_SECONDS"
113
+ $TestTimeoutSec = 120
114
+ if ($timeoutRaw -match '^\d+$') { $TestTimeoutSec = [int]$timeoutRaw }
115
+
116
+ if (-not $TestCmd) {
117
+ Log-Fail "reason_code=TEST_COMMAND_MISSING"
118
+ Log-Warn "TEST_COMMAND is required by sync policy for push-eligible paths."
119
+ exit 1
120
+ }
121
+
122
+ if ($TestCmd) { Log-Info "TEST_COMMAND: $TestCmd" }
123
+ if ($LintCmd) { Log-Info "LINT_COMMAND: $LintCmd" }
124
+ if ($TypecheckCmd) { Log-Info "TYPECHECK_COMMAND: $TypecheckCmd" }
125
+ if ($LintFixCmd) { Log-Info "LINT_FIX_COMMAND: $LintFixCmd" }
126
+ if ($FormatCmd) { Log-Info "FORMAT_COMMAND: $FormatCmd" }
127
+ Log-Info "TEST_TIMEOUT_SECONDS: $TestTimeoutSec"
128
+ Write-Host ""
129
+
130
+ function Run-Cmd {
131
+ param([string]$Cmd, [int]$TimeoutSec = 0)
132
+ $prev = $ErrorActionPreference
133
+ $ErrorActionPreference = "Continue"
134
+ try {
135
+ if ($TimeoutSec -le 0) {
136
+ Invoke-Expression $Cmd
137
+ return @{ Ok = ($LASTEXITCODE -eq 0); TimedOut = $false }
138
+ }
139
+ $job = Start-Job -ScriptBlock {
140
+ param($c, $r)
141
+ Set-Location $r
142
+ try {
143
+ Invoke-Expression $c | Out-Null
144
+ } catch {
145
+ return 1
146
+ }
147
+ if ($null -eq $LASTEXITCODE) { return 0 }
148
+ return $LASTEXITCODE
149
+ } -ArgumentList $Cmd, $root
150
+ $finished = Wait-Job -Job $job -Timeout $TimeoutSec
151
+ if (-not $finished -or $job.State -eq "Running") {
152
+ Stop-Job -Job $job -ErrorAction SilentlyContinue
153
+ Remove-Job -Job $job -Force -ErrorAction SilentlyContinue
154
+ return @{ Ok = $false; TimedOut = $true }
155
+ }
156
+ $code = Receive-Job -Job $job
157
+ Remove-Job -Job $job -Force -ErrorAction SilentlyContinue
158
+ $exitNum = 1
159
+ if ($null -ne $code) { $exitNum = [int]$code[-1] }
160
+ return @{ Ok = ($exitNum -eq 0); TimedOut = $false }
161
+ } finally {
162
+ $ErrorActionPreference = $prev
163
+ }
164
+ }
165
+
166
+ $passed = $false
167
+ for ($attempt = 1; $attempt -le $MaxAttempts; $attempt++) {
168
+ Log-Info "--- Attempt $attempt / $MaxAttempts ---"
169
+
170
+ Push-Location $root
171
+ $allOk = $true
172
+
173
+ if ($FormatCmd) {
174
+ Log-Info "Running formatter..."
175
+ $r = Run-Cmd -Cmd $FormatCmd -TimeoutSec 0
176
+ if ($r.Ok) { Log-Pass "Format OK" } else { Log-Warn "Formatter reported issues (non-blocking)" }
177
+ }
178
+
179
+ if ($LintFixCmd) {
180
+ Log-Info "Running lint auto-fix..."
181
+ Run-Cmd -Cmd $LintFixCmd -TimeoutSec 0 | Out-Null
182
+ }
183
+
184
+ if ($LintCmd) {
185
+ Log-Info "Running lint check..."
186
+ $r = Run-Cmd -Cmd $LintCmd -TimeoutSec $TestTimeoutSec
187
+ if ($r.TimedOut) {
188
+ Log-Fail "reason_code=OPTIONAL_CHECK_FAILED"
189
+ $allOk = $false
190
+ } elseif ($r.Ok) { Log-Pass "Lint OK" } else {
191
+ Log-Fail "reason_code=OPTIONAL_CHECK_FAILED"
192
+ $allOk = $false
193
+ }
194
+ }
195
+
196
+ if ($TypecheckCmd) {
197
+ Log-Info "Running typecheck..."
198
+ $r = Run-Cmd -Cmd $TypecheckCmd -TimeoutSec $TestTimeoutSec
199
+ if ($r.TimedOut) {
200
+ Log-Fail "reason_code=OPTIONAL_CHECK_FAILED"
201
+ $allOk = $false
202
+ } elseif ($r.Ok) { Log-Pass "Typecheck OK" } else {
203
+ Log-Fail "reason_code=OPTIONAL_CHECK_FAILED"
204
+ $allOk = $false
205
+ }
206
+ }
207
+
208
+ if ($TestCmd) {
209
+ Log-Info "Running tests..."
210
+ $r = Run-Cmd -Cmd $TestCmd -TimeoutSec $TestTimeoutSec
211
+ if ($r.TimedOut) {
212
+ Log-Fail "reason_code=TEST_TIMEOUT"
213
+ $allOk = $false
214
+ } elseif ($r.Ok) { Log-Pass "Tests OK" } else {
215
+ Log-Fail "reason_code=TEST_FAILED"
216
+ $allOk = $false
217
+ }
218
+ }
219
+
220
+ Pop-Location
221
+
222
+ if ($allOk) {
223
+ Log-Pass "All checks passed on attempt $attempt."
224
+ $passed = $true
225
+ break
226
+ }
227
+
228
+ if ($attempt -ge $MaxAttempts) {
229
+ Log-Fail "Reached max attempts ($MaxAttempts). Aborting push."
230
+ Write-Host ""
231
+ Log-Warn "Fix the issues above, then re-run:"
232
+ Log-Warn " powershell scripts\validate-and-push.ps1"
233
+ exit 1
234
+ }
235
+
236
+ Write-Host ""
237
+ Log-Warn "Fix the failing checks, then press Enter to retry (or Ctrl+C to abort)..."
238
+ Read-Host | Out-Null
239
+ }
240
+
241
+ if (-not $passed) { exit 1 }
242
+
243
+ $post = Invoke-GateJson -PythonExe $py -GateScript $gateScript -SubCommand "post" -RepoRoot $root -BranchName $Branch
244
+ try {
245
+ $postObj = $post.StdOut | ConvertFrom-Json
246
+ } catch {
247
+ Log-Fail "reason_code=SYNC_POST_PARSE_FAILED"
248
+ exit 1
249
+ }
250
+ if (-not $postObj.ok) {
251
+ Log-Fail ("reason_code=" + $postObj.reason_code)
252
+ exit 1
253
+ }
254
+
255
+ Write-Host ""
256
+
257
+ if ($DryRun -or $NoCommit) {
258
+ if ($DryRun) {
259
+ Log-Pass "reason_code=SYNC_PUSHED (dry-run; no git push)"
260
+ } else {
261
+ Log-Info "Auto-commit disabled (--NoCommit). Push manually when ready."
262
+ Log-Pass "reason_code=SYNC_PUSHED (checks only; push skipped by flag)"
263
+ }
264
+ exit 0
265
+ }
266
+
267
+ Push-Location $root
268
+ $status = git status --porcelain
269
+ if ($status) {
270
+ Log-Info "Staging and committing changes..."
271
+ git add -A
272
+ git commit -m "fix: address check failures (validate-and-push)"
273
+ } else {
274
+ Log-Info "Working tree clean, nothing to commit."
275
+ }
276
+
277
+ Log-Info "Pushing to origin/$Branch..."
278
+ git push origin $Branch
279
+ Log-Pass "reason_code=SYNC_PUSHED"
280
+ Pop-Location
@@ -0,0 +1,243 @@
1
+ #!/usr/bin/env bash
2
+ # -------------------------------------------------------------------
3
+ # validate-and-push.sh — local test-fix-push loop
4
+ #
5
+ # Part of the its-magic quality chain:
6
+ # Cursor AI loop → validate-and-push → CI auto-fix (GitHub)
7
+ #
8
+ # Bash is required for safe eval + optional timeout wrapping (DEC-0058 / parity
9
+ # with validate-and-push.ps1).
10
+ #
11
+ # Reads merged scratchpad (installer merge) for sync policy and runbook keys
12
+ # for commands. Opt-in push only when policy allows. See runbook (DEC-0058).
13
+ # -------------------------------------------------------------------
14
+ set -euo pipefail
15
+
16
+ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
17
+ DRY_RUN=0
18
+ if [ "${1:-}" = "--dry-run" ]; then
19
+ DRY_RUN=1
20
+ shift
21
+ fi
22
+
23
+ MAX_ATTEMPTS="${1:-5}"
24
+ BRANCH="${2:-}"
25
+ AUTO_COMMIT="${3:-true}"
26
+
27
+ pass_color="\033[1;32m"
28
+ fail_color="\033[1;31m"
29
+ info_color="\033[1;36m"
30
+ warn_color="\033[1;33m"
31
+ reset="\033[0m"
32
+
33
+ log_info() { printf "${info_color}[info]${reset} %s\n" "$*"; }
34
+ log_pass() { printf "${pass_color}[pass]${reset} %s\n" "$*"; }
35
+ log_fail() { printf "${fail_color}[fail]${reset} %s\n" "$*"; }
36
+ log_warn() { printf "${warn_color}[warn]${reset} %s\n" "$*"; }
37
+
38
+ resolve_python() {
39
+ if command -v python >/dev/null 2>&1; then printf "%s" "python"; return; fi
40
+ if command -v python3 >/dev/null 2>&1; then printf "%s" "python3"; return; fi
41
+ printf "%s" ""
42
+ }
43
+
44
+ run_with_timeout() {
45
+ local sec="$1"
46
+ shift
47
+ if command -v timeout >/dev/null 2>&1; then
48
+ timeout "$sec" "$@"
49
+ elif command -v gtimeout >/dev/null 2>&1; then
50
+ gtimeout "$sec" "$@"
51
+ else
52
+ "$@"
53
+ fi
54
+ }
55
+
56
+ # Run runbook command string from repo root; optional wall-clock cap.
57
+ run_runbook_cmd() {
58
+ local cmd="$1"
59
+ local kind="$2"
60
+ local sec="$3"
61
+ if command -v timeout >/dev/null 2>&1 || command -v gtimeout >/dev/null 2>&1; then
62
+ if ! run_with_timeout "$sec" bash -c 'cd "$1" && eval "$2"' _ "$ROOT" "$cmd"; then
63
+ local ec=$?
64
+ if [ "$kind" = "test" ]; then
65
+ if [ "$ec" = "124" ] || [ "$ec" = "143" ]; then log_fail "reason_code=TEST_TIMEOUT"; else log_fail "reason_code=TEST_FAILED"; fi
66
+ else
67
+ log_fail "reason_code=OPTIONAL_CHECK_FAILED"
68
+ fi
69
+ return 1
70
+ fi
71
+ else
72
+ if ! ( cd "$ROOT" && eval "$cmd" ); then
73
+ if [ "$kind" = "test" ]; then log_fail "reason_code=TEST_FAILED"; else log_fail "reason_code=OPTIONAL_CHECK_FAILED"; fi
74
+ return 1
75
+ fi
76
+ fi
77
+ return 0
78
+ }
79
+
80
+ RUNBOOK="$ROOT/docs/engineering/runbook.md"
81
+
82
+ read_runbook_key() {
83
+ local key="$1"
84
+ if [ ! -f "$RUNBOOK" ]; then return; fi
85
+ local value
86
+ value=$(sed -n "s/^${key}:[[:space:]]*\(.\{1,\}\)$/\1/p" "$RUNBOOK" | head -n 1)
87
+ case "$value" in
88
+ ""|"..."|"<...>"|"TODO") return ;;
89
+ esac
90
+ printf "%s" "$value"
91
+ }
92
+
93
+ run_sync_gate() {
94
+ local sub="$1"
95
+ local out err line gate_ok gate_rc
96
+ out="$(mktemp)"
97
+ err="$(mktemp)"
98
+ set +e
99
+ "$PY" "$GATE_SCRIPT" "$sub" --root "$ROOT" --branch "$BRANCH" >"$out" 2>"$err"
100
+ set -e
101
+ if [ -s "$err" ]; then cat "$err" >&2; fi
102
+ line="$(grep '^{' "$out" | tail -n 1)"
103
+ rm -f "$out" "$err"
104
+ if [ -z "$line" ]; then
105
+ log_fail "reason_code=SYNC_GATE_EMPTY_RESPONSE"
106
+ return 1
107
+ fi
108
+ gate_ok="$("$PY" -c "import json,sys; print(1 if json.loads(sys.argv[1]).get('ok') else 0)" "$line" 2>/dev/null || echo 0)"
109
+ gate_rc="$("$PY" -c "import json,sys; print(json.loads(sys.argv[1]).get('reason_code') or '')" "$line" 2>/dev/null || true)"
110
+ if [ "$gate_ok" != "1" ]; then
111
+ log_fail "reason_code=${gate_rc:-SYNC_GATE_BLOCK}"
112
+ return 1
113
+ fi
114
+ return 0
115
+ }
116
+
117
+ PY="$(resolve_python)"
118
+ if [ -z "$PY" ]; then
119
+ log_fail "reason_code=PYTHON_NOT_ON_PATH"
120
+ log_warn "Install Python 3 and ensure it is on PATH for merged scratchpad policy gates."
121
+ exit 1
122
+ fi
123
+
124
+ GATE_SCRIPT="$ROOT/scripts/sync_push_gates.py"
125
+ if [ ! -f "$GATE_SCRIPT" ]; then
126
+ log_fail "reason_code=SYNC_GATE_SCRIPT_MISSING"
127
+ exit 1
128
+ fi
129
+
130
+ if [ -z "$BRANCH" ]; then
131
+ BRANCH=$(git -C "$ROOT" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")
132
+ fi
133
+
134
+ log_info "validate-and-push loop"
135
+ log_info "Branch: $BRANCH | Max attempts: $MAX_ATTEMPTS"
136
+ if [ "$DRY_RUN" = "1" ]; then log_info "Dry-run: no git push will run."; fi
137
+ printf "\n"
138
+
139
+ if ! run_sync_gate policy; then exit 1; fi
140
+
141
+ TEST_CMD=$(read_runbook_key "TEST_COMMAND")
142
+ LINT_CMD=$(read_runbook_key "LINT_COMMAND")
143
+ TYPECHECK_CMD=$(read_runbook_key "TYPECHECK_COMMAND")
144
+ LINT_FIX_CMD=$(read_runbook_key "LINT_FIX_COMMAND")
145
+ FORMAT_CMD=$(read_runbook_key "FORMAT_COMMAND")
146
+ TIMEOUT_RAW=$(read_runbook_key "TEST_TIMEOUT_SECONDS")
147
+ TEST_TIMEOUT_SEC=120
148
+ case "$TIMEOUT_RAW" in
149
+ ''|*[!0-9]*) ;;
150
+ *) TEST_TIMEOUT_SEC="$TIMEOUT_RAW" ;;
151
+ esac
152
+
153
+ if [ -z "$TEST_CMD" ]; then
154
+ log_fail "reason_code=TEST_COMMAND_MISSING"
155
+ log_warn "TEST_COMMAND is required by sync policy for push-eligible paths."
156
+ exit 1
157
+ fi
158
+
159
+ [ -n "$TEST_CMD" ] && log_info "TEST_COMMAND: $TEST_CMD"
160
+ [ -n "$LINT_CMD" ] && log_info "LINT_COMMAND: $LINT_CMD"
161
+ [ -n "$TYPECHECK_CMD" ] && log_info "TYPECHECK_COMMAND: $TYPECHECK_CMD"
162
+ [ -n "$LINT_FIX_CMD" ] && log_info "LINT_FIX_COMMAND: $LINT_FIX_CMD"
163
+ [ -n "$FORMAT_CMD" ] && log_info "FORMAT_COMMAND: $FORMAT_CMD"
164
+ log_info "TEST_TIMEOUT_SECONDS: $TEST_TIMEOUT_SEC"
165
+ printf "\n"
166
+
167
+ attempt=0
168
+ while [ "$attempt" -lt "$MAX_ATTEMPTS" ]; do
169
+ attempt=$((attempt + 1))
170
+ log_info "--- Attempt $attempt / $MAX_ATTEMPTS ---"
171
+
172
+ cd "$ROOT"
173
+ all_ok=true
174
+
175
+ if [ -n "$FORMAT_CMD" ]; then
176
+ log_info "Running formatter..."
177
+ if eval "$FORMAT_CMD"; then log_pass "Format OK"; else log_warn "Formatter reported issues (non-blocking)"; fi
178
+ fi
179
+
180
+ if [ -n "$LINT_FIX_CMD" ]; then
181
+ log_info "Running lint auto-fix..."
182
+ eval "$LINT_FIX_CMD" || true
183
+ fi
184
+
185
+ if [ -n "$LINT_CMD" ]; then
186
+ log_info "Running lint check..."
187
+ if run_runbook_cmd "$LINT_CMD" "optional" "$TEST_TIMEOUT_SEC"; then log_pass "Lint OK"; else all_ok=false; fi
188
+ fi
189
+
190
+ if [ -n "$TYPECHECK_CMD" ]; then
191
+ log_info "Running typecheck..."
192
+ if run_runbook_cmd "$TYPECHECK_CMD" "optional" "$TEST_TIMEOUT_SEC"; then log_pass "Typecheck OK"; else all_ok=false; fi
193
+ fi
194
+
195
+ if [ -n "$TEST_CMD" ]; then
196
+ log_info "Running tests..."
197
+ if run_runbook_cmd "$TEST_CMD" "test" "$TEST_TIMEOUT_SEC"; then log_pass "Tests OK"; else all_ok=false; fi
198
+ fi
199
+
200
+ if [ "$all_ok" = "true" ]; then
201
+ log_pass "All checks passed on attempt $attempt."
202
+ break
203
+ fi
204
+
205
+ if [ "$attempt" -ge "$MAX_ATTEMPTS" ]; then
206
+ log_fail "Reached max attempts ($MAX_ATTEMPTS). Aborting push."
207
+ printf "\n"
208
+ log_warn "Fix the issues above, then re-run:"
209
+ log_warn " bash scripts/validate-and-push.sh"
210
+ exit 1
211
+ fi
212
+
213
+ printf "\n"
214
+ log_warn "Fix the failing checks, then press Enter to retry (or Ctrl+C to abort)..."
215
+ read -r _unused
216
+ done
217
+
218
+ if ! run_sync_gate post; then exit 1; fi
219
+
220
+ printf "\n"
221
+
222
+ if [ "$DRY_RUN" = "1" ] || [ "$AUTO_COMMIT" != "true" ]; then
223
+ if [ "$DRY_RUN" = "1" ]; then
224
+ log_pass "reason_code=SYNC_PUSHED (dry-run; no git push)"
225
+ else
226
+ log_info "Auto-commit disabled. Push manually when ready."
227
+ log_pass "reason_code=SYNC_PUSHED (checks only; push skipped by flag)"
228
+ fi
229
+ exit 0
230
+ fi
231
+
232
+ cd "$ROOT"
233
+ if [ -n "$(git status --porcelain)" ]; then
234
+ log_info "Staging and committing changes..."
235
+ git add -A
236
+ git commit -m "fix: address check failures (validate-and-push)"
237
+ else
238
+ log_info "Working tree clean, nothing to commit."
239
+ fi
240
+
241
+ log_info "Pushing to origin/$BRANCH..."
242
+ git push origin "$BRANCH"
243
+ log_pass "reason_code=SYNC_PUSHED"
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Validate documentation profile surfaces (DEC-0059).
4
+
5
+ Reads merged scratchpad via installer.merge_scratchpad_layers (DEC-0058 pattern).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import os
12
+ import sys
13
+
14
+ # Repo root (parent of scripts/)
15
+ _SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
16
+ _REPO_ROOT = os.path.normpath(os.path.join(_SCRIPT_DIR, ".."))
17
+
18
+ if _REPO_ROOT not in sys.path:
19
+ sys.path.insert(0, _REPO_ROOT)
20
+ if _SCRIPT_DIR not in sys.path:
21
+ sys.path.insert(0, _SCRIPT_DIR)
22
+
23
+ import installer # noqa: E402
24
+ import doc_profile_lib # noqa: E402
25
+
26
+
27
+ def _fail(messages: list[str]) -> int:
28
+ for m in messages:
29
+ print(m, file=sys.stderr)
30
+ return 1
31
+
32
+
33
+ def main() -> int:
34
+ parser = argparse.ArgumentParser(description="Validate README/doc profile vs merged scratchpad.")
35
+ parser.add_argument(
36
+ "--repo",
37
+ default=_REPO_ROOT,
38
+ help="Target repository root (default: parent of scripts/).",
39
+ )
40
+ parser.add_argument(
41
+ "--no-template-parity",
42
+ action="store_true",
43
+ help="Skip active vs template/ parity (for fixture-only trees).",
44
+ )
45
+ parser.add_argument(
46
+ "--self-test",
47
+ action="store_true",
48
+ help="Run resolver matrix self-test and exit.",
49
+ )
50
+ args = parser.parse_args()
51
+
52
+ if args.self_test:
53
+ doc_profile_lib.self_test_resolver()
54
+ print("[DOC_PROFILE_SELF_TEST_OK]")
55
+ return 0
56
+
57
+ target = os.path.abspath(args.repo)
58
+ template_root = None if args.no_template_parity else os.path.join(target, "template")
59
+
60
+ merged, paths = installer.merge_scratchpad_layers(target)
61
+ merge_errors: list[str] = []
62
+ if not os.path.isfile(paths["example"]):
63
+ merge_errors.append(
64
+ "[DOC_PROFILE_MERGE_ERROR] EXAMPLE_LAYER_MISSING: "
65
+ f".cursor/scratchpad.local.example.md not found under {target}."
66
+ )
67
+ if not os.path.isfile(paths["baseline"]):
68
+ merge_errors.append(
69
+ "[DOC_PROFILE_MERGE_ERROR] MATERIALIZED_BASELINE_MISSING: "
70
+ f".cursor/scratchpad.md not found under {target}."
71
+ )
72
+ if merge_errors:
73
+ return _fail(merge_errors)
74
+
75
+ ok, scratch_diagnostics = installer.validate_merged_scratchpad(target)
76
+ if not ok:
77
+ out = [ln for ln in scratch_diagnostics if "SCRATCHPAD_MERGE_ERROR" in ln or "REQUIRED_KEY" in ln]
78
+ mapped = [
79
+ ln.replace("[SCRATCHPAD_MERGE_ERROR]", "[DOC_PROFILE_MERGE_ERROR]", 1)
80
+ if ln.startswith("[SCRATCHPAD_MERGE_ERROR]")
81
+ else f"[DOC_PROFILE_MERGE_ERROR] {ln}"
82
+ for ln in (out or scratch_diagnostics)
83
+ ]
84
+ return _fail(mapped)
85
+
86
+ readme_path = os.path.join(target, "README.md")
87
+ readme_txt = ""
88
+ if os.path.isfile(readme_path):
89
+ with open(readme_path, "r", encoding="utf-8") as f:
90
+ readme_txt = f.read()
91
+ for w in doc_profile_lib.optional_mode_warnings(merged, readme_txt):
92
+ print(w, file=sys.stderr)
93
+
94
+ errs = doc_profile_lib.validate_repo_doc_profile(target, merged, template_root)
95
+ if errs:
96
+ return _fail(errs)
97
+
98
+ print("[DOC_PROFILE_VALIDATE_OK]")
99
+ return 0
100
+
101
+
102
+ if __name__ == "__main__":
103
+ raise SystemExit(main())