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.
- package/README.md +1602 -755
- package/bin/its-magic.js +121 -11
- package/bin/postinstall.js +93 -0
- package/installer.ps1 +759 -0
- package/installer.py +1036 -0
- package/installer.sh +649 -0
- package/package.json +18 -14
- package/scripts/check_intake_template_parity.py +312 -0
- package/scripts/doc_profile_lib.py +415 -0
- package/scripts/guard_installer_publish.py +88 -0
- package/scripts/intake_bug_routing_guard.py +67 -0
- package/scripts/intake_evidence_lib.py +802 -0
- package/scripts/intake_evidence_validate.py +73 -0
- package/scripts/materialize_codebase_map.py +184 -0
- package/scripts/remote_config_summary.py +243 -0
- package/template/.cursor/agents/curator.mdc +35 -0
- package/template/.cursor/agents/dev.mdc +29 -0
- package/template/.cursor/agents/po.mdc +141 -0
- package/template/.cursor/agents/qa.mdc +28 -0
- package/template/.cursor/agents/release.mdc +28 -0
- package/template/.cursor/agents/security.mdc +98 -0
- package/template/.cursor/agents/tech-lead.mdc +57 -0
- package/template/.cursor/commands/architecture.md +127 -0
- package/template/.cursor/commands/ask.md +55 -0
- package/template/.cursor/commands/auto.md +533 -0
- package/template/.cursor/commands/discovery.md +47 -0
- package/template/.cursor/commands/execute.md +343 -0
- package/template/.cursor/commands/intake.md +323 -0
- package/template/.cursor/commands/map-codebase.md +46 -0
- package/template/.cursor/commands/memory-audit.md +175 -0
- package/template/.cursor/commands/milestone-complete.md +51 -0
- package/template/.cursor/commands/milestone-start.md +59 -0
- package/template/.cursor/commands/pause.md +64 -0
- package/{.cursor/commands/gsd-phase-context.md → template/.cursor/commands/phase-context.md} +6 -2
- package/template/.cursor/commands/plan-verify.md +34 -0
- package/template/.cursor/commands/qa.md +226 -0
- package/template/.cursor/commands/quick.md +41 -0
- package/template/.cursor/commands/refresh-context.md +82 -0
- package/template/.cursor/commands/release.md +523 -0
- package/template/.cursor/commands/research.md +49 -0
- package/template/.cursor/commands/resume.md +67 -0
- package/template/.cursor/commands/security-review.md +81 -0
- package/template/.cursor/commands/sprint-plan.md +103 -0
- package/template/.cursor/commands/status-reconcile.md +95 -0
- package/template/.cursor/commands/verify-work.md +152 -0
- package/template/.cursor/dev-environment.json.example +22 -0
- package/{.cursor → template/.cursor}/hooks/README.md +3 -2
- package/{.cursor/hooks/gsd-hook.py → template/.cursor/hooks/hook.py} +15 -6
- package/template/.cursor/hooks.json +26 -0
- package/template/.cursor/remote.json +31 -0
- package/template/.cursor/rules/caveman.mdc +184 -0
- package/template/.cursor/rules/coding-standards.mdc +39 -0
- package/template/.cursor/rules/core.mdc +111 -0
- package/template/.cursor/rules/handoffs.mdc +27 -0
- package/template/.cursor/rules/quality.mdc +49 -0
- package/template/.cursor/scratchpad.local.example.md +323 -0
- package/template/.cursor/scratchpad.md +324 -0
- package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/SKILL.md +2 -2
- package/template/.cursorignore +6 -0
- package/template/.env.example +28 -0
- package/template/.github/workflows/ci.yml +194 -0
- package/{.github → template/.github}/workflows/deploy.yml +5 -2
- package/template/.its-magic-version +1 -0
- package/template/CHANGELOG.md +11 -0
- package/template/README.md +1602 -0
- package/template/decisions/DEC-0001.md +11 -0
- package/template/decisions/DEC-0002.md +11 -0
- package/template/docs/developer/README.md +44 -0
- package/template/docs/engineering/architecture.md +9 -0
- package/template/docs/engineering/artifact-ordering-policy.md +48 -0
- package/template/docs/engineering/artifact-ownership-policy.md +57 -0
- package/template/docs/engineering/auto-orchestration-reference.md +1211 -0
- package/{docs → template/docs}/engineering/codebase-map.md +0 -5
- package/template/docs/engineering/compatibility-report.md +20 -0
- package/template/docs/engineering/compatibility-signals.md +7 -0
- package/template/docs/engineering/component-scope-report.md +16 -0
- package/template/docs/engineering/component-scope.md +19 -0
- package/template/docs/engineering/context/installer-owned-paths.manifest +96 -0
- package/template/docs/engineering/context/readme-section-affinity.json +30 -0
- package/template/docs/engineering/decisions.md +16 -0
- package/template/docs/engineering/legacy-drift-audit.md +21 -0
- package/template/docs/engineering/manifests/components/api-gateway.manifest.yaml +12 -0
- package/template/docs/engineering/manifests/registry.manifest.yaml +20 -0
- package/template/docs/engineering/manifests/repo.manifest.yaml +11 -0
- package/template/docs/engineering/phase-context.md +16 -0
- package/template/docs/engineering/release-targets.json +123 -0
- package/template/docs/engineering/research.md +29 -0
- package/template/docs/engineering/runbook.md +2391 -0
- package/template/docs/engineering/runtime-connectivity.md +81 -0
- package/template/docs/engineering/security-review.md +33 -0
- package/template/docs/engineering/spec-pack/README.md +20 -0
- package/template/docs/engineering/state-archive/README.md +15 -0
- package/template/docs/engineering/state.md +20 -0
- package/template/docs/engineering/status-normalization-report.md +19 -0
- package/template/docs/engineering/token-cost-parity-manifest.md +16 -0
- package/template/docs/engineering/us-0084-remote-e2e.md +44 -0
- package/template/docs/product/acceptance.md +1 -0
- package/template/docs/product/backlog.md +5 -0
- package/template/docs/product/vision.md +11 -0
- package/template/docs/user-guides/README.md +40 -0
- package/{handoffs → template/handoffs}/dev_to_qa.md +0 -3
- package/{handoffs → template/handoffs}/po_to_tl.md +0 -3
- package/{handoffs → template/handoffs}/qa_to_dev.md +1 -2
- package/template/handoffs/release_notes.md +51 -0
- package/template/handoffs/release_queue.md +47 -0
- package/template/handoffs/release_to_dev.md +31 -0
- package/template/handoffs/releases/Sxxxx-release-notes.md +62 -0
- package/template/handoffs/releases/vX.Y.Z-release-notes.md.example +21 -0
- package/template/handoffs/resume_brief.md +14 -0
- package/{handoffs → template/handoffs}/tl_to_dev.md +0 -2
- package/template/handoffs/token_cost_runs/README.md +26 -0
- package/template/its_magic/.its-magic-version +1 -0
- package/template/its_magic/README.md +9 -0
- package/template/scripts/auto_outer_driver.py +521 -0
- package/template/scripts/caveman_compress_input.py +903 -0
- package/template/scripts/check_downstream_ci_guard.py +67 -0
- package/template/scripts/check_intake_template_parity.py +312 -0
- package/template/scripts/check_token_cost_parity.py +69 -0
- package/template/scripts/dev_environment_lib.py +601 -0
- package/template/scripts/doc_profile_lib.py +415 -0
- package/template/scripts/downstream_ci_guard_lib.py +222 -0
- package/template/scripts/enforce-triad-hot-surface.py +753 -0
- package/template/scripts/guard_installer_publish.py +88 -0
- package/template/scripts/intake_bug_resume_brief_refresh.py +303 -0
- package/template/scripts/intake_bug_routing_guard.py +67 -0
- package/template/scripts/intake_evidence_lib.py +802 -0
- package/template/scripts/intake_evidence_validate.py +73 -0
- package/template/scripts/materialize_codebase_map.py +184 -0
- package/template/scripts/pack_json_validate.py +130 -0
- package/template/scripts/project_readme_coverage_lib.py +417 -0
- package/template/scripts/readme_feature_coverage_lib.py +608 -0
- package/{scripts → template/scripts}/release-all.sh +27 -4
- package/template/scripts/release_changelog_backfill.py +153 -0
- package/template/scripts/release_changelog_lib.py +544 -0
- package/template/scripts/release_changelog_validate.py +134 -0
- package/template/scripts/remote_config_summary.py +243 -0
- package/template/scripts/sync_push_gates.py +198 -0
- package/template/scripts/token_cost_compare.py +40 -0
- package/template/scripts/token_cost_lib.py +108 -0
- package/template/scripts/uat_probe_lib.py +868 -0
- package/template/scripts/validate-and-push.ps1 +280 -0
- package/template/scripts/validate-and-push.sh +243 -0
- package/template/scripts/validate_doc_profile.py +103 -0
- package/template/scripts/validate_project_readme_coverage.py +151 -0
- package/template/scripts/validate_readme_feature_coverage.py +140 -0
- package/template/sprints/S0001/progress.md +1 -0
- package/template/sprints/S0001/qa-findings.md +9 -0
- package/template/sprints/S0001/release-findings.md +24 -0
- package/template/sprints/S0001/sprint.md +9 -0
- package/template/sprints/S0001/summary.md +7 -0
- package/template/sprints/S0001/tasks.md +1 -0
- package/template/sprints/S0001/uat.json +6 -0
- package/template/sprints/S0001/uat.md +5 -0
- package/template/sprints/quick/Q0001/summary.md +1 -0
- package/{sprints → template/sprints}/quick/Q0001/task.json +0 -1
- package/.cursor/agents/curator.mdc +0 -21
- package/.cursor/agents/dev.mdc +0 -20
- package/.cursor/agents/po.mdc +0 -19
- package/.cursor/agents/qa.mdc +0 -19
- package/.cursor/agents/release.mdc +0 -19
- package/.cursor/agents/tech-lead.mdc +0 -21
- package/.cursor/commands/gsd-architecture.md +0 -29
- package/.cursor/commands/gsd-auto.md +0 -27
- package/.cursor/commands/gsd-discovery.md +0 -27
- package/.cursor/commands/gsd-execute.md +0 -32
- package/.cursor/commands/gsd-intake.md +0 -28
- package/.cursor/commands/gsd-map-codebase.md +0 -25
- package/.cursor/commands/gsd-milestone-complete.md +0 -24
- package/.cursor/commands/gsd-milestone-start.md +0 -26
- package/.cursor/commands/gsd-pause.md +0 -25
- package/.cursor/commands/gsd-plan-verify.md +0 -26
- package/.cursor/commands/gsd-qa.md +0 -28
- package/.cursor/commands/gsd-quick.md +0 -24
- package/.cursor/commands/gsd-refresh-context.md +0 -26
- package/.cursor/commands/gsd-release.md +0 -29
- package/.cursor/commands/gsd-research.md +0 -28
- package/.cursor/commands/gsd-resume.md +0 -26
- package/.cursor/commands/gsd-sprint-plan.md +0 -30
- package/.cursor/commands/gsd-verify-work.md +0 -25
- package/.cursor/hooks.json +0 -26
- package/.cursor/plans/cursor-gsd-team-kit_8cfee9b8.plan.md +0 -57
- package/.cursor/remote.json +0 -18
- package/.cursor/rules/gsd-core.mdc +0 -18
- package/.cursor/rules/gsd-handoffs.mdc +0 -10
- package/.cursor/rules/gsd-quality.mdc +0 -15
- package/.cursor/scratchpad.md +0 -34
- package/.github/workflows/ci.yml +0 -47
- package/decisions/DEC-0001.md +0 -21
- package/decisions/DEC-0002.md +0 -21
- package/docs/engineering/architecture.md +0 -354
- package/docs/engineering/decisions.md +0 -6
- package/docs/engineering/research.md +0 -11
- package/docs/engineering/runbook.md +0 -32
- package/docs/engineering/state.md +0 -33
- package/docs/product/acceptance.md +0 -6
- package/docs/product/backlog.md +0 -7
- package/docs/product/vision.md +0 -46
- package/gsd-installer.ps1 +0 -189
- package/gsd-installer.py +0 -195
- package/gsd-installer.sh +0 -201
- package/handoffs/release_notes.md +0 -14
- package/handoffs/resume_brief.md +0 -8
- package/milestones/M0001/milestone.json +0 -7
- package/milestones/M0001/phases.json +0 -9
- package/milestones/M0001/progress.md +0 -3
- package/milestones/M0001/summary.md +0 -3
- package/scripts/generate-release-notes.ps1 +0 -74
- package/scripts/generate-release-notes.sh +0 -63
- package/scripts/release-all.ps1 +0 -423
- package/sprints/S0001/progress.md +0 -4
- package/sprints/S0001/qa-findings.md +0 -113
- package/sprints/S0001/sprint.md +0 -70
- package/sprints/S0001/summary.md +0 -46
- package/sprints/S0001/tasks.md +0 -35
- package/sprints/S0001/uat.json +0 -8
- package/sprints/S0001/uat.md +0 -8
- package/sprints/quick/Q0001/summary.md +0 -3
- /package/{.cursor/rules/gsd-escalation.mdc → template/.cursor/rules/escalation.mdc} +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/acceptance.json +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/acceptance.md +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/architecture.json +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/architecture.md +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/decision.json +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/decision.md +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/handoff.json +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/handoff.md +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/milestone.json +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/phase-context.json +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/plan-verify.json +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/sprint.json +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/sprint.md +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/story.json +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/story.md +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/uat.json +0 -0
- /package/{docs → template/docs}/engineering/context/phase-template.json +0 -0
- /package/{docs → template/docs}/engineering/dependencies.json +0 -0
- /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())
|