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
@@ -1,423 +0,0 @@
1
- <#
2
- .SYNOPSIS
3
- Unified release: npm + Chocolatey + Homebrew (all three at once).
4
- .DESCRIPTION
5
- 1. Bumps version in package.json (patch|minor|major|explicit)
6
- 2. Publishes to npm
7
- 3. Creates a GitHub release with a source zip (for Homebrew/Chocolatey URLs)
8
- 4. Updates Chocolatey nuspec + pushes to chocolatey.org
9
- 5. Updates Homebrew formula with new URL + sha256
10
- .PARAMETER Bump
11
- Version bump type: patch, minor, major, or an explicit semver (e.g. 1.2.3).
12
- Default: patch
13
- .PARAMETER NpmTag
14
- npm dist-tag (e.g. latest, beta, rc). Default: latest
15
- .PARAMETER SkipNpm
16
- Skip npm publish.
17
- .PARAMETER SkipChoco
18
- Skip Chocolatey push.
19
- .PARAMETER SkipBrew
20
- Skip Homebrew formula update.
21
- .PARAMETER DryRun
22
- Print what would happen without executing.
23
- #>
24
- param(
25
- [string]$Bump = "patch",
26
- [string]$NpmTag = "latest",
27
- [switch]$SkipNpm,
28
- [switch]$SkipChoco,
29
- [switch]$SkipBrew,
30
- [switch]$DryRun,
31
- [switch]$SkipBrewPush,
32
- [string]$BrewTapRepo = "",
33
- [string]$BrewTapBranch = "main",
34
- [string]$BrewTapDir = "",
35
- [bool]$CreateBrewTapIfMissing = $true
36
- )
37
-
38
- $ErrorActionPreference = 'Stop'
39
- $repoRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
40
- if (-not (Test-Path (Join-Path $repoRoot 'package.json'))) {
41
- $repoRoot = Split-Path -Parent $PSScriptRoot
42
- }
43
- Push-Location $repoRoot
44
-
45
- # ── Helpers ──────────────────────────────────────────────────────────
46
- function Log($msg) { Write-Host "[release] $msg" -ForegroundColor Cyan }
47
- function Warn($msg){ Write-Host "[release] $msg" -ForegroundColor Yellow }
48
- function Err($msg) { Write-Host "[release] $msg" -ForegroundColor Red; Pop-Location; exit 1 }
49
-
50
- # Quote char for building regex patterns (avoids PS 5.1 parser issues)
51
- $DQ = [char]34
52
- $chocoFailed = $false
53
- $brewTapFailed = $false
54
- $brewTapPushed = $false
55
-
56
- # ── 1. Version bump ─────────────────────────────────────────────────
57
- Log "Bumping version ($Bump) ..."
58
- if ($DryRun) {
59
- Log "(dry-run) would run: npm version $Bump --no-git-tag-version"
60
- $newVersion = "0.0.0-dryrun"
61
- } else {
62
- npm version $Bump --no-git-tag-version | Out-Null
63
- $pkg = Get-Content package.json -Raw | ConvertFrom-Json
64
- $newVersion = $pkg.version
65
- }
66
- Log "New version: $newVersion"
67
-
68
- # ── 2. npm publish ───────────────────────────────────────────────────
69
- if (-not $SkipNpm) {
70
- Log "Publishing to npm (tag=$NpmTag) ..."
71
- if ($DryRun) {
72
- Log "(dry-run) would run: npm publish --tag $NpmTag"
73
- } else {
74
- npm publish --tag $NpmTag
75
- if ($LASTEXITCODE -ne 0) { Err "npm publish failed" }
76
- Log "npm publish OK"
77
- }
78
- } else {
79
- Warn "Skipping npm"
80
- }
81
-
82
- # ── 3. GitHub release (needed by Homebrew + Chocolatey) ──────────────
83
- $ghAvailable = $null -ne (Get-Command gh -ErrorAction SilentlyContinue)
84
- $tagName = "v$newVersion"
85
- $zipUrl = ""
86
- $tarUrl = ""
87
-
88
- $isPrerelease = $newVersion -match '-'
89
- $chocoVersion = $newVersion
90
- if ($newVersion -match '^(\d+\.\d+\.\d+)-(\d+)$') {
91
- # Old Chocolatey/NuGet rejects numeric-only prerelease labels (e.g. 0.1.1-1).
92
- # Convert to a compatible label for nuspec only.
93
- $chocoVersion = "$($Matches[1])-beta$($Matches[2])"
94
- }
95
- if ($chocoVersion -ne $newVersion) {
96
- Log "Chocolatey version normalized: $newVersion -> $chocoVersion"
97
- }
98
-
99
- if ($ghAvailable) {
100
- Log "Creating GitHub release $tagName ..."
101
- if ($DryRun) {
102
- Log "(dry-run) would run: gh release create $tagName --generate-notes"
103
- $zipUrl = "https://github.com/USER/its-magic/archive/refs/tags/$tagName.zip"
104
- $tarUrl = "https://github.com/USER/its-magic/archive/refs/tags/$tagName.tar.gz"
105
- } else {
106
- $ghArgs = @("release", "create", $tagName, "--generate-notes", "--title", $tagName)
107
- if ($isPrerelease) { $ghArgs += "--prerelease" }
108
- & gh @ghArgs
109
- if ($LASTEXITCODE -ne 0) { Warn "gh release create failed - continuing anyway" }
110
- # Derive archive URL from current remote
111
- $remoteUrl = git remote get-url origin 2>$null
112
- if ($remoteUrl -match 'github\.com[:/](.+?)(\.git)?$') {
113
- $repoSlug = $Matches[1]
114
- $zipUrl = "https://github.com/$repoSlug/archive/refs/tags/$tagName.zip"
115
- $tarUrl = "https://github.com/$repoSlug/archive/refs/tags/$tagName.tar.gz"
116
- }
117
- Log "GitHub release created: $tagName"
118
- }
119
- } else {
120
- Warn "gh CLI not found - skipping GitHub release"
121
- $zipUrl = "https://github.com/USER/its-magic/archive/refs/tags/$tagName.zip"
122
- $tarUrl = "https://github.com/USER/its-magic/archive/refs/tags/$tagName.tar.gz"
123
- }
124
-
125
- # ── 4. Chocolatey ───────────────────────────────────────────────────
126
- if (-not $SkipChoco) {
127
- $chocoDir = Join-Path $repoRoot 'packaging\chocolatey'
128
- $nuspec = Join-Path $chocoDir 'its-magic.nuspec'
129
- $chocoInstall = Join-Path $chocoDir 'tools\chocolateyInstall.ps1'
130
-
131
- if (Test-Path $nuspec) {
132
- Log "Updating Chocolatey nuspec to $chocoVersion ..."
133
- if (-not $DryRun) {
134
- # Update version in nuspec
135
- $xml = [xml](Get-Content $nuspec -Raw)
136
- $xml.package.metadata.version = $chocoVersion
137
- $xml.Save($nuspec)
138
-
139
- # Update URL in install script
140
- $installContent = Get-Content $chocoInstall -Raw
141
- if ($zipUrl) {
142
- $installContent = $installContent -replace "\`$url\s*=\s*'[^']+'", "`$url = '$zipUrl'"
143
- }
144
- Set-Content -Path $chocoInstall -Value $installContent
145
- }
146
-
147
- # Compute checksum if we have the zip
148
- if ($zipUrl -and -not $DryRun) {
149
- Log "Downloading zip for checksum ..."
150
- $tmpZip = Join-Path $env:TEMP "gsd-$tagName.zip"
151
- try {
152
- Invoke-WebRequest -Uri $zipUrl -OutFile $tmpZip -UseBasicParsing
153
- $sha = (Get-FileHash -Path $tmpZip -Algorithm SHA256).Hash.ToLower()
154
- $installContent = Get-Content $chocoInstall -Raw
155
- $installContent = $installContent -replace "\`$checksum\s*=\s*'[^']+'", "`$checksum = '$sha'"
156
- Set-Content -Path $chocoInstall -Value $installContent
157
- Remove-Item $tmpZip -Force
158
- Log "Checksum: $sha"
159
- } catch {
160
- Warn "Could not download zip for checksum - set PLACEHOLDER manually"
161
- }
162
- }
163
-
164
- $chocoAvailable = $null -ne (Get-Command choco -ErrorAction SilentlyContinue)
165
- if ($chocoAvailable) {
166
- Log "Packing + pushing Chocolatey package ..."
167
- if ($DryRun) {
168
- Log "(dry-run) would run: choco pack + choco push"
169
- } else {
170
- Push-Location $chocoDir
171
- choco pack
172
- if ($LASTEXITCODE -ne 0) {
173
- $chocoFailed = $true
174
- Warn "choco pack failed"
175
- } else {
176
- $nupkg = Get-ChildItem "*.nupkg" | Sort-Object LastWriteTime -Descending | Select-Object -First 1
177
- if ($nupkg) {
178
- choco push $nupkg.Name --source https://push.chocolatey.org/
179
- if ($LASTEXITCODE -ne 0) {
180
- $chocoFailed = $true
181
- Warn "choco push failed"
182
- }
183
- else { Log "Chocolatey push OK" }
184
- } else {
185
- $chocoFailed = $true
186
- Warn "No .nupkg created by choco pack"
187
- }
188
- }
189
- Pop-Location
190
- }
191
- } else {
192
- $chocoFailed = $true
193
- Warn "choco not found - nuspec updated but not pushed. Run choco pack and choco push manually."
194
- }
195
- } else {
196
- $chocoFailed = $true
197
- Warn "nuspec not found at $nuspec - skipping Chocolatey"
198
- }
199
- } else {
200
- Warn "Skipping Chocolatey"
201
- }
202
-
203
- # ── 5. Homebrew ─────────────────────────────────────────────────────
204
- if (-not $SkipBrew) {
205
- # Detect pre-release: anything with a hyphen
206
- if ($isPrerelease) {
207
- $formulaPath = Join-Path $repoRoot 'packaging\homebrew\its-magic-beta.rb'
208
- Log "Pre-release detected - using beta formula"
209
- } else {
210
- $formulaPath = Join-Path $repoRoot 'packaging\homebrew\its-magic.rb'
211
- }
212
-
213
- if (Test-Path $formulaPath) {
214
- $formulaName = Split-Path -Leaf $formulaPath
215
- Log "Updating Homebrew formula ($formulaName) to $newVersion ..."
216
- if (-not $DryRun) {
217
- $formula = Get-Content $formulaPath -Raw
218
-
219
- # Update version in URL
220
- if ($tarUrl) {
221
- $urlPattern = 'url ' + $DQ + 'https://github\.com/[^' + $DQ + ']+\.tar\.gz' + $DQ
222
- $urlReplace = 'url ' + $DQ + $tarUrl + $DQ
223
- $formula = $formula -replace $urlPattern, $urlReplace
224
- } else {
225
- $formula = $formula -replace 'vVERSION', $tagName
226
- }
227
-
228
- # Update explicit version line for beta formula
229
- if ($isPrerelease) {
230
- $verPattern = 'version ' + $DQ + '[^' + $DQ + ']*' + $DQ
231
- $verReplace = 'version ' + $DQ + $newVersion + $DQ
232
- $formula = $formula -replace $verPattern, $verReplace
233
- }
234
-
235
- # Compute tar.gz sha256 if possible
236
- if ($tarUrl) {
237
- $tmpTar = Join-Path $env:TEMP "gsd-$tagName.tar.gz"
238
- try {
239
- Invoke-WebRequest -Uri $tarUrl -OutFile $tmpTar -UseBasicParsing
240
- $sha = (Get-FileHash -Path $tmpTar -Algorithm SHA256).Hash.ToLower()
241
- $shaPattern = 'sha256 ' + $DQ + '[^' + $DQ + ']*' + $DQ
242
- $shaReplace = 'sha256 ' + $DQ + $sha + $DQ
243
- $formula = $formula -replace $shaPattern, $shaReplace
244
- Remove-Item $tmpTar -Force
245
- Log "Homebrew sha256: $sha"
246
- } catch {
247
- Warn "Could not download tar.gz - set sha256 manually in the formula"
248
- }
249
- }
250
-
251
- Set-Content -Path $formulaPath -Value $formula
252
- Log "Homebrew formula updated: $formulaName"
253
- if ($isPrerelease) {
254
- Log "Users install beta with: brew install USER/tap/its-magic-beta"
255
- } else {
256
- Log "Users install stable with: brew install USER/tap/its-magic"
257
- }
258
- } else {
259
- $formulaName = Split-Path -Leaf $formulaPath
260
- Log "(dry-run) would update $formulaName"
261
- }
262
- } else {
263
- Warn "Formula not found at $formulaPath - skipping Homebrew"
264
- }
265
-
266
- # Optional: publish formulas to Homebrew tap repository
267
- if (-not $SkipBrewPush) {
268
- if ($DryRun) {
269
- Log "(dry-run) would publish formulas to Homebrew tap"
270
- } else {
271
- $gitAvailable = $null -ne (Get-Command git -ErrorAction SilentlyContinue)
272
- $ghAvailableForTap = $null -ne (Get-Command gh -ErrorAction SilentlyContinue)
273
- if (-not $gitAvailable) {
274
- $brewTapFailed = $true
275
- Warn "git not found - cannot push Homebrew formulas to tap"
276
- } else {
277
- if (-not $BrewTapRepo) {
278
- $originUrl = git remote get-url origin 2>$null
279
- if ($originUrl -match 'github\.com[:/](.+?)/(.+?)(\.git)?$') {
280
- $owner = $Matches[1]
281
- $BrewTapRepo = "$owner/homebrew-tap"
282
- }
283
- }
284
-
285
- if (-not $BrewTapRepo) {
286
- $brewTapFailed = $true
287
- Warn "Could not infer Homebrew tap repo. Pass -BrewTapRepo owner/homebrew-tap"
288
- } else {
289
- if (-not $BrewTapDir) {
290
- $safeTap = ($BrewTapRepo -replace '[^a-zA-Z0-9_-]', '-')
291
- $BrewTapDir = Join-Path $env:TEMP "its-magic-$safeTap"
292
- }
293
-
294
- Log "Publishing formulas to tap $BrewTapRepo (branch: $BrewTapBranch) ..."
295
-
296
- # Use a child scope with relaxed error handling for git commands
297
- # that write to stderr even on success (e.g. empty repo warnings).
298
- $tapReady = $true
299
- $prevEAP = $ErrorActionPreference
300
- $ErrorActionPreference = 'Continue'
301
-
302
- # ── Step A: Ensure we have a local clone ──
303
- $tapGitDir = Join-Path $BrewTapDir ".git"
304
-
305
- # Clean up non-git directory leftovers
306
- if ((Test-Path $BrewTapDir) -and -not (Test-Path $tapGitDir)) {
307
- Warn "Tap directory exists but is not a git repo. Removing: $BrewTapDir"
308
- Remove-Item -Recurse -Force $BrewTapDir
309
- }
310
-
311
- if (Test-Path $tapGitDir) {
312
- # Already cloned - fetch latest
313
- git -C $BrewTapDir fetch origin 2>&1 | Out-Null
314
- } else {
315
- # Clone (try gh first, then git)
316
- $cloneOk = $false
317
- if ($ghAvailableForTap) {
318
- & gh repo clone $BrewTapRepo $BrewTapDir 2>&1 | Out-Null
319
- if ($LASTEXITCODE -eq 0) { $cloneOk = $true }
320
- }
321
- if (-not $cloneOk) {
322
- git clone "https://github.com/$BrewTapRepo.git" $BrewTapDir 2>&1 | Out-Null
323
- if ($LASTEXITCODE -eq 0) { $cloneOk = $true }
324
- }
325
- if (-not $cloneOk -and $CreateBrewTapIfMissing -and $ghAvailableForTap) {
326
- Warn "Tap repo not found. Creating $BrewTapRepo on GitHub ..."
327
- & gh repo create $BrewTapRepo --public --description "Homebrew tap for its-magic" 2>&1 | Out-Null
328
- if ($LASTEXITCODE -eq 0) {
329
- Log "Tap repo created: https://github.com/$BrewTapRepo"
330
- if (Test-Path $BrewTapDir) { Remove-Item -Recurse -Force $BrewTapDir }
331
- & gh repo clone $BrewTapRepo $BrewTapDir 2>&1 | Out-Null
332
- if ($LASTEXITCODE -eq 0) { $cloneOk = $true }
333
- }
334
- }
335
- if (-not $cloneOk) {
336
- $tapReady = $false; $brewTapFailed = $true
337
- Warn "Failed to clone/create tap repo $BrewTapRepo"
338
- }
339
- }
340
-
341
- # ── Step B: Ensure branch exists ──
342
- if ($tapReady) {
343
- # Check if repo has any commits at all
344
- $hasCommits = $false
345
- git -C $BrewTapDir log --oneline -1 2>&1 | Out-Null
346
- if ($LASTEXITCODE -eq 0) { $hasCommits = $true }
347
-
348
- if ($hasCommits) {
349
- git -C $BrewTapDir checkout $BrewTapBranch 2>&1 | Out-Null
350
- if ($LASTEXITCODE -ne 0) {
351
- git -C $BrewTapDir checkout -b $BrewTapBranch 2>&1 | Out-Null
352
- }
353
- # Try to pull if remote branch exists
354
- git -C $BrewTapDir pull origin $BrewTapBranch 2>&1 | Out-Null
355
- } else {
356
- Log "Empty tap repo detected - creating initial branch $BrewTapBranch"
357
- git -C $BrewTapDir checkout --orphan $BrewTapBranch 2>&1 | Out-Null
358
- }
359
- }
360
-
361
- # ── Step C: Copy formulas, commit, push ──
362
- if ($tapReady) {
363
- $tapFormulaDir = Join-Path $BrewTapDir "Formula"
364
- if (-not (Test-Path $tapFormulaDir)) {
365
- New-Item -ItemType Directory -Path $tapFormulaDir -Force | Out-Null
366
- }
367
-
368
- Copy-Item (Join-Path $repoRoot "packaging\homebrew\its-magic.rb") (Join-Path $tapFormulaDir "its-magic.rb") -Force
369
- Copy-Item (Join-Path $repoRoot "packaging\homebrew\its-magic-beta.rb") (Join-Path $tapFormulaDir "its-magic-beta.rb") -Force
370
-
371
- git -C $BrewTapDir add Formula/its-magic.rb Formula/its-magic-beta.rb
372
-
373
- $tapChanges = git -C $BrewTapDir status --porcelain
374
- if ($tapChanges) {
375
- git -C $BrewTapDir commit -m "chore: update its-magic formulas for $newVersion" 2>&1 | Out-Null
376
- if ($LASTEXITCODE -ne 0) {
377
- $brewTapFailed = $true
378
- Warn "Failed to commit Homebrew tap changes"
379
- } else {
380
- git -C $BrewTapDir push -u origin $BrewTapBranch 2>&1 | Out-Null
381
- if ($LASTEXITCODE -ne 0) {
382
- $brewTapFailed = $true
383
- Warn "Failed to push Homebrew tap changes"
384
- } else {
385
- $brewTapPushed = $true
386
- Log "Homebrew tap updated: https://github.com/$BrewTapRepo"
387
- }
388
- }
389
- } else {
390
- Log "No Homebrew formula changes to push"
391
- }
392
- }
393
-
394
- $ErrorActionPreference = $prevEAP
395
- }
396
- }
397
- }
398
- } else {
399
- Warn "Skipping Homebrew tap push"
400
- }
401
- } else {
402
- Warn "Skipping Homebrew"
403
- }
404
-
405
- # ── Done ─────────────────────────────────────────────────────────────
406
- Pop-Location
407
- $npmStatus = "OK"
408
- if ($SkipNpm) { $npmStatus = "SKIPPED" }
409
- $chocoStatus = "OK"
410
- if ($SkipChoco) { $chocoStatus = "SKIPPED" }
411
- elseif ($DryRun) { $chocoStatus = "DRY-RUN" }
412
- elseif ($chocoFailed) { $chocoStatus = "FAILED - check log" }
413
- $brewStatus = "OK - formula updated"
414
- if ($SkipBrew) { $brewStatus = "SKIPPED" }
415
- elseif ($brewTapFailed) { $brewStatus = "FORMULA OK - TAP PUSH FAILED" }
416
- elseif ($brewTapPushed) { $brewStatus = "OK - formula updated + tap pushed" }
417
-
418
- Log "=========================================="
419
- Log "Release $newVersion complete!"
420
- Log " npm: $npmStatus"
421
- Log " Chocolatey: $chocoStatus"
422
- Log " Homebrew: $brewStatus"
423
- Log "=========================================="
@@ -1,4 +0,0 @@
1
- # Progress
2
-
3
- - ...
4
-
@@ -1,113 +0,0 @@
1
- # QA Findings — Webview UI → Backend Integration & Invalid Input Handling
2
-
3
- **Sprint:** S0001
4
- **Date:** 2026-02-06
5
- **Scope:** Verify the webview UI calls the backend correctly and handles invalid input.
6
- **Method:** Static code review of `examples/webview-app/` (frontend + backend + shared schema).
7
-
8
- ---
9
-
10
- ## Test Plan
11
-
12
- ### Area 1 — UI → Backend API Calls
13
-
14
- | # | Test Case | Frontend Entry Point | API Endpoint | Result |
15
- |---|-----------|---------------------|-------------|--------|
16
- | 1 | Initial page load fetches items | `loadItems()` on bootstrap (line 338) | `GET /api/v1/items` | PASS |
17
- | 2 | Filter change reloads items | `change` listeners → `loadItems()` (lines 301-304) | `GET /api/v1/items?status=…&category=…` | PASS |
18
- | 3 | Search input debounced reload | `input` → 300ms debounce → `loadItems()` (lines 305-308) | `GET /api/v1/items?search=…` | PASS |
19
- | 4 | Pagination prev/next | Button clicks update `currentMeta.page` → `loadItems()` (lines 315-320) | `GET /api/v1/items?page=N` | PASS |
20
- | 5 | Click item opens detail | `openDetail()` → `fetchItem(id)` (lines 215-230) | `GET /api/v1/items/:id` | PASS |
21
- | 6 | Delete item calls API | `handleDelete()` → `deleteItem(id)` (lines 263-275) | `DELETE /api/v1/items/:id` | PASS |
22
- | 7 | Clear/reset filters resets & reloads | `clearFilters()` → `loadItems()` (lines 252-259) | `GET /api/v1/items` (defaults) | PASS |
23
- | 8 | Keyboard nav (arrows) opens detail | `navigateItem()` → `openDetail()` (lines 329-334) | `GET /api/v1/items/:id` | PASS |
24
- | 9 | Request headers set Content-Type JSON | `apiFetch()` sets `Content-Type: application/json` (line 52) | All endpoints | PASS |
25
- | 10 | 204 response handled (delete) | `apiFetch()` returns null on 204 (line 56) | `DELETE /api/v1/items/:id` | PASS |
26
-
27
- ### Area 2 — Invalid Input Handling
28
-
29
- | # | Test Case | Frontend Behaviour | Backend Behaviour | Result |
30
- |---|-----------|--------------------|--------------------|--------|
31
- | 11 | API returns 400 validation error | `apiFetch()` throws structured Error with code/details (lines 60-64) | 400 + `VALIDATION_ERROR` envelope | PASS |
32
- | 12 | List load failure shows error in UI | `loadItems()` catch renders `.error-message` div (lines 208-211) | N/A | PASS |
33
- | 13 | Detail load failure feedback | `openDetail()` catch logs to console only (lines 227-229) | 404 `NOT_FOUND` | **FAIL** |
34
- | 14 | Delete failure shows alert | `handleDelete()` catch calls `alert()` (lines 272-274) | 404/400 | PASS |
35
- | 15 | Search maxlength enforced client-side | `<input maxlength="200">` (index.html line 50) | `validateQuery` checks ≤200 (validators.js line 149) | PASS |
36
- | 16 | XSS prevention | `escapeHtml()` used for title/category rendering (lines 143, 147) | N/A | PASS |
37
- | 17 | Invalid status in query params | Backend rejects with 400 | `validateQuery` checks enum (validators.js lines 137-146) | PASS |
38
- | 18 | Invalid sort/order params | Backend rejects with 400 | `validateQuery` checks enum (validators.js lines 167-171) | PASS |
39
- | 19 | Invalid page/pageSize params | Backend rejects with 400 | `validateQuery` checks integer + range (validators.js lines 159-164) | PASS |
40
- | 20 | Non-JSON backend response | `res.json()` throws SyntaxError — unhandled gracefully | N/A | **FAIL** |
41
- | 21 | Category filter not validated by backend | Unrecognized category returns empty list (no error) | No category enum validation in `validateQuery` | **WARN** |
42
- | 22 | POST/PATCH body validation (title required) | N/A (no create/edit UI) | `validateBody` enforces required + length (validators.js lines 82-124) | PASS |
43
-
44
- ---
45
-
46
- ## Findings
47
-
48
- ### F-001 — Silent failure on item detail fetch (Medium)
49
-
50
- **File:** `examples/webview-app/frontend/app.js` lines 224-229
51
- **Severity:** Medium
52
- **Category:** UX / Error handling
53
-
54
- `openDetail()` catches fetch errors but only logs to `console.error`. The user receives no visible feedback when a detail fetch fails (e.g., item deleted by another user, network error). Compare with `loadItems()` which renders an inline error message, and `handleDelete()` which shows an `alert()`.
55
-
56
- **Expected:** Display a user-visible error in the detail panel or a toast notification.
57
- **Recommendation:** Render an error state inside `$detailPanel` or show an inline banner, consistent with the error handling in `loadItems()`.
58
-
59
- ---
60
-
61
- ### F-002 — Non-JSON error responses crash `apiFetch` (Medium)
62
-
63
- **File:** `examples/webview-app/frontend/app.js` lines 49-68
64
- **Severity:** Medium
65
- **Category:** Robustness
66
-
67
- `apiFetch()` unconditionally calls `res.json()` for all non-204 responses (line 58). If the backend returns a non-JSON response (e.g., an HTML proxy error page, a 502 from a reverse proxy, or a plain-text 500), `res.json()` will throw a `SyntaxError` instead of the structured error the callers expect.
68
-
69
- **Expected:** Graceful handling of non-JSON responses with a fallback error message.
70
- **Recommendation:** Wrap `res.json()` in a try/catch or check the `Content-Type` header before parsing.
71
-
72
- ---
73
-
74
- ### F-003 — No loading/spinner state during API calls (Low)
75
-
76
- **File:** `examples/webview-app/frontend/app.js`
77
- **Severity:** Low
78
- **Category:** UX
79
-
80
- No loading indicator is shown during any API call (`loadItems`, `openDetail`, `handleDelete`). On slow connections or when the backend is unresponsive, the UI appears frozen with no feedback.
81
-
82
- **Expected:** A spinner or skeleton state while data is loading.
83
-
84
- ---
85
-
86
- ### F-004 — Category filter values not validated on backend (Low)
87
-
88
- **File:** `examples/webview-app/backend/validators.js` lines 132-185
89
- **Severity:** Low
90
- **Category:** Validation gap
91
-
92
- `validateQuery` validates `status` against the schema enum but does not validate the `category` query parameter against any allow-list. An arbitrary category value like `?category=XSS_ATTEMPT` would simply produce an empty result set (no crash), so this is low-severity. However, it is inconsistent with the `status` validation approach.
93
-
94
- ---
95
-
96
- ### F-005 — Soft-delete confirmation implies restorability but no restore UI exists (Low)
97
-
98
- **File:** `examples/webview-app/frontend/app.js` line 265
99
- **Severity:** Low
100
- **Category:** UX
101
-
102
- The delete confirmation says _"This action is reversible (soft delete)"_, which is technically true at the data layer. However, no UI exists to undelete items, making this effectively irreversible from the user's perspective.
103
-
104
- ---
105
-
106
- ## Verdict
107
-
108
- | Severity | Count |
109
- |----------|-------|
110
- | Medium | 2 (F-001, F-002) |
111
- | Low | 3 (F-003, F-004, F-005) |
112
-
113
- **Overall:** The UI correctly calls all documented backend endpoints. Filter, search, pagination, detail, and delete flows all route to the correct API paths with proper parameters. Backend validation is solid with structured error envelopes. Two medium-severity gaps exist in frontend error handling that should be addressed before release: silent detail fetch failures (F-001) and non-JSON response handling (F-002). No critical/blocking defects found.
@@ -1,70 +0,0 @@
1
- # Sprint S0001
2
-
3
- ## Goal
4
-
5
- Deliver a working vertical slice: filtered item list with detail panel, powered by a shared-schema REST API. Users can filter items by status/category/date, view details in a slide-over panel, and create/update items — all validated end-to-end with shared Zod schemas.
6
-
7
- ## Scope
8
-
9
- - **US-0002** — Filter Bar with Instant Results
10
- - **US-0003** — Slide-Over Detail Panel
11
- - **US-0004** — Empty-State & Filter Awareness
12
-
13
- ## User Stories → Task Mapping
14
-
15
- | Story | Tasks |
16
- |---------|--------------------------------|
17
- | US-0002 | T-015, T-016, T-017, T-018, T-019 |
18
- | US-0003 | T-010, T-016, T-020 |
19
- | US-0004 | T-021 |
20
- | Infra | T-001, T-002, T-003, T-007, T-008, T-022 |
21
-
22
- ## Build Order (dependency chain)
23
-
24
- ```
25
- T-001 Scaffold workspace
26
- └─ T-002 Shared Zod schemas + types
27
- ├─ T-003 tsconfig path aliases
28
- ├─ T-004 Backend validation middleware
29
- └─ T-005 Frontend form validation setup
30
-
31
- T-007 Express server + health check ─┐
32
- T-008 Drizzle schema + SQLite setup ──┤
33
- T-014 Error envelope ────────────────┘
34
-
35
- T-009 GET /items (filtered + paginated)
36
- T-010 GET /items/:id
37
- T-011 POST /items
38
- T-012 PATCH /items/:id
39
- T-013 DELETE /items/:id (soft)
40
-
41
- T-015 React + Vite + Tailwind shell ─┐
42
- T-022 Vite dev proxy ────────────────┘
43
-
44
- T-016 Typed API client + TanStack Query hooks
45
- T-006 Response validation on API client
46
-
47
- T-017 FilterBar component
48
- T-018 useFilters hook (URL sync)
49
- T-019 ItemList component
50
- T-020 DetailPanel component
51
- T-021 EmptyState component
52
- ```
53
-
54
- ## Risks
55
-
56
- | Risk | Likelihood | Impact | Mitigation |
57
- |------|-----------|--------|------------|
58
- | Shared schema drift if `/shared` is not built first | Medium | High | npm workspaces build order: shared → backend → frontend |
59
- | CORS misconfiguration between SPA and API | Medium | Low | Vite proxy for dev; explicit CORS middleware for prod |
60
- | SQLite → PostgreSQL dialect differences | Low | Medium | Drizzle ORM abstracts dialect; test against both in CI |
61
- | Filter query params exceed URL length | Low | Low | Start with 4 dimensions; add POST filter fallback if needed |
62
-
63
- ## Definition of Done
64
-
65
- - All 22 tasks complete and checked off in `tasks.md`
66
- - Shared schemas consumed by both frontend and backend without duplication
67
- - API returns structured error envelope for validation failures
68
- - Filter bar updates list instantly; active filters reflected in URL
69
- - Detail panel opens/closes with keyboard navigation (arrows, Escape)
70
- - Empty state renders with "Reset filters" action when no results match
@@ -1,46 +0,0 @@
1
- # Sprint Summary — S0001
2
-
3
- ## Goal
4
-
5
- Deliver a working vertical slice: filtered item list with detail panel, powered by a shared-schema REST API.
6
-
7
- ## Completed
8
-
9
- - GSD Workflow Kit: all slash commands, agent roles, rules, templates, and CI/CD workflows
10
- - Installers (PowerShell, Bash, Python) for kit distribution
11
- - Benchmark harness (scenario, live, headless, prompted modes)
12
- - Reference app (`examples/webview-app/`):
13
- - Express backend with full CRUD API (`/api/v1/items`)
14
- - Shared JSON Schema for cross-layer validation
15
- - Structured error envelopes (VALIDATION_ERROR, NOT_FOUND, INTERNAL_ERROR)
16
- - Frontend SPA with filter bar, sortable list, pagination
17
- - Slide-over detail panel with keyboard navigation
18
- - Empty state with filter awareness and reset action
19
- - XSS prevention, soft-delete, seed data (20 items)
20
- - Architecture decisions documented (DEC-0001, DEC-0002)
21
- - QA review completed — 22 test cases, 20 PASS, 2 FAIL, 1 WARN
22
-
23
- ## User Stories Delivered
24
-
25
- | Story | Title | Status |
26
- |---------|--------------------------------|-----------|
27
- | US-0002 | Filter Bar with Instant Results | Delivered |
28
- | US-0003 | Slide-Over Detail Panel | Delivered |
29
- | US-0004 | Empty-State & Filter Awareness | Delivered |
30
-
31
- ## Open Items
32
-
33
- - **F-001** (Medium): Silent failure on item detail fetch — needs visible user feedback
34
- - **F-002** (Medium): Non-JSON error responses crash `apiFetch` — needs try/catch or Content-Type check
35
- - **F-003** (Low): No loading/spinner state during API calls
36
- - **F-004** (Low): Category filter values not validated on backend
37
- - **F-005** (Low): Soft-delete confirmation implies restorability but no restore UI exists
38
-
39
- ## Metrics
40
-
41
- - QA test cases: 22
42
- - Pass: 20 | Fail: 2 | Warn: 1
43
- - Critical/blocking defects: 0
44
- - Medium findings: 2
45
- - Low findings: 3
46
-