savepoint 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +4 -1
- package/.savepoint/Design.md +22 -17
- package/.savepoint/audit/v1/E01/proposals.md +168 -0
- package/.savepoint/audit/v1/E01/snapshot.md +78 -0
- package/.savepoint/audit/{E01-go-setup → v1/E01-go-setup}/proposals.md +7 -7
- package/.savepoint/audit/{E01-go-setup → v1/E01-go-setup}/snapshot.md +2 -2
- package/.savepoint/audit/{E01-scaffolding → v1/E01-scaffolding}/proposals/AGENTS.md +5 -5
- package/.savepoint/audit/{E02-data-readers → v1/E02-data-readers}/proposals.md +20 -20
- package/.savepoint/audit/{E02-data-readers → v1/E02-data-readers}/snapshot.md +1 -1
- package/.savepoint/audit/{E03-board-tui-core → v1/E03-board-tui-core}/proposals.md +11 -11
- package/.savepoint/audit/{E03-board-tui-core → v1/E03-board-tui-core}/snapshot.md +1 -1
- package/.savepoint/audit/{E04-board-components → v1/E04-board-components}/proposals.md +14 -14
- package/.savepoint/audit/{E04-board-components → v1/E04-board-components}/snapshot.md +1 -1
- package/.savepoint/audit/{E05-init-command → v1/E05-init-command}/snapshot.md +1 -1
- package/.savepoint/audit/{E05-phase-transitions → v1/E05-phase-transitions}/proposals.md +4 -4
- package/.savepoint/audit/{E05-phase-transitions → v1/E05-phase-transitions}/snapshot.md +1 -1
- package/.savepoint/audit/{E06-atari-noir-layout → v1/E06-atari-noir-layout}/proposals.md +2 -2
- package/.savepoint/audit/{E07-audit-pipeline → v1/E07-audit-pipeline}/snapshot.md +6 -6
- package/.savepoint/audit/v1.1/E02-cross-platform-compatibility/proposals.md +114 -0
- package/.savepoint/audit/v1.1/E02-cross-platform-compatibility/snapshot.md +41 -0
- package/.savepoint/audit/v1.1/E04-epic-navigation/proposals.md +156 -0
- package/.savepoint/audit/v1.1/E04-epic-navigation/snapshot.md +48 -0
- package/.savepoint/releases/v1/epics/E01-go-setup/tasks/T001-init-module.md +1 -1
- package/.savepoint/releases/v1/epics/E03-board-tui-core/tasks/T005-layout.md +1 -1
- package/.savepoint/releases/v1/epics/E04-board-components/tasks/T002-card.md +1 -1
- package/.savepoint/releases/v1/epics/E04-board-components/tasks/T006-help-overlay.md +1 -1
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/{Design.md → E06-Detail.md} +5 -3
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T002-header-and-dividers.md +1 -1
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T003-footer-status-bar.md +1 -1
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T004-component-refinement.md +1 -1
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T010-auto-refresh-watcher.md +2 -0
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/{Design.md → E01-Detail.md} +9 -1
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/{T007-next-activity-header.md → T001-next-activity-header.md} +13 -12
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T002-rename-epic-design-files.md +9 -9
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T003-rename-release-prd.md +2 -2
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T004-update-instruction-files.md +13 -12
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T005-update-cross-references.md +14 -13
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T006-column-and-detail-scrolling.md +25 -15
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T007-column-focus-border-stability.md +57 -0
- package/.savepoint/releases/v1.1/epics/E02-cross-platform-compatibility/{Design.md → E02-Detail.md} +12 -3
- package/.savepoint/releases/v1.1/epics/E02-cross-platform-compatibility/tasks/T001-fix-makefile.md +11 -8
- package/.savepoint/releases/v1.1/epics/E02-cross-platform-compatibility/tasks/T002-linux-build-target.md +12 -7
- package/.savepoint/releases/v1.1/epics/E02-cross-platform-compatibility/tasks/T003-macos-build-target.md +9 -5
- package/.savepoint/releases/v1.1/epics/E02-cross-platform-compatibility/tasks/T004-smoke-tests-and-artifacts.md +30 -9
- package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/E03-Detail.md +32 -0
- package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/tasks/T001-border-resize-fix.md +40 -0
- package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/tasks/T002-next-activity-below-header.md +64 -0
- package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/tasks/T003-checkbox-rendering-fix.md +56 -0
- package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/tasks/T005-unify-status-glyphs.md +65 -0
- package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/tasks/T006-forced-256-color-profile.md +36 -0
- package/.savepoint/releases/v1.1/epics/E04-epic-navigation/E04-Detail.md +51 -0
- package/.savepoint/releases/v1.1/epics/E04-epic-navigation/tasks/T001-sidebar-focusable-navigation.md +65 -0
- package/.savepoint/releases/v1.1/epics/E04-epic-navigation/tasks/T002-epic-detail-overlay.md +73 -0
- package/.savepoint/releases/v1.1/epics/E04-epic-navigation/tasks/T003-epic-status-glyphs.md +73 -0
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/E05-Detail.md +45 -0
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T001-update-agents-md.md +34 -0
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T002-update-router-md.md +30 -0
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T003-update-design-md.md +33 -0
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T004-implement-m-hotkey.md +88 -0
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T005-update-help-overlay.md +30 -0
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T006-tests-and-quality-gates.md +46 -0
- package/.savepoint/releases/v1.1/v1.1-PRD.md +79 -0
- package/.savepoint/router.md +33 -105
- package/AGENTS.md +56 -113
- package/Makefile +19 -3
- package/README.md +6 -5
- package/agent-skills/savepoint-audit/SKILL.md +6 -6
- package/agent-skills/savepoint-build-task/SKILL.md +2 -2
- package/agent-skills/savepoint-create-plan/SKILL.md +3 -3
- package/agent-skills/savepoint-create-task/SKILL.md +2 -2
- package/agent-skills/savepoint-draft-prd/SKILL.md +1 -1
- package/agent-skills/savepoint-system-design/SKILL.md +1 -1
- package/internal/board/board.go +43 -27
- package/internal/board/board_test.go +71 -0
- package/internal/board/card.go +34 -3
- package/internal/board/card_test.go +105 -12
- package/internal/board/column.go +40 -5
- package/internal/board/column_test.go +60 -13
- package/internal/board/detail.go +107 -25
- package/internal/board/detail_test.go +117 -26
- package/internal/board/epic_panel.go +105 -8
- package/internal/board/epic_panel_test.go +343 -5
- package/internal/board/layout.go +12 -2
- package/internal/board/layout_test.go +17 -0
- package/internal/board/model.go +141 -24
- package/internal/board/render_policy_test.go +77 -0
- package/internal/board/status.go +23 -0
- package/internal/board/update.go +276 -8
- package/internal/board/update_test.go +166 -0
- package/internal/board/view.go +131 -17
- package/internal/board/view_test.go +159 -1
- package/internal/board/watch.go +24 -6
- package/internal/buildtool/main.go +219 -0
- package/internal/data/parser.go +8 -0
- package/internal/data/parser_test.go +35 -0
- package/internal/data/task.go +10 -0
- package/internal/styles/palette.go +3 -3
- package/internal/styles/styles.go +39 -12
- package/main.go +9 -0
- package/package.json +1 -1
- package/savepoint +0 -0
- package/savepoint.exe +0 -0
- package/templates/project/.savepoint/router.md +6 -5
- package/templates/project/AGENTS.md +47 -101
- package/templates/prompts/audit-reconciliation.prompt.md +6 -6
- package/templates/prompts/epic-design.prompt.md +3 -3
- package/templates/prompts/task-breakdown.prompt.md +1 -1
- package/templates/prompts/task-building.prompt.md +1 -1
- package/templates/prompts/task-planning.prompt.md +1 -1
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T001-border-resize-fix.md +0 -36
- package/main.exe +0 -0
- /package/.savepoint/audit/{E01-scaffolding → v1/E01-scaffolding}/proposals/Design.md +0 -0
- /package/.savepoint/audit/{E01-scaffolding → v1/E01-scaffolding}/proposals/epic-Design.md +0 -0
- /package/.savepoint/audit/{E01-scaffolding → v1/E01-scaffolding}/proposals/quality-review.md +0 -0
- /package/.savepoint/audit/{E01-scaffolding → v1/E01-scaffolding}/snapshot.md +0 -0
- /package/.savepoint/audit/{E02-data-model → v1/E02-data-model}/snapshot.md +0 -0
- /package/.savepoint/audit/{E03-cli-foundation → v1/E03-cli-foundation}/snapshot.md +0 -0
- /package/.savepoint/audit/{E04-templates-and-prompts → v1/E04-templates-and-prompts}/snapshot.md +0 -0
- /package/.savepoint/audit/{E06-atari-noir-layout → v1/E06-atari-noir-layout}/snapshot.md +0 -0
- /package/.savepoint/audit/{E06-tui-board → v1/E06-tui-board}/snapshot.md +0 -0
- /package/.savepoint/audit/{E08-board-workflow-cleanup → v1/E08-board-workflow-cleanup}/snapshot.md +0 -0
- /package/.savepoint/releases/v1/epics/E01-go-setup/{Design.md → E01-Detail.md} +0 -0
- /package/.savepoint/releases/v1/epics/E02-data-readers/{Design.md → E02-Detail.md} +0 -0
- /package/.savepoint/releases/v1/epics/E03-board-tui-core/{Design.md → E03-Detail.md} +0 -0
- /package/.savepoint/releases/v1/epics/E04-board-components/{Design.md → E04-Detail.md} +0 -0
- /package/.savepoint/releases/v1/epics/E05-phase-transitions/{Design.md → E05-Detail.md} +0 -0
- /package/.savepoint/releases/v1/{PRD.md → v1-PRD.md} +0 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"archive/tar"
|
|
5
|
+
"compress/gzip"
|
|
6
|
+
"errors"
|
|
7
|
+
"flag"
|
|
8
|
+
"fmt"
|
|
9
|
+
"io"
|
|
10
|
+
"os"
|
|
11
|
+
"os/exec"
|
|
12
|
+
"path/filepath"
|
|
13
|
+
"runtime"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
type target struct {
|
|
17
|
+
os string
|
|
18
|
+
arch string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
var targets = []target{
|
|
22
|
+
{os: "linux", arch: "amd64"},
|
|
23
|
+
{os: "linux", arch: "arm64"},
|
|
24
|
+
{os: "darwin", arch: "amd64"},
|
|
25
|
+
{os: "darwin", arch: "arm64"},
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
var versionOverride string
|
|
29
|
+
|
|
30
|
+
func main() {
|
|
31
|
+
if err := run(os.Args[1:]); err != nil {
|
|
32
|
+
fmt.Fprintln(os.Stderr, err)
|
|
33
|
+
os.Exit(1)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
func run(args []string) error {
|
|
38
|
+
flags := flag.NewFlagSet("buildtool", flag.ContinueOnError)
|
|
39
|
+
flags.StringVar(&versionOverride, "version", "", "version to inject into the binary")
|
|
40
|
+
if err := flags.Parse(args); err != nil {
|
|
41
|
+
return err
|
|
42
|
+
}
|
|
43
|
+
if flags.NArg() != 1 {
|
|
44
|
+
return errors.New("usage: go run ./internal/buildtool [-version vX.Y.Z] <build|clean|build-linux|build-darwin|build-all|dist|smoke-test>")
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
switch flags.Arg(0) {
|
|
48
|
+
case "build":
|
|
49
|
+
return buildLocal()
|
|
50
|
+
case "clean":
|
|
51
|
+
return clean()
|
|
52
|
+
case "build-linux":
|
|
53
|
+
return buildMatching("linux")
|
|
54
|
+
case "build-darwin":
|
|
55
|
+
return buildMatching("darwin")
|
|
56
|
+
case "build-all":
|
|
57
|
+
return buildAll()
|
|
58
|
+
case "dist":
|
|
59
|
+
return dist()
|
|
60
|
+
case "smoke-test":
|
|
61
|
+
return smokeTest()
|
|
62
|
+
default:
|
|
63
|
+
return fmt.Errorf("unknown build target %q", flags.Arg(0))
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
func buildLocal() error {
|
|
68
|
+
return runGoBuild(localExecutable(), runtime.GOOS, runtime.GOARCH)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
func clean() error {
|
|
72
|
+
for _, path := range []string{"savepoint", "savepoint.exe", "dist"} {
|
|
73
|
+
if err := os.RemoveAll(path); err != nil {
|
|
74
|
+
return fmt.Errorf("clean %s: %w", path, err)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return nil
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
func buildMatching(goos string) error {
|
|
81
|
+
for _, target := range targets {
|
|
82
|
+
if target.os != goos {
|
|
83
|
+
continue
|
|
84
|
+
}
|
|
85
|
+
if err := buildTarget(target); err != nil {
|
|
86
|
+
return err
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return nil
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
func buildAll() error {
|
|
93
|
+
for _, target := range targets {
|
|
94
|
+
if err := buildTarget(target); err != nil {
|
|
95
|
+
return err
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return nil
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
func buildTarget(target target) error {
|
|
102
|
+
output := filepath.Join("dist", target.os+"-"+target.arch, "savepoint")
|
|
103
|
+
return runGoBuild(output, target.os, target.arch)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
func runGoBuild(output, goos, goarch string) error {
|
|
107
|
+
if err := os.MkdirAll(filepath.Dir(output), 0o755); err != nil && filepath.Dir(output) != "." {
|
|
108
|
+
return fmt.Errorf("create output dir: %w", err)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
cmd := exec.Command("go", "build", "-ldflags", "-X main.version="+version(), "-o", output, "main.go")
|
|
112
|
+
cmd.Env = append(os.Environ(), "GOOS="+goos, "GOARCH="+goarch)
|
|
113
|
+
cmd.Stdout = os.Stdout
|
|
114
|
+
cmd.Stderr = os.Stderr
|
|
115
|
+
if err := cmd.Run(); err != nil {
|
|
116
|
+
return fmt.Errorf("build %s/%s: %w", goos, goarch, err)
|
|
117
|
+
}
|
|
118
|
+
return nil
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
func dist() error {
|
|
122
|
+
if err := buildAll(); err != nil {
|
|
123
|
+
return err
|
|
124
|
+
}
|
|
125
|
+
for _, target := range targets {
|
|
126
|
+
name := "savepoint-" + version() + "-" + target.os + "-" + target.arch + ".tar.gz"
|
|
127
|
+
source := filepath.Join("dist", target.os+"-"+target.arch, "savepoint")
|
|
128
|
+
archive := filepath.Join("dist", name)
|
|
129
|
+
if err := writeTarGz(archive, source, "savepoint"); err != nil {
|
|
130
|
+
return err
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return nil
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
func writeTarGz(archivePath, sourcePath, archiveName string) error {
|
|
137
|
+
source, err := os.Open(sourcePath)
|
|
138
|
+
if err != nil {
|
|
139
|
+
return fmt.Errorf("open artifact source: %w", err)
|
|
140
|
+
}
|
|
141
|
+
defer source.Close()
|
|
142
|
+
|
|
143
|
+
info, err := source.Stat()
|
|
144
|
+
if err != nil {
|
|
145
|
+
return fmt.Errorf("stat artifact source: %w", err)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
archive, err := os.Create(archivePath)
|
|
149
|
+
if err != nil {
|
|
150
|
+
return fmt.Errorf("create archive: %w", err)
|
|
151
|
+
}
|
|
152
|
+
defer archive.Close()
|
|
153
|
+
|
|
154
|
+
gzipWriter := gzip.NewWriter(archive)
|
|
155
|
+
defer gzipWriter.Close()
|
|
156
|
+
|
|
157
|
+
tarWriter := tar.NewWriter(gzipWriter)
|
|
158
|
+
defer tarWriter.Close()
|
|
159
|
+
|
|
160
|
+
header, err := tar.FileInfoHeader(info, "")
|
|
161
|
+
if err != nil {
|
|
162
|
+
return fmt.Errorf("create archive header: %w", err)
|
|
163
|
+
}
|
|
164
|
+
header.Name = archiveName
|
|
165
|
+
if err := tarWriter.WriteHeader(header); err != nil {
|
|
166
|
+
return fmt.Errorf("write archive header: %w", err)
|
|
167
|
+
}
|
|
168
|
+
if _, err := io.Copy(tarWriter, source); err != nil {
|
|
169
|
+
return fmt.Errorf("write archive content: %w", err)
|
|
170
|
+
}
|
|
171
|
+
return nil
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
func smokeTest() error {
|
|
175
|
+
if err := buildLocal(); err != nil {
|
|
176
|
+
return err
|
|
177
|
+
}
|
|
178
|
+
cmd := exec.Command("."+string(os.PathSeparator)+localExecutable(), "--version")
|
|
179
|
+
cmd.Stdout = os.Stdout
|
|
180
|
+
cmd.Stderr = os.Stderr
|
|
181
|
+
if err := cmd.Run(); err != nil {
|
|
182
|
+
return fmt.Errorf("smoke test: %w", err)
|
|
183
|
+
}
|
|
184
|
+
fmt.Println("smoke test passed")
|
|
185
|
+
return nil
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
func version() string {
|
|
189
|
+
if versionOverride != "" {
|
|
190
|
+
return versionOverride
|
|
191
|
+
}
|
|
192
|
+
if value := os.Getenv("VERSION"); value != "" {
|
|
193
|
+
return value
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
cmd := exec.Command("git", "describe", "--tags", "--abbrev=0")
|
|
197
|
+
output, err := cmd.Output()
|
|
198
|
+
if err == nil && len(output) > 0 {
|
|
199
|
+
return string(trimSpace(output))
|
|
200
|
+
}
|
|
201
|
+
return "v0.0.0"
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
func localExecutable() string {
|
|
205
|
+
if runtime.GOOS == "windows" {
|
|
206
|
+
return "savepoint.exe"
|
|
207
|
+
}
|
|
208
|
+
return "savepoint"
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
func trimSpace(value []byte) []byte {
|
|
212
|
+
for len(value) > 0 && (value[len(value)-1] == '\n' || value[len(value)-1] == '\r' || value[len(value)-1] == '\t' || value[len(value)-1] == ' ') {
|
|
213
|
+
value = value[:len(value)-1]
|
|
214
|
+
}
|
|
215
|
+
for len(value) > 0 && (value[0] == '\n' || value[0] == '\r' || value[0] == '\t' || value[0] == ' ') {
|
|
216
|
+
value = value[1:]
|
|
217
|
+
}
|
|
218
|
+
return value
|
|
219
|
+
}
|
package/internal/data/parser.go
CHANGED
|
@@ -175,18 +175,26 @@ func extractChecklistItems(content, heading string) []CheckItem {
|
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
items := []CheckItem{}
|
|
178
|
+
var current *CheckItem
|
|
178
179
|
for _, line := range strings.Split(section, "\n") {
|
|
179
180
|
trimmed := strings.TrimSpace(line)
|
|
180
181
|
if strings.HasPrefix(trimmed, "- [x] ") {
|
|
181
182
|
items = append(items, CheckItem{Text: strings.TrimSpace(trimmed[6:]), Done: true})
|
|
183
|
+
current = &items[len(items)-1]
|
|
182
184
|
continue
|
|
183
185
|
}
|
|
184
186
|
if strings.HasPrefix(trimmed, "- [ ] ") {
|
|
185
187
|
items = append(items, CheckItem{Text: strings.TrimSpace(trimmed[6:]), Done: false})
|
|
188
|
+
current = &items[len(items)-1]
|
|
186
189
|
continue
|
|
187
190
|
}
|
|
188
191
|
if strings.HasPrefix(trimmed, "- ") {
|
|
189
192
|
items = append(items, CheckItem{Text: strings.TrimSpace(trimmed[2:]), Done: false})
|
|
193
|
+
current = &items[len(items)-1]
|
|
194
|
+
continue
|
|
195
|
+
}
|
|
196
|
+
if trimmed != "" && current != nil {
|
|
197
|
+
current.Text = strings.TrimSpace(current.Text + " " + trimmed)
|
|
190
198
|
}
|
|
191
199
|
}
|
|
192
200
|
return items
|
|
@@ -220,3 +220,38 @@ Notes here.`
|
|
|
220
220
|
t.Errorf("Task.Checklist[1] = %+v, want {Text:\"Second checklist item.\", Done:true}", task.Checklist[1])
|
|
221
221
|
}
|
|
222
222
|
}
|
|
223
|
+
|
|
224
|
+
func TestParseTaskFile_joinsHardWrappedChecklistItems(t *testing.T) {
|
|
225
|
+
p := NewParser()
|
|
226
|
+
content := `---
|
|
227
|
+
id: E06/T001
|
|
228
|
+
status: planned
|
|
229
|
+
objective: "Style the board"
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
# Task
|
|
233
|
+
|
|
234
|
+
## Implementation Plan
|
|
235
|
+
|
|
236
|
+
- [ ] First sentence spans across a hard markdown line break
|
|
237
|
+
before it ends. Second sentence stays in the same checklist item.
|
|
238
|
+
- [x] Already checked sentence wraps
|
|
239
|
+
without becoming another checklist item.
|
|
240
|
+
`
|
|
241
|
+
|
|
242
|
+
task, err := p.ParseTaskFile("test.md", content)
|
|
243
|
+
if err != nil {
|
|
244
|
+
t.Fatalf("ParseTaskFile() error = %v", err)
|
|
245
|
+
}
|
|
246
|
+
if len(task.Checklist) != 2 {
|
|
247
|
+
t.Fatalf("Task.Checklist len = %d, want 2", len(task.Checklist))
|
|
248
|
+
}
|
|
249
|
+
wantFirst := "First sentence spans across a hard markdown line break before it ends. Second sentence stays in the same checklist item."
|
|
250
|
+
if task.Checklist[0].Text != wantFirst || task.Checklist[0].Done {
|
|
251
|
+
t.Errorf("Task.Checklist[0] = %+v, want text %q and Done=false", task.Checklist[0], wantFirst)
|
|
252
|
+
}
|
|
253
|
+
wantSecond := "Already checked sentence wraps without becoming another checklist item."
|
|
254
|
+
if task.Checklist[1].Text != wantSecond || !task.Checklist[1].Done {
|
|
255
|
+
t.Errorf("Task.Checklist[1] = %+v, want text %q and Done=true", task.Checklist[1], wantSecond)
|
|
256
|
+
}
|
|
257
|
+
}
|
package/internal/data/task.go
CHANGED
|
@@ -26,6 +26,15 @@ const (
|
|
|
26
26
|
StageAudit ProgressStage = "audit"
|
|
27
27
|
)
|
|
28
28
|
|
|
29
|
+
type TaskStatus string
|
|
30
|
+
|
|
31
|
+
const (
|
|
32
|
+
StatusPlanned TaskStatus = "planned"
|
|
33
|
+
StatusInProgress TaskStatus = "in_progress"
|
|
34
|
+
StatusDone TaskStatus = "done"
|
|
35
|
+
StatusAudited TaskStatus = "audited"
|
|
36
|
+
)
|
|
37
|
+
|
|
29
38
|
type Progress struct {
|
|
30
39
|
Stage ProgressStage `yaml:"stage"`
|
|
31
40
|
Started bool `yaml:"started"`
|
|
@@ -35,6 +44,7 @@ type Task struct {
|
|
|
35
44
|
ID string `yaml:"id"`
|
|
36
45
|
Title string `yaml:"title"`
|
|
37
46
|
Description string `yaml:"description,omitempty"`
|
|
47
|
+
Status string `yaml:"status,omitempty"`
|
|
38
48
|
Epic string `yaml:"epic"`
|
|
39
49
|
Release string `yaml:"release"`
|
|
40
50
|
Column ColumnType `yaml:"column"`
|
|
@@ -17,9 +17,9 @@ const (
|
|
|
17
17
|
|
|
18
18
|
// 256-color (ANSI256) fallbacks — nearest terminal approximations
|
|
19
19
|
const (
|
|
20
|
-
Background256 = "
|
|
21
|
-
Surface256 = "
|
|
22
|
-
Surface2256 = "
|
|
20
|
+
Background256 = "16"
|
|
21
|
+
Surface256 = "16"
|
|
22
|
+
Surface2256 = "16"
|
|
23
23
|
Border256 = "234"
|
|
24
24
|
BorderSubtle256 = "235"
|
|
25
25
|
PrimaryText256 = "230"
|
|
@@ -17,6 +17,8 @@ var (
|
|
|
17
17
|
clrDim = color(Dim, Dim256, Dim16)
|
|
18
18
|
)
|
|
19
19
|
|
|
20
|
+
var boxBorder = lipgloss.NormalBorder()
|
|
21
|
+
|
|
20
22
|
var (
|
|
21
23
|
HeaderIcon = lipgloss.NewStyle().
|
|
22
24
|
Foreground(clrOrange).
|
|
@@ -29,20 +31,21 @@ var (
|
|
|
29
31
|
Foreground(clrBorder)
|
|
30
32
|
|
|
31
33
|
HeaderFrame = lipgloss.NewStyle().
|
|
32
|
-
Background(clrSurfaceDark).
|
|
33
34
|
Padding(1, 1)
|
|
34
35
|
|
|
35
|
-
BoardFrame = lipgloss.NewStyle()
|
|
36
|
-
Background(clrSurfaceDark)
|
|
36
|
+
BoardFrame = lipgloss.NewStyle()
|
|
37
37
|
|
|
38
38
|
Column = lipgloss.NewStyle().
|
|
39
|
-
Background(clrSurfaceDark).
|
|
40
39
|
Padding(0, 1)
|
|
41
40
|
|
|
41
|
+
ColumnUnfocused = lipgloss.NewStyle().
|
|
42
|
+
BorderStyle(boxBorder).
|
|
43
|
+
BorderForeground(clrBorder).
|
|
44
|
+
Padding(0, 1)
|
|
45
|
+
|
|
42
46
|
ColumnFocused = lipgloss.NewStyle().
|
|
43
|
-
BorderStyle(
|
|
47
|
+
BorderStyle(boxBorder).
|
|
44
48
|
BorderForeground(clrOrange).
|
|
45
|
-
Background(clrSurfaceDark).
|
|
46
49
|
Padding(0, 1)
|
|
47
50
|
|
|
48
51
|
ColumnTitle = lipgloss.NewStyle().
|
|
@@ -63,30 +66,49 @@ var (
|
|
|
63
66
|
Foreground(clrText)
|
|
64
67
|
|
|
65
68
|
EpicPanel = lipgloss.NewStyle().
|
|
66
|
-
|
|
69
|
+
BorderStyle(boxBorder).
|
|
70
|
+
BorderForeground(clrBorder).
|
|
67
71
|
Padding(0, 1)
|
|
68
72
|
|
|
73
|
+
EpicItemFocused = lipgloss.NewStyle().
|
|
74
|
+
Foreground(clrPurple)
|
|
75
|
+
|
|
76
|
+
EpicTitleFocused = lipgloss.NewStyle().
|
|
77
|
+
Foreground(clrPurple).
|
|
78
|
+
Bold(true)
|
|
79
|
+
|
|
80
|
+
EpicPanelFocused = lipgloss.NewStyle().
|
|
81
|
+
BorderStyle(boxBorder).
|
|
82
|
+
BorderForeground(clrPurple).
|
|
83
|
+
Padding(0, 1)
|
|
84
|
+
|
|
69
85
|
Card = lipgloss.NewStyle().
|
|
70
|
-
Background(clrSurface).
|
|
71
86
|
Padding(0, 1)
|
|
72
87
|
|
|
73
88
|
CardFocused = lipgloss.NewStyle().
|
|
74
|
-
BorderStyle(
|
|
89
|
+
BorderStyle(boxBorder).
|
|
75
90
|
BorderForeground(clrOrange).
|
|
76
|
-
Background(clrSurface).
|
|
77
91
|
Padding(0, 1)
|
|
78
92
|
|
|
79
|
-
CardMeta
|
|
93
|
+
CardMeta = lipgloss.NewStyle().Foreground(clrDim)
|
|
94
|
+
ScrollIndicator = lipgloss.NewStyle().
|
|
95
|
+
Foreground(clrDim).
|
|
96
|
+
Faint(true)
|
|
80
97
|
|
|
81
98
|
GlyphBuild = lipgloss.NewStyle().Foreground(clrOrange)
|
|
82
99
|
GlyphTest = lipgloss.NewStyle().Foreground(clrGreen)
|
|
83
100
|
GlyphAudit = lipgloss.NewStyle().Foreground(clrPurple)
|
|
84
101
|
|
|
85
102
|
DetailOverlay = lipgloss.NewStyle().
|
|
86
|
-
BorderStyle(
|
|
103
|
+
BorderStyle(boxBorder).
|
|
87
104
|
BorderForeground(clrOrange).
|
|
88
105
|
Padding(0, 1)
|
|
89
106
|
|
|
107
|
+
EpicDetailOverlay = lipgloss.NewStyle().
|
|
108
|
+
BorderStyle(boxBorder).
|
|
109
|
+
BorderForeground(clrPurple).
|
|
110
|
+
Padding(0, 1)
|
|
111
|
+
|
|
90
112
|
// Footer phase styles
|
|
91
113
|
FooterPhasePlan = lipgloss.NewStyle().
|
|
92
114
|
Foreground(clrPurple).
|
|
@@ -106,6 +128,11 @@ var (
|
|
|
106
128
|
FooterHints = lipgloss.NewStyle().
|
|
107
129
|
Foreground(clrDim)
|
|
108
130
|
|
|
131
|
+
HeaderRight = lipgloss.NewStyle().
|
|
132
|
+
Foreground(clrDim)
|
|
133
|
+
|
|
134
|
+
RootLine = lipgloss.NewStyle()
|
|
135
|
+
|
|
109
136
|
// Tag styles for semantic encoding
|
|
110
137
|
TagDone = lipgloss.NewStyle().Foreground(clrGreen)
|
|
111
138
|
TagAI = lipgloss.NewStyle().Foreground(clrPurple)
|
package/main.go
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
package main
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"os"
|
|
6
|
+
|
|
4
7
|
"github.com/opencode/savepoint/internal/board"
|
|
5
8
|
)
|
|
6
9
|
|
|
10
|
+
var version = "dev"
|
|
11
|
+
|
|
7
12
|
func main() {
|
|
13
|
+
if len(os.Args) > 1 && os.Args[1] == "--version" {
|
|
14
|
+
fmt.Println(version)
|
|
15
|
+
os.Exit(0)
|
|
16
|
+
}
|
|
8
17
|
if err := board.Run(); err != nil {
|
|
9
18
|
panic(err)
|
|
10
19
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "savepoint",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
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",
|
package/savepoint
CHANGED
|
Binary file
|
package/savepoint.exe
CHANGED
|
Binary file
|
|
@@ -9,7 +9,7 @@ This file routes the agent based on the project's current state. Read this whene
|
|
|
9
9
|
3. The active epic Design
|
|
10
10
|
4. The active task file, when a task is selected
|
|
11
11
|
|
|
12
|
-
Read `.savepoint/PRD.md` only for project vision changes. Read `.savepoint/Design.md` only for architecture changes or audit closeout. Read `.savepoint/releases/
|
|
12
|
+
Read `.savepoint/PRD.md` only for project vision changes. Read `.savepoint/Design.md` only for architecture changes or audit closeout. Read `.savepoint/releases/{release}/{release}-PRD.md` only for release planning or epic-order questions.
|
|
13
13
|
|
|
14
14
|
**Conditional read (token discipline):** if your active task touches **Ink/TUI implementation**, also read `agent-skills/ink-tui-design/SKILL.md` after Design.md as the execution guide. If it touches **TUI rendering, theme, or visual design**, also read `.savepoint/visual-identity.md` as the visual guardrails. Otherwise skip the extra files — they are tokens you do not need.
|
|
15
15
|
|
|
@@ -30,7 +30,7 @@ If the user explicitly asks you to audit an epic, perform the audit for that epi
|
|
|
30
30
|
Persist the audit artifacts before replying:
|
|
31
31
|
|
|
32
32
|
- Ensure `.savepoint/audit/{E##-epic}/snapshot.md` exists. Create a manual snapshot once if needed.
|
|
33
|
-
- Write the proposal bundle to `.savepoint/audit/{E##-epic}/proposals.md`.
|
|
33
|
+
- Write the proposal bundle to `.savepoint/audit/{release}/{E##-epic}/proposals.md`.
|
|
34
34
|
- Do not stop at chat-only findings. The filesystem artifact is part of the task output.
|
|
35
35
|
|
|
36
36
|
## State → next action
|
|
@@ -43,9 +43,9 @@ The project has its PRD and Design locked but no epics defined yet.
|
|
|
43
43
|
|
|
44
44
|
**Next action:**
|
|
45
45
|
|
|
46
|
-
1. Read `.savepoint/releases/
|
|
46
|
+
1. Read `.savepoint/releases/{release}/{release}-PRD.md` — the release scope (epic list lives there).
|
|
47
47
|
2. Help the user define the epics list and confirm priority.
|
|
48
|
-
3. For each epic in order, create the directory `.savepoint/releases/
|
|
48
|
+
3. For each epic in order, create the directory `.savepoint/releases/{release}/epics/E##-{epic-name}/` with a `Design.md` stub.
|
|
49
49
|
4. When epic E01 (scaffolding) is created, transition to `state: epic-design` for that epic.
|
|
50
50
|
|
|
51
51
|
**Do not** start writing code. We are still in planning.
|
|
@@ -70,7 +70,7 @@ Epic Design exists but tasks are missing or not fully planned.
|
|
|
70
70
|
|
|
71
71
|
1. Re-read the epic Design.
|
|
72
72
|
2. Create or update the full epic task list — each task **independently buildable**, **objective-led**, with declared `depends_on`.
|
|
73
|
-
3. Each task file lives at `.savepoint/releases/
|
|
73
|
+
3. Each task file lives at `.savepoint/releases/{release}/epics/{E##-epic}/tasks/TNNN-slug.md` with frontmatter:
|
|
74
74
|
```yaml
|
|
75
75
|
---
|
|
76
76
|
id: {E##-epic}/TNNN-slug
|
|
@@ -150,3 +150,4 @@ If you are not Claude Opus / Gemini 2.5 Pro / GPT-5.5 / equivalent, surface a wa
|
|
|
150
150
|
> _"Heads up — I'm running on a lighter model. Savepoint's planning steps work best with top-tier models because the embedded prompts are detailed. I'll do my best, but consider switching the model for PRD/Design/Task-breakdown steps."_
|
|
151
151
|
|
|
152
152
|
Then proceed.
|
|
153
|
+
proceed.
|