savepoint 1.0.2 → 1.0.3

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 (233) hide show
  1. package/.claude/settings.local.json +12 -1
  2. package/.golangci.yml +11 -0
  3. package/.savepoint/Design.md +37 -36
  4. package/.savepoint/{audit/v1.1/E02-cross-platform-compatibility/proposals.md → releases/v1.1/epics/E02-cross-platform-compatibility/E02-Audit.md} +48 -38
  5. package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/E03-Audit.md +195 -0
  6. package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/E03-Detail.md +14 -1
  7. package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/tasks/T006-forced-256-color-profile.md +3 -3
  8. package/.savepoint/{audit/v1.1/E04-epic-navigation/proposals.md → releases/v1.1/epics/E04-epic-navigation/E04-Audit.md} +65 -54
  9. package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/E05-Audit.md +237 -0
  10. package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/E05-Detail.md +25 -16
  11. package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T001-update-agents-md.md +17 -6
  12. package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T002-update-router-md.md +15 -5
  13. package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T003-update-design-md.md +19 -5
  14. package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T004-implement-m-hotkey.md +11 -1
  15. package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T005-update-help-overlay.md +9 -6
  16. package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T006-tests-and-quality-gates.md +29 -13
  17. package/.savepoint/releases/v1.1/epics/E06-audit-command/E06-Audit.md +56 -0
  18. package/.savepoint/releases/v1.1/epics/E06-audit-command/E06-Detail.md +63 -0
  19. package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T005-proposals.md +44 -0
  20. package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T007-apply-close.md +35 -0
  21. package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T009-integration.md +40 -0
  22. package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T010-audit-file-migration.md +45 -0
  23. package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T011-model-tab-state.md +26 -0
  24. package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T012-epic-audit-render.md +33 -0
  25. package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T013-handle-tab-keys.md +34 -0
  26. package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T014-tab-indicator.md +33 -0
  27. package/.savepoint/releases/v1.1/epics/E07-init-command/E07-Audit.md +336 -0
  28. package/.savepoint/releases/v1.1/epics/E07-init-command/E07-Detail.md +61 -0
  29. package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T001-cli-entrypoint.md +37 -0
  30. package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T002-target-validation.md +28 -0
  31. package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T003-scaffold-writer.md +46 -0
  32. package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T004-atomic-writes.md +27 -0
  33. package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T005-magic-prompt.md +25 -0
  34. package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T006-clipboard.md +26 -0
  35. package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T007-integration-test.md +26 -0
  36. package/.savepoint/releases/v1.1/epics/E08-board-command/E08-Audit.md +333 -0
  37. package/.savepoint/releases/v1.1/epics/E08-board-command/E08-Detail.md +68 -0
  38. package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T001-cli-entrypoint.md +26 -0
  39. package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T002-non-tty-fallback.md +27 -0
  40. package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T003-tui-app-shell.md +28 -0
  41. package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T004-board-model.md +29 -0
  42. package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T005-detail-pane.md +27 -0
  43. package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T006-status-transitions.md +29 -0
  44. package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T007-theme-fallbacks.md +29 -0
  45. package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T008-integration-test.md +27 -0
  46. package/.savepoint/releases/v1.1/epics/E09-doctor-command/E09-Audit.md +207 -0
  47. package/.savepoint/releases/v1.1/epics/E09-doctor-command/E09-Detail.md +65 -0
  48. package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T001-cli-entrypoint.md +24 -0
  49. package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T002-config-router-validation.md +28 -0
  50. package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T003-structure-checks.md +29 -0
  51. package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T004-dependency-checks.md +27 -0
  52. package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T005-audit-orphan-checks.md +28 -0
  53. package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T006-quality-gates-report.md +31 -0
  54. package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/E11-Detail.md +36 -0
  55. package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/tasks/T001-debug-logging.md +25 -0
  56. package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/tasks/T002-increase-debounce.md +21 -0
  57. package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/tasks/T003-error-handling.md +22 -0
  58. package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/tasks/T004-test-verify.md +29 -0
  59. package/.savepoint/releases/v1.1/epics/E12-validation-fix/E12-Audit.md +444 -0
  60. package/.savepoint/releases/v1.1/epics/E12-validation-fix/E12-Detail.md +45 -0
  61. package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T001-default-phase.md +35 -0
  62. package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T002-default-status.md +19 -0
  63. package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T003-better-errors.md +29 -0
  64. package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T004-validate-on-write.md +25 -0
  65. package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T005-tests.md +37 -0
  66. package/.savepoint/releases/v1.1/epics/E13-audit-remediation/E13-Audit.md +118 -0
  67. package/.savepoint/releases/v1.1/epics/E13-audit-remediation/E13-Detail.md +73 -0
  68. package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T001-safe-cleanup.md +66 -0
  69. package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T002-bug-fixes.md +35 -0
  70. package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T003-centralize-duplication.md +60 -0
  71. package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T004-infrastructure.md +33 -0
  72. package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T005-decompose-update.md +37 -0
  73. package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T006-async-io.md +40 -0
  74. package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T007-test-coverage.md +37 -0
  75. package/.savepoint/releases/v1.1/epics/E14-structural-improvements/E14-Audit.md +267 -0
  76. package/.savepoint/releases/v1.1/epics/E14-structural-improvements/E14-Detail.md +54 -0
  77. package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T001-group-model.md +39 -0
  78. package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T002-data-interfaces.md +42 -0
  79. package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T003-discover-orphans.md +33 -0
  80. package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T004-epic-panel-headings.md +35 -0
  81. package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T005-shell-tokenization.md +27 -0
  82. package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T006-unify-enums.md +29 -0
  83. package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T007-testutil-package.md +28 -0
  84. package/.savepoint/releases/v1.1/epics/E15-hardening/E15-Detail.md +43 -0
  85. package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T001-benchmarks.md +31 -0
  86. package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T002-fuzz-targets.md +28 -0
  87. package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T003-debug-flag.md +30 -0
  88. package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T004-dist-checksums.md +27 -0
  89. package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T005-windows-targets.md +28 -0
  90. package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T006-abbreviation-splitting.md +26 -0
  91. package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T007-root-test-allowlist.md +28 -0
  92. package/.savepoint/releases/v1.1/epics/_archived/T001-cli-entrypoint.md +25 -0
  93. package/.savepoint/releases/v1.1/epics/_archived/T002-quality-gates.md +27 -0
  94. package/.savepoint/releases/v1.1/epics/_archived/T003-snapshot.md +27 -0
  95. package/.savepoint/releases/v1.1/epics/_archived/T004-ai-reconcile.md +29 -0
  96. package/.savepoint/releases/v1.1/epics/_archived/T006-tui-review.md +31 -0
  97. package/.savepoint/releases/v1.1/epics/_archived/T008-skip-handling.md +34 -0
  98. package/.savepoint/releases/v1.1/v1.1-PRD.md +67 -7
  99. package/.savepoint/router.md +9 -16
  100. package/AGENTS.md +38 -23
  101. package/README.md +0 -1
  102. package/agent-skills/savepoint-audit/SKILL.md +86 -34
  103. package/agent-skills/savepoint-build-task/SKILL.md +7 -2
  104. package/agent-skills/savepoint-create-plan/SKILL.md +7 -2
  105. package/agent-skills/savepoint-create-task/SKILL.md +44 -31
  106. package/agent-skills/savepoint-draft-prd/SKILL.md +7 -2
  107. package/agent-skills/savepoint-system-design/SKILL.md +7 -2
  108. package/agent_skills_test.go +91 -0
  109. package/cmd/board.go +59 -0
  110. package/cmd/board_test.go +137 -0
  111. package/cmd/doctor.go +53 -0
  112. package/cmd/doctor_test.go +146 -0
  113. package/cmd/init.go +63 -0
  114. package/cmd/init_test.go +104 -0
  115. package/internal/board/board.go +40 -36
  116. package/internal/board/board_test.go +27 -82
  117. package/internal/board/card.go +43 -23
  118. package/internal/board/card_test.go +41 -5
  119. package/internal/board/column.go +44 -13
  120. package/internal/board/column_test.go +5 -2
  121. package/internal/board/detail.go +0 -47
  122. package/internal/board/epic_panel.go +118 -22
  123. package/internal/board/epic_panel_test.go +302 -17
  124. package/internal/board/help.go +1 -0
  125. package/internal/board/help_test.go +1 -0
  126. package/internal/board/integration_test.go +266 -0
  127. package/internal/board/interfaces.go +65 -0
  128. package/internal/board/interfaces_test.go +114 -0
  129. package/internal/board/io.go +93 -0
  130. package/internal/board/model.go +79 -118
  131. package/internal/board/plain.go +88 -0
  132. package/internal/board/plain_test.go +117 -0
  133. package/internal/board/release.go +1 -9
  134. package/internal/board/release_test.go +6 -6
  135. package/internal/board/status.go +4 -4
  136. package/internal/board/theme.go +24 -0
  137. package/internal/board/theme_test.go +31 -0
  138. package/internal/board/transitions.go +113 -88
  139. package/internal/board/transitions_test.go +164 -141
  140. package/internal/board/tui.go +32 -0
  141. package/internal/board/update.go +325 -215
  142. package/internal/board/update_test.go +299 -18
  143. package/internal/board/util.go +76 -0
  144. package/internal/board/view.go +31 -28
  145. package/internal/board/view_test.go +12 -2
  146. package/internal/board/watch.go +35 -5
  147. package/internal/buildtool/main.go +2 -10
  148. package/internal/buildtool/main_test.go +46 -0
  149. package/internal/data/config.go +17 -3
  150. package/internal/data/config_test.go +49 -0
  151. package/internal/data/discover.go +26 -0
  152. package/internal/data/discover_test.go +34 -10
  153. package/internal/data/errors.go +4 -0
  154. package/internal/data/lifecycle.go +13 -6
  155. package/internal/data/lifecycle_test.go +14 -11
  156. package/internal/data/parser.go +21 -6
  157. package/internal/data/parser_test.go +31 -7
  158. package/internal/data/task.go +0 -9
  159. package/internal/data/write.go +85 -11
  160. package/internal/data/write_test.go +167 -0
  161. package/internal/doctor/checks.go +567 -0
  162. package/internal/doctor/checks_test.go +716 -0
  163. package/internal/doctor/gates.go +193 -0
  164. package/internal/doctor/gates_test.go +166 -0
  165. package/internal/doctor/interfaces.go +64 -0
  166. package/internal/doctor/interfaces_test.go +104 -0
  167. package/internal/doctor/repairs.go +80 -0
  168. package/internal/doctor/repairs_test.go +81 -0
  169. package/internal/doctor/report.go +157 -0
  170. package/internal/doctor/report_test.go +89 -0
  171. package/internal/init/clipboard.go +146 -0
  172. package/internal/init/clipboard_test.go +74 -0
  173. package/internal/init/install.go +16 -0
  174. package/internal/init/integration_test.go +197 -0
  175. package/internal/init/prompt.go +14 -0
  176. package/internal/init/prompt_test.go +77 -0
  177. package/internal/init/scaffold.go +59 -0
  178. package/internal/init/scaffold_test.go +179 -0
  179. package/internal/init/template_freshness_test.go +56 -0
  180. package/internal/init/validate.go +85 -0
  181. package/internal/init/validate_test.go +141 -0
  182. package/internal/init/write.go +73 -0
  183. package/internal/init/write_test.go +91 -0
  184. package/internal/styles/styles_test.go +133 -0
  185. package/internal/testutil/fixture.go +113 -0
  186. package/internal/testutil/fs.go +26 -0
  187. package/main.go +101 -4
  188. package/package.json +2 -2
  189. package/project-audit/audit_report_glm_5.1.md +411 -0
  190. package/project-audit/audit_report_opus_4.6 +406 -0
  191. package/project-audit/consolidated-audit-report.md +456 -0
  192. package/savepoint +0 -0
  193. package/templates/project/.savepoint/Design.md +2 -2
  194. package/templates/project/.savepoint/router.md +10 -10
  195. package/templates/project/AGENTS.md +33 -21
  196. package/templates/project/agent-skills/savepoint-audit/SKILL.md +87 -0
  197. package/templates/project/agent-skills/savepoint-build-task/SKILL.md +44 -0
  198. package/templates/project/agent-skills/savepoint-create-plan/SKILL.md +33 -0
  199. package/templates/project/agent-skills/savepoint-create-task/SKILL.md +44 -0
  200. package/templates/project/agent-skills/savepoint-draft-prd/SKILL.md +37 -0
  201. package/templates/project/agent-skills/savepoint-system-design/SKILL.md +38 -0
  202. package/templates/prompts/audit-reconciliation.prompt.md +33 -28
  203. package/templates/prompts/design.prompt.md +3 -1
  204. package/.savepoint/audit/v1/E01/proposals.md +0 -168
  205. package/.savepoint/audit/v1/E01/snapshot.md +0 -78
  206. package/.savepoint/audit/v1/E01-go-setup/proposals.md +0 -166
  207. package/.savepoint/audit/v1/E01-go-setup/snapshot.md +0 -71
  208. package/.savepoint/audit/v1/E01-scaffolding/proposals/AGENTS.md +0 -66
  209. package/.savepoint/audit/v1/E01-scaffolding/proposals/Design.md +0 -210
  210. package/.savepoint/audit/v1/E01-scaffolding/proposals/epic-Design.md +0 -117
  211. package/.savepoint/audit/v1/E01-scaffolding/proposals/quality-review.md +0 -101
  212. package/.savepoint/audit/v1/E01-scaffolding/snapshot.md +0 -54
  213. package/.savepoint/audit/v1/E02-data-model/snapshot.md +0 -128
  214. package/.savepoint/audit/v1/E02-data-readers/proposals.md +0 -123
  215. package/.savepoint/audit/v1/E02-data-readers/snapshot.md +0 -54
  216. package/.savepoint/audit/v1/E03-board-tui-core/proposals.md +0 -146
  217. package/.savepoint/audit/v1/E03-board-tui-core/snapshot.md +0 -57
  218. package/.savepoint/audit/v1/E03-cli-foundation/snapshot.md +0 -106
  219. package/.savepoint/audit/v1/E04-board-components/proposals.md +0 -118
  220. package/.savepoint/audit/v1/E04-board-components/snapshot.md +0 -77
  221. package/.savepoint/audit/v1/E04-templates-and-prompts/snapshot.md +0 -115
  222. package/.savepoint/audit/v1/E05-init-command/snapshot.md +0 -125
  223. package/.savepoint/audit/v1/E05-phase-transitions/proposals.md +0 -83
  224. package/.savepoint/audit/v1/E05-phase-transitions/snapshot.md +0 -36
  225. package/.savepoint/audit/v1/E06-atari-noir-layout/proposals.md +0 -130
  226. package/.savepoint/audit/v1/E06-atari-noir-layout/snapshot.md +0 -84
  227. package/.savepoint/audit/v1/E06-tui-board/snapshot.md +0 -64
  228. package/.savepoint/audit/v1/E07-audit-pipeline/snapshot.md +0 -165
  229. package/.savepoint/audit/v1/E08-board-workflow-cleanup/snapshot.md +0 -65
  230. package/.savepoint/audit/v1.1/E02-cross-platform-compatibility/snapshot.md +0 -41
  231. package/.savepoint/audit/v1.1/E04-epic-navigation/snapshot.md +0 -48
  232. package/ink-cli-ui-design.zip +0 -0
  233. package/savepoint.exe +0 -0
