savepoint 1.0.2 → 1.0.4

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 (242) hide show
  1. package/.claude/settings.local.json +12 -1
  2. package/.github/workflows/ci.yml +20 -0
  3. package/.golangci.yml +11 -0
  4. package/.savepoint/Design.md +40 -38
  5. package/.savepoint/{audit/v1.1/E02-cross-platform-compatibility/proposals.md → releases/v1.1/epics/E02-cross-platform-compatibility/E02-Audit.md} +48 -38
  6. package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/E03-Audit.md +195 -0
  7. package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/E03-Detail.md +14 -1
  8. package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/tasks/T006-forced-256-color-profile.md +3 -3
  9. package/.savepoint/{audit/v1.1/E04-epic-navigation/proposals.md → releases/v1.1/epics/E04-epic-navigation/E04-Audit.md} +65 -54
  10. package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/E05-Audit.md +237 -0
  11. package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/E05-Detail.md +25 -16
  12. package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T001-update-agents-md.md +17 -6
  13. package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T002-update-router-md.md +15 -5
  14. package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T003-update-design-md.md +19 -5
  15. package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T004-implement-m-hotkey.md +11 -1
  16. package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T005-update-help-overlay.md +9 -6
  17. package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T006-tests-and-quality-gates.md +29 -13
  18. package/.savepoint/releases/v1.1/epics/E06-audit-command/E06-Audit.md +56 -0
  19. package/.savepoint/releases/v1.1/epics/E06-audit-command/E06-Detail.md +63 -0
  20. package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T005-proposals.md +44 -0
  21. package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T007-apply-close.md +35 -0
  22. package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T009-integration.md +40 -0
  23. package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T010-audit-file-migration.md +45 -0
  24. package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T011-model-tab-state.md +26 -0
  25. package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T012-epic-audit-render.md +33 -0
  26. package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T013-handle-tab-keys.md +34 -0
  27. package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T014-tab-indicator.md +33 -0
  28. package/.savepoint/releases/v1.1/epics/E07-init-command/E07-Audit.md +336 -0
  29. package/.savepoint/releases/v1.1/epics/E07-init-command/E07-Detail.md +61 -0
  30. package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T001-cli-entrypoint.md +37 -0
  31. package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T002-target-validation.md +28 -0
  32. package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T003-scaffold-writer.md +46 -0
  33. package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T004-atomic-writes.md +27 -0
  34. package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T005-magic-prompt.md +25 -0
  35. package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T006-clipboard.md +26 -0
  36. package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T007-integration-test.md +26 -0
  37. package/.savepoint/releases/v1.1/epics/E08-board-command/E08-Audit.md +333 -0
  38. package/.savepoint/releases/v1.1/epics/E08-board-command/E08-Detail.md +68 -0
  39. package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T001-cli-entrypoint.md +26 -0
  40. package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T002-non-tty-fallback.md +27 -0
  41. package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T003-tui-app-shell.md +28 -0
  42. package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T004-board-model.md +29 -0
  43. package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T005-detail-pane.md +27 -0
  44. package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T006-status-transitions.md +29 -0
  45. package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T007-theme-fallbacks.md +29 -0
  46. package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T008-integration-test.md +27 -0
  47. package/.savepoint/releases/v1.1/epics/E09-doctor-command/E09-Audit.md +207 -0
  48. package/.savepoint/releases/v1.1/epics/E09-doctor-command/E09-Detail.md +65 -0
  49. package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T001-cli-entrypoint.md +24 -0
  50. package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T002-config-router-validation.md +28 -0
  51. package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T003-structure-checks.md +29 -0
  52. package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T004-dependency-checks.md +27 -0
  53. package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T005-audit-orphan-checks.md +28 -0
  54. package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T006-quality-gates-report.md +31 -0
  55. package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/E11-Detail.md +36 -0
  56. package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/tasks/T001-debug-logging.md +25 -0
  57. package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/tasks/T002-increase-debounce.md +21 -0
  58. package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/tasks/T003-error-handling.md +22 -0
  59. package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/tasks/T004-test-verify.md +29 -0
  60. package/.savepoint/releases/v1.1/epics/E12-validation-fix/E12-Audit.md +444 -0
  61. package/.savepoint/releases/v1.1/epics/E12-validation-fix/E12-Detail.md +45 -0
  62. package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T001-default-phase.md +35 -0
  63. package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T002-default-status.md +19 -0
  64. package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T003-better-errors.md +29 -0
  65. package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T004-validate-on-write.md +25 -0
  66. package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T005-tests.md +37 -0
  67. package/.savepoint/releases/v1.1/epics/E13-audit-remediation/E13-Audit.md +118 -0
  68. package/.savepoint/releases/v1.1/epics/E13-audit-remediation/E13-Detail.md +73 -0
  69. package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T001-safe-cleanup.md +66 -0
  70. package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T002-bug-fixes.md +35 -0
  71. package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T003-centralize-duplication.md +60 -0
  72. package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T004-infrastructure.md +33 -0
  73. package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T005-decompose-update.md +37 -0
  74. package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T006-async-io.md +40 -0
  75. package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T007-test-coverage.md +37 -0
  76. package/.savepoint/releases/v1.1/epics/E14-structural-improvements/E14-Audit.md +267 -0
  77. package/.savepoint/releases/v1.1/epics/E14-structural-improvements/E14-Detail.md +54 -0
  78. package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T001-group-model.md +39 -0
  79. package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T002-data-interfaces.md +42 -0
  80. package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T003-discover-orphans.md +33 -0
  81. package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T004-epic-panel-headings.md +35 -0
  82. package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T005-shell-tokenization.md +27 -0
  83. package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T006-unify-enums.md +29 -0
  84. package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T007-testutil-package.md +28 -0
  85. package/.savepoint/releases/v1.1/epics/E15-hardening/E15-Audit.md +272 -0
  86. package/.savepoint/releases/v1.1/epics/E15-hardening/E15-Detail.md +60 -0
  87. package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T001-benchmarks.md +31 -0
  88. package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T002-fuzz-targets.md +34 -0
  89. package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T003-debug-flag.md +30 -0
  90. package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T004-dist-checksums.md +27 -0
  91. package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T005-windows-targets.md +28 -0
  92. package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T006-abbreviation-splitting.md +26 -0
  93. package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T007-root-test-allowlist.md +33 -0
  94. package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T008-ci-and-release-automation.md +46 -0
  95. package/.savepoint/releases/v1.1/epics/_archived/T001-cli-entrypoint.md +25 -0
  96. package/.savepoint/releases/v1.1/epics/_archived/T002-quality-gates.md +27 -0
  97. package/.savepoint/releases/v1.1/epics/_archived/T003-snapshot.md +27 -0
  98. package/.savepoint/releases/v1.1/epics/_archived/T004-ai-reconcile.md +29 -0
  99. package/.savepoint/releases/v1.1/epics/_archived/T006-tui-review.md +31 -0
  100. package/.savepoint/releases/v1.1/epics/_archived/T008-skip-handling.md +34 -0
  101. package/.savepoint/releases/v1.1/v1.1-PRD.md +67 -7
  102. package/.savepoint/router.md +10 -17
  103. package/AGENTS.md +39 -24
  104. package/Makefile +3 -1
  105. package/README.md +0 -1
  106. package/agent-skills/savepoint-audit/SKILL.md +86 -34
  107. package/agent-skills/savepoint-build-task/SKILL.md +7 -2
  108. package/agent-skills/savepoint-create-plan/SKILL.md +7 -2
  109. package/agent-skills/savepoint-create-task/SKILL.md +44 -31
  110. package/agent-skills/savepoint-draft-prd/SKILL.md +7 -2
  111. package/agent-skills/savepoint-system-design/SKILL.md +7 -2
  112. package/agent_skills_test.go +91 -0
  113. package/cmd/board.go +59 -0
  114. package/cmd/board_test.go +137 -0
  115. package/cmd/doctor.go +53 -0
  116. package/cmd/doctor_test.go +146 -0
  117. package/cmd/init.go +63 -0
  118. package/cmd/init_test.go +104 -0
  119. package/internal/board/board.go +44 -36
  120. package/internal/board/board_test.go +27 -82
  121. package/internal/board/card.go +43 -23
  122. package/internal/board/card_test.go +74 -5
  123. package/internal/board/column.go +75 -15
  124. package/internal/board/column_test.go +76 -2
  125. package/internal/board/debug.go +26 -0
  126. package/internal/board/debug_test.go +108 -0
  127. package/internal/board/detail.go +33 -47
  128. package/internal/board/detail_test.go +48 -0
  129. package/internal/board/epic_panel.go +120 -22
  130. package/internal/board/epic_panel_test.go +302 -17
  131. package/internal/board/help.go +1 -0
  132. package/internal/board/help_test.go +1 -0
  133. package/internal/board/integration_test.go +266 -0
  134. package/internal/board/interfaces.go +65 -0
  135. package/internal/board/interfaces_test.go +114 -0
  136. package/internal/board/io.go +93 -0
  137. package/internal/board/model.go +79 -118
  138. package/internal/board/plain.go +88 -0
  139. package/internal/board/plain_test.go +117 -0
  140. package/internal/board/release.go +1 -9
  141. package/internal/board/release_test.go +6 -6
  142. package/internal/board/status.go +4 -4
  143. package/internal/board/theme.go +24 -0
  144. package/internal/board/theme_test.go +31 -0
  145. package/internal/board/transitions.go +113 -88
  146. package/internal/board/transitions_test.go +164 -141
  147. package/internal/board/tui.go +32 -0
  148. package/internal/board/update.go +344 -215
  149. package/internal/board/update_test.go +326 -18
  150. package/internal/board/util.go +76 -0
  151. package/internal/board/view.go +31 -28
  152. package/internal/board/view_test.go +74 -2
  153. package/internal/board/watch.go +41 -5
  154. package/internal/buildtool/main.go +45 -15
  155. package/internal/buildtool/main_test.go +224 -0
  156. package/internal/data/config.go +17 -3
  157. package/internal/data/config_test.go +49 -0
  158. package/internal/data/discover.go +26 -0
  159. package/internal/data/discover_test.go +34 -10
  160. package/internal/data/errors.go +4 -0
  161. package/internal/data/fuzz_test.go +75 -0
  162. package/internal/data/lifecycle.go +13 -6
  163. package/internal/data/lifecycle_test.go +14 -11
  164. package/internal/data/parser.go +22 -6
  165. package/internal/data/parser_test.go +31 -7
  166. package/internal/data/task.go +0 -9
  167. package/internal/data/testdata/fuzz/FuzzSplitFrontmatterBody/68eb66b0fe91e7e3 +2 -0
  168. package/internal/data/write.go +88 -11
  169. package/internal/data/write_test.go +167 -0
  170. package/internal/doctor/checks.go +567 -0
  171. package/internal/doctor/checks_test.go +716 -0
  172. package/internal/doctor/gates.go +193 -0
  173. package/internal/doctor/gates_test.go +166 -0
  174. package/internal/doctor/interfaces.go +64 -0
  175. package/internal/doctor/interfaces_test.go +104 -0
  176. package/internal/doctor/repairs.go +80 -0
  177. package/internal/doctor/repairs_test.go +81 -0
  178. package/internal/doctor/report.go +157 -0
  179. package/internal/doctor/report_test.go +89 -0
  180. package/internal/init/clipboard.go +146 -0
  181. package/internal/init/clipboard_test.go +74 -0
  182. package/internal/init/install.go +16 -0
  183. package/internal/init/integration_test.go +197 -0
  184. package/internal/init/prompt.go +14 -0
  185. package/internal/init/prompt_test.go +77 -0
  186. package/internal/init/scaffold.go +59 -0
  187. package/internal/init/scaffold_test.go +179 -0
  188. package/internal/init/template_freshness_test.go +56 -0
  189. package/internal/init/validate.go +85 -0
  190. package/internal/init/validate_test.go +141 -0
  191. package/internal/init/write.go +73 -0
  192. package/internal/init/write_test.go +91 -0
  193. package/internal/styles/styles_test.go +133 -0
  194. package/internal/testutil/fixture.go +113 -0
  195. package/internal/testutil/fs.go +26 -0
  196. package/main.go +120 -4
  197. package/package.json +2 -2
  198. package/project-audit/audit_report_glm_5.1.md +411 -0
  199. package/project-audit/audit_report_opus_4.6.md +406 -0
  200. package/project-audit/consolidated-audit-report.md +456 -0
  201. package/templates/project/.savepoint/Design.md +2 -2
  202. package/templates/project/.savepoint/router.md +10 -10
  203. package/templates/project/AGENTS.md +33 -21
  204. package/templates/project/agent-skills/savepoint-audit/SKILL.md +87 -0
  205. package/templates/project/agent-skills/savepoint-build-task/SKILL.md +44 -0
  206. package/templates/project/agent-skills/savepoint-create-plan/SKILL.md +33 -0
  207. package/templates/project/agent-skills/savepoint-create-task/SKILL.md +44 -0
  208. package/templates/project/agent-skills/savepoint-draft-prd/SKILL.md +37 -0
  209. package/templates/project/agent-skills/savepoint-system-design/SKILL.md +38 -0
  210. package/templates/prompts/audit-reconciliation.prompt.md +33 -28
  211. package/templates/prompts/design.prompt.md +3 -1
  212. package/.savepoint/audit/v1/E01/proposals.md +0 -168
  213. package/.savepoint/audit/v1/E01/snapshot.md +0 -78
  214. package/.savepoint/audit/v1/E01-go-setup/proposals.md +0 -166
  215. package/.savepoint/audit/v1/E01-go-setup/snapshot.md +0 -71
  216. package/.savepoint/audit/v1/E01-scaffolding/proposals/AGENTS.md +0 -66
  217. package/.savepoint/audit/v1/E01-scaffolding/proposals/Design.md +0 -210
  218. package/.savepoint/audit/v1/E01-scaffolding/proposals/epic-Design.md +0 -117
  219. package/.savepoint/audit/v1/E01-scaffolding/proposals/quality-review.md +0 -101
  220. package/.savepoint/audit/v1/E01-scaffolding/snapshot.md +0 -54
  221. package/.savepoint/audit/v1/E02-data-model/snapshot.md +0 -128
  222. package/.savepoint/audit/v1/E02-data-readers/proposals.md +0 -123
  223. package/.savepoint/audit/v1/E02-data-readers/snapshot.md +0 -54
  224. package/.savepoint/audit/v1/E03-board-tui-core/proposals.md +0 -146
  225. package/.savepoint/audit/v1/E03-board-tui-core/snapshot.md +0 -57
  226. package/.savepoint/audit/v1/E03-cli-foundation/snapshot.md +0 -106
  227. package/.savepoint/audit/v1/E04-board-components/proposals.md +0 -118
  228. package/.savepoint/audit/v1/E04-board-components/snapshot.md +0 -77
  229. package/.savepoint/audit/v1/E04-templates-and-prompts/snapshot.md +0 -115
  230. package/.savepoint/audit/v1/E05-init-command/snapshot.md +0 -125
  231. package/.savepoint/audit/v1/E05-phase-transitions/proposals.md +0 -83
  232. package/.savepoint/audit/v1/E05-phase-transitions/snapshot.md +0 -36
  233. package/.savepoint/audit/v1/E06-atari-noir-layout/proposals.md +0 -130
  234. package/.savepoint/audit/v1/E06-atari-noir-layout/snapshot.md +0 -84
  235. package/.savepoint/audit/v1/E06-tui-board/snapshot.md +0 -64
  236. package/.savepoint/audit/v1/E07-audit-pipeline/snapshot.md +0 -165
  237. package/.savepoint/audit/v1/E08-board-workflow-cleanup/snapshot.md +0 -65
  238. package/.savepoint/audit/v1.1/E02-cross-platform-compatibility/snapshot.md +0 -41
  239. package/.savepoint/audit/v1.1/E04-epic-navigation/snapshot.md +0 -48
  240. package/ink-cli-ui-design.zip +0 -0
  241. package/savepoint +0 -0
  242. package/savepoint.exe +0 -0