@@ -0,0 +1,91 @@
1
+ package init
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "strings"
7
+ "testing"
8
+
9
+ "github.com/opencode/savepoint/internal/testutil"
10
+ )
11
+
12
+ func TestAtomicWrite_createsFile(t *testing.T) {
13
+ dir := t.TempDir()
14
+ target := filepath.Join(dir, "output.txt")
15
+
16
+ if err := AtomicWrite(target, []byte("hello")); err != nil {
17
+ t.Fatalf("AtomicWrite() error = %v", err)
18
+ }
19
+
20
+ data, err := os.ReadFile(target)
21
+ if err != nil {
22
+ t.Fatal(err)
23
+ }
24
+ if string(data) != "hello" {
25
+ t.Fatalf("got %q, want %q", string(data), "hello")
26
+ }
27
+ }
28
+
29
+ func TestAtomicWrite_replacesExistingFile(t *testing.T) {
30
+ dir := t.TempDir()
31
+ target := filepath.Join(dir, "output.txt")
32
+ testutil.WriteFile(t, target, "old")
33
+
34
+ if err := AtomicWrite(target, []byte("new")); err != nil {
35
+ t.Fatalf("AtomicWrite() error = %v", err)
36
+ }
37
+
38
+ data, err := os.ReadFile(target)
39
+ if err != nil {
40
+ t.Fatal(err)
41
+ }
42
+ if string(data) != "new" {
43
+ t.Fatalf("got %q, want %q", string(data), "new")
44
+ }
45
+
46
+ assertNoWriteArtifacts(t, dir)
47
+ }
48
+
49
+ func TestAtomicWrite_noTempFileLeftBehind(t *testing.T) {
50
+ dir := t.TempDir()
51
+ target := filepath.Join(dir, "output.txt")
52
+
53
+ if err := AtomicWrite(target, []byte("data")); err != nil {
54
+ t.Fatalf("AtomicWrite() error = %v", err)
55
+ }
56
+
57
+ assertNoWriteArtifacts(t, dir)
58
+ }
59
+
60
+ func TestAtomicWrite_handlesNestedDirectories(t *testing.T) {
61
+ dir := t.TempDir()
62
+ target := filepath.Join(dir, "deep", "nested", "output.txt")
63
+
64
+ // Parent directories must exist before calling AtomicWrite
65
+ testutil.MkdirAll(t, filepath.Dir(target))
66
+
67
+ if err := AtomicWrite(target, []byte("nested")); err != nil {
68
+ t.Fatalf("AtomicWrite() error = %v", err)
69
+ }
70
+
71
+ if _, err := os.Stat(target); err != nil {
72
+ t.Errorf("target not created: %v", err)
73
+ }
74
+ }
75
+
76
+ func assertNoWriteArtifacts(t *testing.T, dir string) {
77
+ t.Helper()
78
+
79
+ entries, err := os.ReadDir(dir)
80
+ if err != nil {
81
+ t.Fatal(err)
82
+ }
83
+ for _, e := range entries {
84
+ if strings.HasPrefix(e.Name(), ".tmp-") && strings.HasSuffix(e.Name(), ".write") {
85
+ t.Errorf("temp file %q left behind after successful write", e.Name())
86
+ }
87
+ if strings.HasSuffix(e.Name(), ".savepoint-bak") {
88
+ t.Errorf("backup file %q left behind after successful write", e.Name())
89
+ }
90
+ }
91
+ }
@@ -0,0 +1,133 @@
1
+ package styles
2
+
3
+ import (
4
+ "testing"
5
+
6
+ "github.com/charmbracelet/lipgloss"
7
+ )
8
+
9
+ func TestPaletteConstants_present(t *testing.T) {
10
+ // Truecolor tier
11
+ if Background == "" {
12
+ t.Error("Background constant is empty")
13
+ }
14
+ if Surface == "" {
15
+ t.Error("Surface constant is empty")
16
+ }
17
+ if Surface2 == "" {
18
+ t.Error("Surface2 constant is empty")
19
+ }
20
+ if Border == "" {
21
+ t.Error("Border constant is empty")
22
+ }
23
+ if BorderSubtle == "" {
24
+ t.Error("BorderSubtle constant is empty")
25
+ }
26
+ if PrimaryText == "" {
27
+ t.Error("PrimaryText constant is empty")
28
+ }
29
+ if AtariOrange == "" {
30
+ t.Error("AtariOrange constant is empty")
31
+ }
32
+ if NPPGreen == "" {
33
+ t.Error("NPPGreen constant is empty")
34
+ }
35
+ if VibePurple == "" {
36
+ t.Error("VibePurple constant is empty")
37
+ }
38
+ if Dim == "" {
39
+ t.Error("Dim constant is empty")
40
+ }
41
+ }
42
+
43
+ func TestPaletteConstants_256tier(t *testing.T) {
44
+ if Background256 == "" {
45
+ t.Error("Background256 constant is empty")
46
+ }
47
+ if Surface256 == "" {
48
+ t.Error("Surface256 constant is empty")
49
+ }
50
+ if Surface2256 == "" {
51
+ t.Error("Surface2256 constant is empty")
52
+ }
53
+ if Border256 == "" {
54
+ t.Error("Border256 constant is empty")
55
+ }
56
+ if BorderSubtle256 == "" {
57
+ t.Error("BorderSubtle256 constant is empty")
58
+ }
59
+ if PrimaryText256 == "" {
60
+ t.Error("PrimaryText256 constant is empty")
61
+ }
62
+ if AtariOrange256 == "" {
63
+ t.Error("AtariOrange256 constant is empty")
64
+ }
65
+ if NPPGreen256 == "" {
66
+ t.Error("NPPGreen256 constant is empty")
67
+ }
68
+ if VibePurple256 == "" {
69
+ t.Error("VibePurple256 constant is empty")
70
+ }
71
+ if Dim256 == "" {
72
+ t.Error("Dim256 constant is empty")
73
+ }
74
+ }
75
+
76
+ func TestPaletteConstants_16tier(t *testing.T) {
77
+ if Background16 == "" {
78
+ t.Error("Background16 constant is empty")
79
+ }
80
+ if Surface16 == "" {
81
+ t.Error("Surface16 constant is empty")
82
+ }
83
+ if Surface216 == "" {
84
+ t.Error("Surface216 constant is empty")
85
+ }
86
+ if Border16 == "" {
87
+ t.Error("Border16 constant is empty")
88
+ }
89
+ if BorderSubtle16 == "" {
90
+ t.Error("BorderSubtle16 constant is empty")
91
+ }
92
+ if PrimaryText16 == "" {
93
+ t.Error("PrimaryText16 constant is empty")
94
+ }
95
+ if AtariOrange16 == "" {
96
+ t.Error("AtariOrange16 constant is empty")
97
+ }
98
+ if NPPGreen16 == "" {
99
+ t.Error("NPPGreen16 constant is empty")
100
+ }
101
+ if VibePurple16 == "" {
102
+ t.Error("VibePurple16 constant is empty")
103
+ }
104
+ }
105
+
106
+ func TestColor(t *testing.T) {
107
+ c := color("#FF0000", "196", "9")
108
+ expected := lipgloss.CompleteColor{TrueColor: "#FF0000", ANSI256: "196", ANSI: "9"}
109
+ if c != expected {
110
+ t.Errorf("color() = %+v, want %+v", c, expected)
111
+ }
112
+ }
113
+
114
+ func TestColor_usesPaletteConstants(t *testing.T) {
115
+ c := color(AtariOrange, AtariOrange256, AtariOrange16)
116
+ if c.TrueColor != AtariOrange {
117
+ t.Errorf("color TrueColor = %q, want %q", c.TrueColor, AtariOrange)
118
+ }
119
+ if c.ANSI256 != AtariOrange256 {
120
+ t.Errorf("color ANSI256 = %q, want %q", c.ANSI256, AtariOrange256)
121
+ }
122
+ if c.ANSI != AtariOrange16 {
123
+ t.Errorf("color ANSI = %q, want %q", c.ANSI, AtariOrange16)
124
+ }
125
+ }
126
+
127
+ func TestColor_returnsCompleteColor(t *testing.T) {
128
+ var cc lipgloss.CompleteColor
129
+ cc = color(Background, Background256, Background16)
130
+ if cc.TrueColor != Background {
131
+ t.Errorf("expected TrueColor %q, got %q", Background, cc.TrueColor)
132
+ }
133
+ }
@@ -0,0 +1,113 @@
1
+ package testutil
2
+
3
+ import (
4
+ "fmt"
5
+ "path/filepath"
6
+ "strings"
7
+ "testing"
8
+ )
9
+
10
+ // WriteRouter writes a router.md at root with the given fields.
11
+ // If nextAction is empty, it defaults to "".
12
+ func WriteRouter(t testing.TB, root, state, release, epic, task, nextAction string) {
13
+ t.Helper()
14
+ if nextAction == "" {
15
+ nextAction = `""`
16
+ } else {
17
+ nextAction = `"` + nextAction + `"`
18
+ }
19
+ if task == "" {
20
+ task = `""`
21
+ } else {
22
+ task = `"` + task + `"`
23
+ }
24
+ content := "# Agent State Machine\n\n## Current state\n\n```yaml\nstate: " + state + "\nrelease: " + release + "\nepic: " + epic + "\ntask: " + task + "\nnext_action: " + nextAction + "\n```\n"
25
+ WriteFile(t, filepath.Join(root, "router.md"), content)
26
+ }
27
+
28
+ // WriteReleasePRD writes a minimal release PRD file.
29
+ func WriteReleasePRD(t testing.TB, releasePath string) {
30
+ t.Helper()
31
+ releaseID := filepath.Base(releasePath)
32
+ WriteFile(t, filepath.Join(releasePath, releaseID+"-PRD.md"), "---\ntype: project-prd\nstatus: active\n---\n\n# Release\n")
33
+ }
34
+
35
+ // WriteEpicDetail writes a minimal epic detail file.
36
+ func WriteEpicDetail(t testing.TB, epicPath, prefix string) {
37
+ t.Helper()
38
+ WriteFile(t, filepath.Join(epicPath, prefix+"-Detail.md"), "---\ntype: epic-design\nstatus: planned\n---\n\n# Epic\n")
39
+ }
40
+
41
+ // TaskFixture describes a task file to create.
42
+ type TaskFixture struct {
43
+ Slug string // e.g. "T001-task" — becomes filename
44
+ Release string // optional; omitted if empty
45
+ Status string
46
+ Phase string // optional; omitted if empty
47
+ Objective string
48
+ DependsOn []string // optional; defaults to empty list
49
+ Body string // optional; defaults to minimal body
50
+ Extra map[string]string // optional; extra frontmatter fields
51
+ }
52
+
53
+ // WriteTask writes a task file in the savepoint directory structure.
54
+ func WriteTask(t testing.TB, root, release, epic string, task TaskFixture) {
55
+ t.Helper()
56
+ path := filepath.Join(root, "releases", release, "epics", epic, "tasks", task.Slug+".md")
57
+ id := epic + "/" + task.Slug
58
+
59
+ var b strings.Builder
60
+ b.WriteString("---\n")
61
+ b.WriteString("id: " + id + "\n")
62
+ if task.Release != "" {
63
+ b.WriteString("release: " + task.Release + "\n")
64
+ }
65
+ b.WriteString("status: " + task.Status + "\n")
66
+ if task.Phase != "" {
67
+ b.WriteString("phase: " + task.Phase + "\n")
68
+ }
69
+ b.WriteString("objective: \"" + task.Objective + "\"\n")
70
+
71
+ deps := "[]"
72
+ if len(task.DependsOn) > 0 {
73
+ quoted := make([]string, len(task.DependsOn))
74
+ for i, d := range task.DependsOn {
75
+ quoted[i] = fmt.Sprintf("%q", d)
76
+ }
77
+ deps = "[" + strings.Join(quoted, ", ") + "]"
78
+ }
79
+ b.WriteString("depends_on: " + deps + "\n")
80
+ for k, v := range task.Extra {
81
+ b.WriteString(k + ": " + v + "\n")
82
+ }
83
+ b.WriteString("---\n\n")
84
+
85
+ if task.Body != "" {
86
+ b.WriteString(task.Body)
87
+ } else {
88
+ b.WriteString("# " + task.Slug + "\n\n## Acceptance Criteria\n\n- it works\n")
89
+ }
90
+
91
+ WriteFile(t, path, b.String())
92
+ }
93
+
94
+ // SetupMinimalProject creates a minimal valid savepoint project structure.
95
+ // It creates config.yml, router.md, release PRD, epic detail, and the directory tree.
96
+ func SetupMinimalProject(t testing.TB, root, release, epic string) {
97
+ t.Helper()
98
+ releasePath := filepath.Join(root, "releases", release)
99
+ //nolint:gocritic // assignment to same epicPath variable is fine
100
+ epicPath := filepath.Join(releasePath, "epics", epic)
101
+ tasksPath := filepath.Join(epicPath, "tasks")
102
+ MkdirAll(t, tasksPath)
103
+
104
+ prefix := epic
105
+ if idx := strings.IndexByte(epic, '-'); idx != -1 {
106
+ prefix = epic[:idx]
107
+ }
108
+
109
+ WriteFile(t, filepath.Join(root, "config.yml"), "quality_gates:\n lint: null\n typecheck: null\n test: null\ntheme:\n bg: \"#000\"\n")
110
+ WriteRouter(t, root, "task-building", release, epic, "", "")
111
+ WriteReleasePRD(t, releasePath)
112
+ WriteEpicDetail(t, epicPath, prefix)
113
+ }
@@ -0,0 +1,26 @@
1
+ package testutil
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "testing"
7
+ )
8
+
9
+ // WriteFile writes content to path, creating parent directories if needed.
10
+ func WriteFile(t testing.TB, path, content string) {
11
+ t.Helper()
12
+ if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
13
+ t.Fatal(err)
14
+ }
15
+ if err := os.WriteFile(path, []byte(content), 0644); err != nil {
16
+ t.Fatal(err)
17
+ }
18
+ }
19
+
20
+ // MkdirAll creates directories with mode 0755, fatal on error.
21
+ func MkdirAll(t testing.TB, path string) {
22
+ t.Helper()
23
+ if err := os.MkdirAll(path, 0755); err != nil {
24
+ t.Fatal(err)
25
+ }
26
+ }
package/main.go CHANGED
@@ -1,20 +1,117 @@
1
1
  package main
2
2
 
3
3
  import (
4
+ "context"
5
+ "embed"
4
6
  "fmt"
7
+ "io/fs"
5
8
  "os"
6
9
 
10
+ "github.com/opencode/savepoint/cmd"
7
11
  "github.com/opencode/savepoint/internal/board"
12
+ "github.com/opencode/savepoint/internal/data"
13
+ "github.com/opencode/savepoint/internal/doctor"
14
+ savepointinit "github.com/opencode/savepoint/internal/init"
8
15
  )
9
16
 
17
+ //go:embed templates/project
18
+ //go:embed templates/project/.savepoint
19
+ //go:embed templates/prompts
20
+ var projectTemplates embed.FS
21
+
10
22
  var version = "dev"
11
23
 
12
24
  func main() {
13
- if len(os.Args) > 1 && os.Args[1] == "--version" {
14
- fmt.Println(version)
15
- os.Exit(0)
25
+ if len(os.Args) > 1 {
26
+ switch os.Args[1] {
27
+ case "--version":
28
+ fmt.Println(version)
29
+ os.Exit(0)
30
+ case "init":
31
+ if err := cmd.RunInit(context.Background(), os.Args[2:], os.Stdout, initRunner); err != nil {
32
+ fmt.Fprintln(os.Stderr, err)
33
+ os.Exit(1)
34
+ }
35
+ os.Exit(0)
36
+ case "board":
37
+ if err := cmd.RunBoard(context.Background(), os.Args[2:], os.Stdout, func(opts cmd.BoardOptions) error {
38
+ return board.RunWithFilters(opts.Release, opts.Epic)
39
+ }); err != nil {
40
+ fmt.Fprintln(os.Stderr, err)
41
+ os.Exit(1)
42
+ }
43
+ os.Exit(0)
44
+ case "doctor":
45
+ code, err := cmd.RunDoctor(context.Background(), os.Args[2:], os.Stdout, func(opts cmd.DoctorOptions) (int, error) {
46
+ return runDoctorChecks(opts)
47
+ })
48
+ if err != nil {
49
+ fmt.Fprintln(os.Stderr, err)
50
+ }
51
+ os.Exit(code)
52
+ }
16
53
  }
17
54
  if err := board.Run(); err != nil {
18
55
  panic(err)
19
56
  }
20
- }
57
+ }
58
+
59
+ func runDoctorChecks(opts cmd.DoctorOptions) (int, error) {
60
+ discover := data.NewDiscover()
61
+ root, err := discover.FindSavepointRoot(".")
62
+ if err != nil {
63
+ return 2, fmt.Errorf("savepoint root not found: %w", err)
64
+ }
65
+
66
+ report := doctor.RunAllChecks(root, opts.Epic)
67
+ fmt.Fprint(os.Stdout, report.Format())
68
+
69
+ if report.HasProblems() {
70
+ return 1, nil
71
+ }
72
+ return 0, nil
73
+ }
74
+
75
+ func initRunner(ctx context.Context, opts cmd.InitOptions) error {
76
+ if err := savepointinit.ValidateTarget(opts.Dir, opts.Force); err != nil {
77
+ return err
78
+ }
79
+
80
+ sub, err := fs.Sub(projectTemplates, "templates/project")
81
+ if err != nil {
82
+ return fmt.Errorf("cannot load templates: %w", err)
83
+ }
84
+
85
+ projectName := savepointinit.ProjectNameFromDir(opts.Dir)
86
+ if err := savepointinit.Scaffold(sub, opts.Dir, projectName, opts.Force); err != nil {
87
+ return err
88
+ }
89
+
90
+ promptSub, err := fs.Sub(projectTemplates, "templates/prompts")
91
+ if err != nil {
92
+ return fmt.Errorf("cannot load prompt templates: %w", err)
93
+ }
94
+
95
+ prompt, err := savepointinit.RenderMagicPrompt(promptSub, projectName)
96
+ if err != nil {
97
+ return fmt.Errorf("render magic prompt: %w", err)
98
+ }
99
+
100
+ fmt.Println(prompt)
101
+
102
+ result := savepointinit.CopyToClipboard(prompt)
103
+ switch result.Status {
104
+ case savepointinit.ClipboardCopied:
105
+ fmt.Fprintf(os.Stderr, "prompt copied to clipboard via %s\n", result.Tool)
106
+ case savepointinit.ClipboardFailed:
107
+ fmt.Fprintf(os.Stderr, "warning: clipboard copy failed: %s\n", result.Message)
108
+ }
109
+
110
+ if opts.Install {
111
+ if err := savepointinit.InstallDependencies(opts.Dir); err != nil {
112
+ return err
113
+ }
114
+ }
115
+
116
+ return nil
117
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "savepoint",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "It’s a simple, file-based state machine and cinematic Terminal UI (TUI) designed to force you—and your agent (Claude, Cursor, Aider, Gemini)—to slow down, write down what you're actually building, and check your work before moving on.",
5
5
  "keywords": [
6
6
  "board",
@@ -21,6 +21,6 @@
21
21
  "savepoint": "./savepoint"
22
22
  },
23
23
  "scripts": {
24
- "test": "savepoint init"
24
+ "test": "echo \"Run 'make test' for Go tests\""
25
25
  }
26
26
  }