@@ -1,141 +1,164 @@
1
- package board
2
-
3
- import (
4
- "testing"
5
-
6
- "github.com/opencode/savepoint/internal/data"
7
- )
8
-
9
- func TestAdvance(t *testing.T) {
10
- tests := []struct {
11
- name string
12
- initialCol data.ColumnType
13
- initialSt data.ProgressStage
14
- expectCol data.ColumnType
15
- expectSt data.ProgressStage
16
- }{
17
- {"planned to in_progress/build", data.ColumnPlanned, "", data.ColumnInProgress, data.StageBuild},
18
- {"in_progress/build to test", data.ColumnInProgress, data.StageBuild, data.ColumnInProgress, data.StageTest},
19
- {"in_progress/test to audit", data.ColumnInProgress, data.StageTest, data.ColumnInProgress, data.StageAudit},
20
- {"in_progress/audit to done", data.ColumnInProgress, data.StageAudit, data.ColumnDone, ""},
21
- }
22
-
23
- for _, tt := range tests {
24
- t.Run(tt.name, func(t *testing.T) {
25
- task := data.Task{Column: tt.initialCol, Stage: tt.initialSt}
26
- Advance(&task)
27
- if task.Column != tt.expectCol || task.Stage != tt.expectSt {
28
- t.Errorf("Advance() = %v/%v, want %v/%v", task.Column, task.Stage, tt.expectCol, tt.expectSt)
29
- }
30
- })
31
- }
32
- }
33
-
34
- func TestRetreat(t *testing.T) {
35
- tests := []struct {
36
- name string
37
- initialCol data.ColumnType
38
- initialSt data.ProgressStage
39
- expectCol data.ColumnType
40
- expectSt data.ProgressStage
41
- }{
42
- {"done to in_progress/audit", data.ColumnDone, "", data.ColumnInProgress, data.StageAudit},
43
- {"in_progress/audit to test", data.ColumnInProgress, data.StageAudit, data.ColumnInProgress, data.StageTest},
44
- {"in_progress/test to build", data.ColumnInProgress, data.StageTest, data.ColumnInProgress, data.StageBuild},
45
- {"in_progress/build to planned", data.ColumnInProgress, data.StageBuild, data.ColumnPlanned, ""},
46
- }
47
-
48
- for _, tt := range tests {
49
- t.Run(tt.name, func(t *testing.T) {
50
- task := data.Task{Column: tt.initialCol, Stage: tt.initialSt}
51
- Retreat(&task)
52
- if task.Column != tt.expectCol || task.Stage != tt.expectSt {
53
- t.Errorf("Retreat() = %v/%v, want %v/%v", task.Column, task.Stage, tt.expectCol, tt.expectSt)
54
- }
55
- })
56
- }
57
- }
58
-
59
- func TestCanAdvance_plannedAlwaysAllowed(t *testing.T) {
60
- task := data.Task{ID: "T1", Column: data.ColumnPlanned}
61
- ok, reason := CanAdvance(&task, nil)
62
- if !ok {
63
- t.Errorf("CanAdvance(planned) = false %q, want true", reason)
64
- }
65
- }
66
-
67
- func TestCanAdvance_buildAlwaysAllowed(t *testing.T) {
68
- task := data.Task{ID: "T1", Column: data.ColumnInProgress, Stage: data.StageBuild}
69
- ok, reason := CanAdvance(&task, nil)
70
- if !ok {
71
- t.Errorf("CanAdvance(build) = false %q, want true", reason)
72
- }
73
- }
74
-
75
- func TestCanAdvance_testAlwaysAllowed(t *testing.T) {
76
- task := data.Task{ID: "T1", Column: data.ColumnInProgress, Stage: data.StageTest}
77
- ok, reason := CanAdvance(&task, nil)
78
- if !ok {
79
- t.Errorf("CanAdvance(test) = false %q, want true", reason)
80
- }
81
- }
82
-
83
- func TestCanAdvance_auditDoneBlockedByDependency(t *testing.T) {
84
- allTasks := []data.Task{
85
- {ID: "T1", Column: data.ColumnInProgress, Stage: data.StageAudit, DependsOn: []string{"T2"}},
86
- {ID: "T2", Column: data.ColumnInProgress, Stage: data.StageBuild},
87
- }
88
- ok, reason := CanAdvance(&allTasks[0], allTasks)
89
- if ok {
90
- t.Fatal("CanAdvance(audit with undep) = true, want false")
91
- }
92
- if reason == "" {
93
- t.Fatal("expected non-empty reason string")
94
- }
95
- }
96
-
97
- func TestCanAdvance_auditDoneAllowedWhenDepsDone(t *testing.T) {
98
- allTasks := []data.Task{
99
- {ID: "T1", Column: data.ColumnInProgress, Stage: data.StageAudit, DependsOn: []string{"T2"}},
100
- {ID: "T2", Column: data.ColumnDone},
101
- }
102
- ok, reason := CanAdvance(&allTasks[0], allTasks)
103
- if !ok {
104
- t.Errorf("CanAdvance(audit with dep done) = false %q, want true", reason)
105
- }
106
- }
107
-
108
- func TestCanAdvance_doneBlocked(t *testing.T) {
109
- task := data.Task{ID: "T1", Column: data.ColumnDone}
110
- ok, reason := CanAdvance(&task, nil)
111
- if ok {
112
- t.Fatal("CanAdvance(done) = true, want false")
113
- }
114
- if reason == "" {
115
- t.Fatal("expected non-empty reason string")
116
- }
117
- }
118
-
119
- func TestCanAdvance_unknownStageBlocked(t *testing.T) {
120
- task := data.Task{ID: "T1", Column: data.ColumnInProgress, Stage: "invalid"}
121
- ok, reason := CanAdvance(&task, nil)
122
- if ok {
123
- t.Fatal("CanAdvance(unknown stage) = true, want false")
124
- }
125
- if reason == "" {
126
- t.Fatal("expected non-empty reason string")
127
- }
128
- }
129
-
130
- func TestCanAdvance_auditDepsNotFoundBlocked(t *testing.T) {
131
- allTasks := []data.Task{
132
- {ID: "T1", Column: data.ColumnInProgress, Stage: data.StageAudit, DependsOn: []string{"T2"}},
133
- }
134
- ok, reason := CanAdvance(&allTasks[0], allTasks)
135
- if ok {
136
- t.Fatal("CanAdvance(audit missing dep) = true, want false")
137
- }
138
- if reason == "" {
139
- t.Fatal("expected non-empty reason string")
140
- }
141
- }
1
+ package board
2
+
3
+ import (
4
+ "testing"
5
+
6
+ "github.com/opencode/savepoint/internal/data"
7
+ )
8
+
9
+ func TestAdvance(t *testing.T) {
10
+ tests := []struct {
11
+ name string
12
+ initialCol data.ColumnType
13
+ initialSt data.ProgressStage
14
+ expectCol data.ColumnType
15
+ expectSt data.ProgressStage
16
+ }{
17
+ {"planned to in_progress/build", data.ColumnPlanned, "", data.ColumnInProgress, data.StageBuild},
18
+ {"in_progress/build to test", data.ColumnInProgress, data.StageBuild, data.ColumnInProgress, data.StageTest},
19
+ {"in_progress/test to audit", data.ColumnInProgress, data.StageTest, data.ColumnInProgress, data.StageAudit},
20
+ {"in_progress/audit to done", data.ColumnInProgress, data.StageAudit, data.ColumnDone, ""},
21
+ }
22
+
23
+ for _, tt := range tests {
24
+ t.Run(tt.name, func(t *testing.T) {
25
+ task := data.Task{Column: tt.initialCol, Stage: tt.initialSt}
26
+ Advance(&task)
27
+ if task.Column != tt.expectCol || task.Stage != tt.expectSt {
28
+ t.Errorf("Advance() = %v/%v, want %v/%v", task.Column, task.Stage, tt.expectCol, tt.expectSt)
29
+ }
30
+ if task.Status != string(tt.expectCol) {
31
+ t.Errorf("Advance() status = %q, want %q", task.Status, tt.expectCol)
32
+ }
33
+ })
34
+ }
35
+ }
36
+
37
+ func TestRetreat(t *testing.T) {
38
+ tests := []struct {
39
+ name string
40
+ initialCol data.ColumnType
41
+ initialSt data.ProgressStage
42
+ expectCol data.ColumnType
43
+ expectSt data.ProgressStage
44
+ }{
45
+ {"done to in_progress/audit", data.ColumnDone, "", data.ColumnInProgress, data.StageAudit},
46
+ {"in_progress/audit to test", data.ColumnInProgress, data.StageAudit, data.ColumnInProgress, data.StageTest},
47
+ {"in_progress/test to build", data.ColumnInProgress, data.StageTest, data.ColumnInProgress, data.StageBuild},
48
+ {"in_progress/build to planned", data.ColumnInProgress, data.StageBuild, data.ColumnPlanned, ""},
49
+ }
50
+
51
+ for _, tt := range tests {
52
+ t.Run(tt.name, func(t *testing.T) {
53
+ task := data.Task{Status: string(tt.initialCol), Column: tt.initialCol, Stage: tt.initialSt}
54
+ Retreat(&task)
55
+ if task.Column != tt.expectCol || task.Stage != tt.expectSt {
56
+ t.Errorf("Retreat() = %v/%v, want %v/%v", task.Column, task.Stage, tt.expectCol, tt.expectSt)
57
+ }
58
+ if task.Status != string(tt.expectCol) {
59
+ t.Errorf("Retreat() status = %q, want %q", task.Status, tt.expectCol)
60
+ }
61
+ })
62
+ }
63
+ }
64
+
65
+ func TestCanAdvance_plannedAllowedWhenDependenciesDone(t *testing.T) {
66
+ allTasks := []data.Task{
67
+ {ID: "T1", Column: data.ColumnPlanned, DependsOn: []string{"T2"}},
68
+ {ID: "T2", Column: data.ColumnDone},
69
+ }
70
+ ok, reason := CanAdvance(&allTasks[0], allTasks)
71
+ if !ok {
72
+ t.Errorf("CanAdvance(planned with done dep) = false %q, want true", reason)
73
+ }
74
+ }
75
+
76
+ func TestCanAdvance_plannedBlockedByDependency(t *testing.T) {
77
+ allTasks := []data.Task{
78
+ {ID: "T1", Column: data.ColumnPlanned, DependsOn: []string{"T2"}},
79
+ {ID: "T2", Column: data.ColumnInProgress},
80
+ }
81
+ ok, reason := CanAdvance(&allTasks[0], allTasks)
82
+ if ok {
83
+ t.Fatal("CanAdvance(planned with unfinished dep) = true, want false")
84
+ }
85
+ if reason != "dependency \"T2\" is not done" {
86
+ t.Errorf("reason = %q, want dependency warning", reason)
87
+ }
88
+ }
89
+
90
+ func TestCanAdvance_buildAlwaysAllowed(t *testing.T) {
91
+ task := data.Task{ID: "T1", Column: data.ColumnInProgress, Stage: data.StageBuild}
92
+ ok, reason := CanAdvance(&task, nil)
93
+ if !ok {
94
+ t.Errorf("CanAdvance(build) = false %q, want true", reason)
95
+ }
96
+ }
97
+
98
+ func TestCanAdvance_testAlwaysAllowed(t *testing.T) {
99
+ task := data.Task{ID: "T1", Column: data.ColumnInProgress, Stage: data.StageTest}
100
+ ok, reason := CanAdvance(&task, nil)
101
+ if !ok {
102
+ t.Errorf("CanAdvance(test) = false %q, want true", reason)
103
+ }
104
+ }
105
+
106
+ func TestCanAdvance_auditDoneBlockedByDependency(t *testing.T) {
107
+ allTasks := []data.Task{
108
+ {ID: "T1", Column: data.ColumnInProgress, Stage: data.StageAudit, DependsOn: []string{"T2"}},
109
+ {ID: "T2", Column: data.ColumnInProgress, Stage: data.StageBuild},
110
+ }
111
+ ok, reason := CanAdvance(&allTasks[0], allTasks)
112
+ if ok {
113
+ t.Fatal("CanAdvance(audit with undep) = true, want false")
114
+ }
115
+ if reason == "" {
116
+ t.Fatal("expected non-empty reason string")
117
+ }
118
+ }
119
+
120
+ func TestCanAdvance_auditDoneAllowedWhenDepsDone(t *testing.T) {
121
+ allTasks := []data.Task{
122
+ {ID: "T1", Column: data.ColumnInProgress, Stage: data.StageAudit, DependsOn: []string{"T2"}},
123
+ {ID: "T2", Column: data.ColumnDone},
124
+ }
125
+ ok, reason := CanAdvance(&allTasks[0], allTasks)
126
+ if !ok {
127
+ t.Errorf("CanAdvance(audit with dep done) = false %q, want true", reason)
128
+ }
129
+ }
130
+
131
+ func TestCanAdvance_doneBlocked(t *testing.T) {
132
+ task := data.Task{ID: "T1", Column: data.ColumnDone}
133
+ ok, reason := CanAdvance(&task, nil)
134
+ if ok {
135
+ t.Fatal("CanAdvance(done) = true, want false")
136
+ }
137
+ if reason == "" {
138
+ t.Fatal("expected non-empty reason string")
139
+ }
140
+ }
141
+
142
+ func TestCanAdvance_unknownStageBlocked(t *testing.T) {
143
+ task := data.Task{ID: "T1", Column: data.ColumnInProgress, Stage: "invalid"}
144
+ ok, reason := CanAdvance(&task, nil)
145
+ if ok {
146
+ t.Fatal("CanAdvance(unknown stage) = true, want false")
147
+ }
148
+ if reason == "" {
149
+ t.Fatal("expected non-empty reason string")
150
+ }
151
+ }
152
+
153
+ func TestCanAdvance_auditDepsNotFoundBlocked(t *testing.T) {
154
+ allTasks := []data.Task{
155
+ {ID: "T1", Column: data.ColumnInProgress, Stage: data.StageAudit, DependsOn: []string{"T2"}},
156
+ }
157
+ ok, reason := CanAdvance(&allTasks[0], allTasks)
158
+ if ok {
159
+ t.Fatal("CanAdvance(audit missing dep) = true, want false")
160
+ }
161
+ if reason == "" {
162
+ t.Fatal("expected non-empty reason string")
163
+ }
164
+ }
@@ -0,0 +1,32 @@
1
+ package board
2
+
3
+ import (
4
+ "fmt"
5
+ "os"
6
+ "path/filepath"
7
+
8
+ tea "github.com/charmbracelet/bubbletea"
9
+ )
10
+
11
+ // RunTUI launches the Bubble Tea board with optional release/epic filters.
12
+ func RunTUI(release, epic string) error {
13
+ model, err := newProjectModel(".", release, epic)
14
+ if err != nil {
15
+ return err
16
+ }
17
+
18
+ cfg, err := model.Dependencies.ConfigReader.Read(filepath.Join(model.Root, "config.yml"))
19
+ if err != nil {
20
+ return err
21
+ }
22
+ model.Theme = cfg.Theme
23
+
24
+ applyColorProfile()
25
+
26
+ p := tea.NewProgram(model, tea.WithAltScreen())
27
+ if _, err := p.Run(); err != nil {
28
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
29
+ return err
30
+ }
31
+ return nil
32
+ }