savepoint 1.0.0 → 1.0.1
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 +5 -1
- package/.savepoint/Design.md +8 -4
- package/.savepoint/audit/E06-atari-noir-layout/proposals.md +130 -0
- package/.savepoint/audit/E06-atari-noir-layout/snapshot.md +84 -0
- package/.savepoint/config.yml +3 -3
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/Design.md +24 -6
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T007-detail-card-fixes.md +7 -7
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T008-checkbox-states.md +10 -8
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T009-router-priority-marker.md +16 -9
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T010-auto-refresh-watcher.md +25 -22
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/Design.md +10 -4
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T001-border-resize-fix.md +2 -1
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T002-rename-epic-design-files.md +38 -0
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T003-rename-release-prd.md +28 -0
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T004-update-instruction-files.md +50 -0
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T005-update-cross-references.md +44 -0
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T006-column-and-detail-scrolling.md +58 -0
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T007-next-activity-header.md +55 -0
- package/.savepoint/releases/v1.1/epics/E02-cross-platform-compatibility/Design.md +40 -0
- package/.savepoint/releases/v1.1/epics/E02-cross-platform-compatibility/tasks/T001-fix-makefile.md +34 -0
- package/.savepoint/releases/v1.1/epics/E02-cross-platform-compatibility/tasks/T002-linux-build-target.md +33 -0
- package/.savepoint/releases/v1.1/epics/E02-cross-platform-compatibility/tasks/T003-macos-build-target.md +32 -0
- package/.savepoint/releases/v1.1/epics/E02-cross-platform-compatibility/tasks/T004-smoke-tests-and-artifacts.md +38 -0
- package/.savepoint/router.md +1 -1
- package/.savepoint/visual-identity.md +4 -3
- package/AGENTS.md +3 -3
- package/README.md +1 -1
- package/go.mod +4 -1
- package/go.sum +2 -0
- package/internal/board/board.go +42 -6
- package/internal/board/board_test.go +53 -0
- package/internal/board/card.go +9 -3
- package/internal/board/card_test.go +28 -14
- package/internal/board/column.go +2 -2
- package/internal/board/column_test.go +17 -9
- package/internal/board/detail.go +21 -11
- package/internal/board/detail_test.go +30 -14
- package/internal/board/model.go +7 -1
- package/internal/board/update.go +24 -1
- package/internal/board/view.go +13 -3
- package/internal/board/view_test.go +2 -2
- package/internal/board/watch.go +82 -0
- package/internal/data/parser.go +31 -1
- package/internal/data/parser_test.go +8 -2
- package/internal/data/task.go +12 -2
- package/internal/styles/palette.go +6 -4
- package/internal/styles/styles.go +5 -15
- package/package.json +5 -4
- package/savepoint +0 -0
package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T004-update-instruction-files.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: E01-tui-optimisation/T004-update-instruction-files
|
|
3
|
+
status: planned
|
|
4
|
+
objective: "Update all instruction files to reference new naming convention"
|
|
5
|
+
depends_on:
|
|
6
|
+
- E01-tui-optimisation/T002-rename-epic-design-files
|
|
7
|
+
- E01-tui-optimisation/T003-rename-release-prd
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# T004: Update Instruction Files
|
|
11
|
+
|
|
12
|
+
## Acceptance Criteria
|
|
13
|
+
|
|
14
|
+
- `.savepoint/router.md` references `E##-Detail.md` and `v1-PRD.md` (not `Design.md` or `PRD.md`)
|
|
15
|
+
- `AGENTS.md` references `E##-Detail.md` pattern
|
|
16
|
+
- `agent-skills/savepoint-create-plan/SKILL.md` updated
|
|
17
|
+
- `agent-skills/savepoint-system-design/SKILL.md` updated
|
|
18
|
+
- `agent-skills/savepoint-build-task/SKILL.md` updated
|
|
19
|
+
- `agent-skills/savepoint-audit/SKILL.md` updated
|
|
20
|
+
- `agent-skills/savepoint-create-task/SKILL.md` updated
|
|
21
|
+
- `templates/project/AGENTS.md` updated
|
|
22
|
+
- `templates/prompts/task-building.prompt.md` updated
|
|
23
|
+
- `templates/prompts/task-planning.prompt.md` updated
|
|
24
|
+
- `templates/prompts/task-breakdown.prompt.md` updated
|
|
25
|
+
- Root `.savepoint/Design.md` references unchanged (per user request)
|
|
26
|
+
|
|
27
|
+
## Implementation Plan
|
|
28
|
+
|
|
29
|
+
- [ ] Update `.savepoint/router.md` — all epic `Design.md` → `E##-Detail.md`, `PRD.md` → `v1-PRD.md`
|
|
30
|
+
- [ ] Update `AGENTS.md` — epic design file path pattern
|
|
31
|
+
- [ ] Update `agent-skills/savepoint-create-plan/SKILL.md`
|
|
32
|
+
- [ ] Update `agent-skills/savepoint-system-design/SKILL.md`
|
|
33
|
+
- [ ] Update `agent-skills/savepoint-build-task/SKILL.md`
|
|
34
|
+
- [ ] Update `agent-skills/savepoint-audit/SKILL.md`
|
|
35
|
+
- [ ] Update `agent-skills/savepoint-create-task/SKILL.md`
|
|
36
|
+
- [ ] Update `templates/project/AGENTS.md`
|
|
37
|
+
- [ ] Update `templates/prompts/task-building.prompt.md`
|
|
38
|
+
- [ ] Update `templates/prompts/task-planning.prompt.md`
|
|
39
|
+
- [ ] Update `templates/prompts/task-breakdown.prompt.md`
|
|
40
|
+
|
|
41
|
+
## Context Log
|
|
42
|
+
|
|
43
|
+
Files read:
|
|
44
|
+
- All instruction files listed above
|
|
45
|
+
|
|
46
|
+
Estimated input tokens: 800
|
|
47
|
+
|
|
48
|
+
Notes:
|
|
49
|
+
- Only change path references, do not alter surrounding content
|
|
50
|
+
- Root Design.md references stay as-is
|
package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T005-update-cross-references.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: E01-tui-optimisation/T005-update-cross-references
|
|
3
|
+
status: planned
|
|
4
|
+
objective: "Update all cross-references in task files and audit snapshots"
|
|
5
|
+
depends_on:
|
|
6
|
+
- E01-tui-optimisation/T002-rename-epic-design-files
|
|
7
|
+
- E01-tui-optimisation/T004-update-instruction-files
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# T005: Update Cross-References
|
|
11
|
+
|
|
12
|
+
## Acceptance Criteria
|
|
13
|
+
|
|
14
|
+
- All task file "Files read" sections reference `E##-Detail.md` instead of `Design.md`
|
|
15
|
+
- All audit `proposals.md` files reference renamed epic design files
|
|
16
|
+
- All audit `snapshot.md` files reference renamed epic design files
|
|
17
|
+
- No stale `Design.md` references remain in any epic-level file paths
|
|
18
|
+
- 244 grep matches reduced to 0 for epic-level `Design.md` references
|
|
19
|
+
|
|
20
|
+
## Implementation Plan
|
|
21
|
+
|
|
22
|
+
- [ ] Find all task files with "Design.md" in content and update epic-level references to `E##-Detail.md`
|
|
23
|
+
- [ ] Update `.savepoint/audit/E02-data-readers/proposals.md`
|
|
24
|
+
- [ ] Update `.savepoint/audit/E03-board-tui-core/proposals.md`
|
|
25
|
+
- [ ] Update `.savepoint/audit/E03-board-tui-core/snapshot.md`
|
|
26
|
+
- [ ] Update `.savepoint/audit/E04-board-components/proposals.md`
|
|
27
|
+
- [ ] Update `.savepoint/audit/E04-board-components/snapshot.md`
|
|
28
|
+
- [ ] Update `.savepoint/audit/E05-phase-transitions/proposals.md`
|
|
29
|
+
- [ ] Update `.savepoint/audit/E05-phase-transitions/snapshot.md`
|
|
30
|
+
- [ ] Update `.savepoint/releases/v1/epics/E03-board-tui-core/tasks/T*.md` files
|
|
31
|
+
- [ ] Update `.savepoint/releases/v1/epics/E04-board-components/tasks/T*.md` files
|
|
32
|
+
- [ ] Update `.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T*.md` files
|
|
33
|
+
- [ ] Run grep verification: no `epics/.*Design\.md` references remain
|
|
34
|
+
|
|
35
|
+
## Context Log
|
|
36
|
+
|
|
37
|
+
Files read:
|
|
38
|
+
- All task and audit files listed above
|
|
39
|
+
|
|
40
|
+
Estimated input tokens: 1200
|
|
41
|
+
|
|
42
|
+
Notes:
|
|
43
|
+
- Only update epic-level Design.md paths (e.g., `E03-board-tui-core/Design.md` → `E03-Detail.md`)
|
|
44
|
+
- Root `.savepoint/Design.md` references must NOT be changed
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: E01-tui-optimisation/T006-column-and-detail-scrolling
|
|
3
|
+
status: planned
|
|
4
|
+
objective: "Add virtual viewport scrolling to board columns and detail overlay so content adapts to terminal height"
|
|
5
|
+
depends_on:
|
|
6
|
+
- E01-tui-optimisation/T001-border-resize-fix
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# T006: Column and Detail Scroll Viewport
|
|
10
|
+
|
|
11
|
+
## Acceptance Criteria
|
|
12
|
+
|
|
13
|
+
- Each column shows only 4-5 task cards by default (adapts to terminal height) and renders `↑ N above` / `↓ N more` indicators when content extends beyond the viewport
|
|
14
|
+
- The column viewport auto-scrolls to keep the focused task visible when navigating with j/k/up/down
|
|
15
|
+
- PageUp/PageDown keys scroll a full page of cards in the focused column
|
|
16
|
+
- The detail overlay is capped at ~70% of terminal height and shows the same `↑ N above` / `↓ N more` indicators when content exceeds available space
|
|
17
|
+
- j/k/up/down keys scroll the detail overlay content when it is open
|
|
18
|
+
- All scroll indicators use dim/subtle styling matching the Atari Noir aesthetic
|
|
19
|
+
- Existing layout breakpoints (120/80 width) still function correctly with height-aware layout
|
|
20
|
+
- No visual corruption on terminal resize — viewport recalculates available height
|
|
21
|
+
- All existing tests pass; new tests cover viewport offset calculations, indicator rendering, and auto-scroll behavior
|
|
22
|
+
|
|
23
|
+
## Implementation Plan
|
|
24
|
+
|
|
25
|
+
- [ ] Add `ColumnOffsets map[data.ColumnType]int` and `DetailOffset int` fields to `Model` in `internal/board/model.go`
|
|
26
|
+
- [ ] Extend `Layout` struct in `internal/board/layout.go` with `ContentHeight int` — computed from terminal height minus header (~3), footer (~3), board border (2), column border (2)
|
|
27
|
+
- [ ] Update `CalculateLayout(width, height)` to accept height and populate `ContentHeight`
|
|
28
|
+
- [ ] Refactor `RenderColumn` in `internal/board/column.go` to accept `maxHeight` param, compute visible task slice from offset, and append scroll indicators (`↑ N above` / `↓ N more`) styled with dim/subtle text
|
|
29
|
+
- [ ] Add `ScrollIndicator` style to `internal/styles/styles.go` (dim, faint)
|
|
30
|
+
- [ ] Refactor `RenderDetail` in `internal/board/detail.go` to accept `maxHeight` param (capped at 70% terminal height), count total lines, and render only the visible slice from `DetailOffset` with scroll indicators at edges
|
|
31
|
+
- [ ] Update `view.go` — pass `layout.ContentHeight` through render chain to `renderColumn` and `RenderDetail`
|
|
32
|
+
- [ ] Update `update.go` — auto-scroll `ColumnOffsets` when focused task moves beyond visible range; handle PageUp/PageDown keybindings in both normal and detail overlay modes; add j/k scroll handling in detail overlay
|
|
33
|
+
- [ ] Add tests in `internal/board/column_test.go` for scroll indicator rendering and viewport slicing
|
|
34
|
+
- [ ] Add tests in `internal/board/layout_test.go` for height-aware `CalculateLayout` including ContentHeight computation and minimum height floors
|
|
35
|
+
- [ ] Run `make build && make test` — all gates pass
|
|
36
|
+
|
|
37
|
+
## Context Log
|
|
38
|
+
|
|
39
|
+
Files read:
|
|
40
|
+
- `internal/board/model.go`
|
|
41
|
+
- `internal/board/layout.go`
|
|
42
|
+
- `internal/board/view.go`
|
|
43
|
+
- `internal/board/column.go`
|
|
44
|
+
- `internal/board/detail.go`
|
|
45
|
+
- `internal/board/card.go`
|
|
46
|
+
- `internal/board/update.go`
|
|
47
|
+
- `internal/styles/styles.go`
|
|
48
|
+
- `internal/board/layout_test.go`
|
|
49
|
+
- `.savepoint/router.md`
|
|
50
|
+
- `.savepoint/releases/v1.1/epics/E01-tui-optimisation/Design.md`
|
|
51
|
+
|
|
52
|
+
Estimated input tokens: 3500
|
|
53
|
+
|
|
54
|
+
Notes:
|
|
55
|
+
- Pure rendering concern — no data model or card rendering changes needed
|
|
56
|
+
- `card.go` untouched — column decides which cards to show
|
|
57
|
+
- Scroll uses virtual viewport (offset tracking) not literal scrollbar glyphs
|
|
58
|
+
- Auto-scroll-to-focus means explicit scrolling is rarely needed by user
|
package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T007-next-activity-header.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: E01-tui-optimisation/T007-next-activity-header
|
|
3
|
+
status: planned
|
|
4
|
+
objective: "Show Next Activity indicator in the TUI header bar, right-aligned, displaying the current router state"
|
|
5
|
+
depends_on: []
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# T007: Next Activity Header Display
|
|
9
|
+
|
|
10
|
+
## Acceptance Criteria
|
|
11
|
+
|
|
12
|
+
- The header bar displays "Next Activity:" followed by a formatted state string on the right side
|
|
13
|
+
- When router state is `task-building`, displays "Build T{NNN} (E{NN}) v{N}" format (e.g., "Build T010 (E06) v1")
|
|
14
|
+
- When router state is `audit-pending`, displays "Audit E{NN}" format (e.g., "Audit E06")
|
|
15
|
+
- For `epic-design` state, displays "Design E{NN}" format
|
|
16
|
+
- For `epic-task-breakdown` state, displays "Plan E{NN}" format
|
|
17
|
+
- For `pre-implementation` state, displays "Planning v{N}" format
|
|
18
|
+
- The Next Activity text (excluding "Next Activity:" label) is capped at 20 characters maximum, with ellipsis truncation if exceeded
|
|
19
|
+
- The display gracefully degrades at narrow terminal widths (header text remains intact, Next Activity truncates but doesn't break layout)
|
|
20
|
+
- All existing tests pass
|
|
21
|
+
|
|
22
|
+
## Implementation Plan
|
|
23
|
+
|
|
24
|
+
- [ ] Add `HeaderRight` style to `internal/styles/styles.go` (dim foreground, right-aligned)
|
|
25
|
+
- [ ] Create `FormatNextActivity(state *data.RouterState) string` helper in `internal/board/view.go` that:
|
|
26
|
+
- Parses router state and formats compact string based on state type
|
|
27
|
+
- Truncates to max 20 chars using `xansi.Truncate` with ellipsis
|
|
28
|
+
- [ ] Update `view.go` `View()` method — modify header rendering:
|
|
29
|
+
- Split header into left (icon + "SAVEPOINT") and right sections
|
|
30
|
+
- Use `lipgloss.PlaceHorizontal` or manual string construction to position Next Activity on right
|
|
31
|
+
- Maintain existing `HeaderFrame` width and padding
|
|
32
|
+
- [ ] Update `main.go` or board initialization to read router state and populate `Model` with parsed state
|
|
33
|
+
- [ ] Add test in `internal/board/view_test.go` for Next Activity rendering at various widths
|
|
34
|
+
- [ ] Add test for `FormatNextActivity` covering all router states and truncation behavior
|
|
35
|
+
- [ ] Run `make build && make test` — all gates pass
|
|
36
|
+
|
|
37
|
+
## Context Log
|
|
38
|
+
|
|
39
|
+
Files read:
|
|
40
|
+
- `internal/board/view.go`
|
|
41
|
+
- `internal/board/model.go`
|
|
42
|
+
- `internal/styles/styles.go`
|
|
43
|
+
- `internal/data/router.go`
|
|
44
|
+
- `internal/board/view_test.go`
|
|
45
|
+
- `.savepoint/router.md`
|
|
46
|
+
- `.savepoint/releases/v1.1/epics/E01-tui-optimisation/Design.md`
|
|
47
|
+
- `.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T006-column-and-detail-scrolling.md`
|
|
48
|
+
|
|
49
|
+
Estimated input tokens: 2800
|
|
50
|
+
|
|
51
|
+
Notes:
|
|
52
|
+
- Uses existing `data.RouterState` model — no new data parsing needed
|
|
53
|
+
- Router state already available via `m.RouterTask` in Model — may need to add `*data.RouterState` field or parse separately
|
|
54
|
+
- Truncation uses same `xansi` package already imported in view.go
|
|
55
|
+
- Max 20 char constraint keeps header clean at all terminal widths
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: epic-design
|
|
3
|
+
status: planned
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Epic E02: Cross-Platform Compatibility
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
Ensure the project builds, runs, and distributes cleanly across Windows, Linux, and macOS. The codebase itself is platform-agnostic (pure Go with `filepath`), but the build tooling and release workflow need adjustments for multi-platform support.
|
|
11
|
+
|
|
12
|
+
## Definition of Done
|
|
13
|
+
|
|
14
|
+
- Makefile works identically across Windows (Git Bash/WSL), Linux, and macOS
|
|
15
|
+
- Binary builds succeed for linux-amd64, linux-arm64, darwin-amd64, darwin-arm64
|
|
16
|
+
- Smoke tests pass on at least one non-Windows target
|
|
17
|
+
- Release artifacts are produced in `dist/` with platform-specific naming
|
|
18
|
+
|
|
19
|
+
## Components and files
|
|
20
|
+
|
|
21
|
+
| Path | Purpose |
|
|
22
|
+
|------|---------|
|
|
23
|
+
| `Makefile` | Build tooling — currently uses `rm -f` (Unix-only) |
|
|
24
|
+
| `dist/` | New directory for release artifacts (platform-organized) |
|
|
25
|
+
| `build.sh` | New cross-platform build helper (optional) |
|
|
26
|
+
|
|
27
|
+
## Architectural notes
|
|
28
|
+
|
|
29
|
+
- All Go code uses `path/filepath` — no syscalls are platform-specific
|
|
30
|
+
- Bubble Tea handles terminal differences across platforms
|
|
31
|
+
- No Go build tags or platform-specific files needed
|
|
32
|
+
- This is purely a build-tooling and release-workflow epic
|
|
33
|
+
|
|
34
|
+
## Definition of Done
|
|
35
|
+
|
|
36
|
+
- [x] Makefile replaced with Go-native commands (no `rm`, `cp`, `mkdir`)
|
|
37
|
+
- [x] `dist/` directory convention established
|
|
38
|
+
- [x] `make build-all` produces binaries for linux-amd64, linux-arm64, darwin-amd64, darwin-arm64
|
|
39
|
+
- [x] `make dist` creates versioned tar/zip artifacts per platform
|
|
40
|
+
- [x] Smoke test script validates binary runs and exits cleanly
|
package/.savepoint/releases/v1.1/epics/E02-cross-platform-compatibility/tasks/T001-fix-makefile.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: E02-cross-platform-compatibility/T001-fix-makefile
|
|
3
|
+
status: planned
|
|
4
|
+
objective: "Replace Unix-only Makefile commands with Go-native cross-platform equivalents"
|
|
5
|
+
depends_on: []
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# T001: Fix Makefile for Cross-Platform Use
|
|
9
|
+
|
|
10
|
+
## Acceptance Criteria
|
|
11
|
+
|
|
12
|
+
- `make build` produces a working binary on Windows (via Git Bash), Linux, and macOS
|
|
13
|
+
- `make test` runs all tests on all three platforms
|
|
14
|
+
- `make clean` removes the binary without using `rm -f` or other Unix-only commands
|
|
15
|
+
- No shell commands in Makefile are platform-dependent
|
|
16
|
+
|
|
17
|
+
## Implementation Plan
|
|
18
|
+
|
|
19
|
+
- [ ] Read current `Makefile` and identify all Unix-specific commands
|
|
20
|
+
- [ ] Replace `rm -f savepoint` with `go clean` or a Go-native removal approach
|
|
21
|
+
- [ ] Use `go build -o savepoint main.go` directly (already portable)
|
|
22
|
+
- [ ] Test `make build && make test && make clean` works on current platform
|
|
23
|
+
- [ ] Update `AGENTS.md` Build/Test/Run section if commands changed
|
|
24
|
+
|
|
25
|
+
## Context Log
|
|
26
|
+
|
|
27
|
+
Files read:
|
|
28
|
+
- `Makefile`
|
|
29
|
+
|
|
30
|
+
Estimated input tokens: 400
|
|
31
|
+
|
|
32
|
+
Notes:
|
|
33
|
+
- Go's `go build` and `go test` are inherently cross-platform
|
|
34
|
+
- Only `rm -f` needs replacement
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: E02-cross-platform-compatibility/T002-linux-build-target
|
|
3
|
+
status: planned
|
|
4
|
+
objective: "Add Linux build targets for amd64 and arm64 architectures"
|
|
5
|
+
depends_on:
|
|
6
|
+
- E02-cross-platform-compatibility/T001-fix-makefile
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# T002: Add Linux Build Target
|
|
10
|
+
|
|
11
|
+
## Acceptance Criteria
|
|
12
|
+
|
|
13
|
+
- `make build-linux` produces linux-amd64 and linux-arm64 binaries in `dist/`
|
|
14
|
+
- Binaries have correct ELF format (can be verified via `file` command on Linux)
|
|
15
|
+
- `dist/` directory is created automatically if it doesn't exist
|
|
16
|
+
- Binaries are named `dist/linux-amd64/savepoint` and `dist/linux-arm64/savepoint`
|
|
17
|
+
|
|
18
|
+
## Implementation Plan
|
|
19
|
+
|
|
20
|
+
- [ ] Add `build-linux` Makefile target with `GOOS=linux GOARCH=amd64` and `GOOS=linux GOARCH=arm64`
|
|
21
|
+
- [ ] Ensure `dist/` directory creation is part of the build target
|
|
22
|
+
- [ ] Use `go build` with output flags to place binaries in `dist/` subdirectories
|
|
23
|
+
- [ ] Verify binaries build correctly with `make build-linux`
|
|
24
|
+
|
|
25
|
+
## Context Log
|
|
26
|
+
|
|
27
|
+
Files read:
|
|
28
|
+
- `Makefile`
|
|
29
|
+
|
|
30
|
+
Estimated input tokens: 350
|
|
31
|
+
|
|
32
|
+
Notes:
|
|
33
|
+
- Go cross-compilation is built-in — no external tooling required
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: E02-cross-platform-compatibility/T003-macos-build-target
|
|
3
|
+
status: planned
|
|
4
|
+
objective: "Add macOS build targets for amd64 and arm64 architectures"
|
|
5
|
+
depends_on:
|
|
6
|
+
- E02-cross-platform-compatibility/T001-fix-makefile
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# T003: Add macOS Build Target
|
|
10
|
+
|
|
11
|
+
## Acceptance Criteria
|
|
12
|
+
|
|
13
|
+
- `make build-darwin` produces darwin-amd64 and darwin-arm64 binaries in `dist/`
|
|
14
|
+
- Binaries have correct Mach-O format
|
|
15
|
+
- `dist/` directory is created automatically if it doesn't exist
|
|
16
|
+
- Binaries are named `dist/darwin-amd64/savepoint` and `dist/darwin-arm64/savepoint`
|
|
17
|
+
|
|
18
|
+
## Implementation Plan
|
|
19
|
+
|
|
20
|
+
- [ ] Add `build-darwin` Makefile target with `GOOS=darwin GOARCH=amd64` and `GOOS=darwin GOARCH=arm64`
|
|
21
|
+
- [ ] Ensure output goes to `dist/darwin-amd64/` and `dist/darwin-arm64/`
|
|
22
|
+
- [ ] Verify binaries build correctly with `make build-darwin`
|
|
23
|
+
|
|
24
|
+
## Context Log
|
|
25
|
+
|
|
26
|
+
Files read:
|
|
27
|
+
- `Makefile`
|
|
28
|
+
|
|
29
|
+
Estimated input tokens: 350
|
|
30
|
+
|
|
31
|
+
Notes:
|
|
32
|
+
- Mirrors T002 pattern for darwin
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: E02-cross-platform-compatibility/T004-smoke-tests-and-artifacts
|
|
3
|
+
status: planned
|
|
4
|
+
objective: "Add smoke test script and create versioned release artifacts for all platforms"
|
|
5
|
+
depends_on:
|
|
6
|
+
- E02-cross-platform-compatibility/T002-linux-build-target
|
|
7
|
+
- E02-cross-platform-compatibility/T003-macos-build-target
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# T004: Smoke Tests and Release Artifacts
|
|
11
|
+
|
|
12
|
+
## Acceptance Criteria
|
|
13
|
+
|
|
14
|
+
- `make dist` builds all platforms and creates versioned tar/zip archives in `dist/`
|
|
15
|
+
- Archive naming follows pattern: `savepoint-{version}-{platform}-{arch}.tar.gz` (or `.zip` for Windows)
|
|
16
|
+
- Smoke test validates each binary starts and exits with code 0
|
|
17
|
+
- Smoke test does not require interactive TUI (headless validation)
|
|
18
|
+
- `dist/` contains both raw binaries and packaged archives
|
|
19
|
+
|
|
20
|
+
## Implementation Plan
|
|
21
|
+
|
|
22
|
+
- [ ] Add `dist` Makefile target that invokes all `build-*` targets
|
|
23
|
+
- [ ] Create platform-appropriate archives (tar.gz for Unix, zip for Windows)
|
|
24
|
+
- [ ] Add `smoke-test` target that runs each binary with `--help` or similar non-interactive flag
|
|
25
|
+
- [ ] Add `.gitignore` entry for `dist/`
|
|
26
|
+
- [ ] Document `make dist` in AGENTS.md
|
|
27
|
+
|
|
28
|
+
## Context Log
|
|
29
|
+
|
|
30
|
+
Files read:
|
|
31
|
+
- `Makefile`
|
|
32
|
+
- `AGENTS.md`
|
|
33
|
+
|
|
34
|
+
Estimated input tokens: 500
|
|
35
|
+
|
|
36
|
+
Notes:
|
|
37
|
+
- Bubble Tea TUI may require special handling for headless smoke test
|
|
38
|
+
- May need a CLI flag or environment variable to do a non-interactive run
|
package/.savepoint/router.md
CHANGED
|
@@ -20,7 +20,7 @@ state: task-building
|
|
|
20
20
|
release: v1.1
|
|
21
21
|
epic: E01-tui-optimisation
|
|
22
22
|
task: E01-tui-optimisation/T001-border-resize-fix
|
|
23
|
-
next_action:
|
|
23
|
+
next_action: Build v1.1 E01 T001 border resize fix.
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
## State → next action
|
|
@@ -20,9 +20,9 @@ A serious digital system that loves old arcade hardware. Dark, cinematic, playfu
|
|
|
20
20
|
|
|
21
21
|
| Element | Hex | Role |
|
|
22
22
|
| ------------- | --------- | ----------------------------- |
|
|
23
|
-
| Background | `#
|
|
24
|
-
| Surface | `#
|
|
25
|
-
| Surface 2 | `#
|
|
23
|
+
| Background | `#000000` | Uniform terminal background |
|
|
24
|
+
| Surface | `#000000` | Cards and panels; same black as background |
|
|
25
|
+
| Surface 2 | `#000000` | Secondary panels / title bars; same black as background |
|
|
26
26
|
| Border | `#1A1A1A` | Quiet structural edges |
|
|
27
27
|
| Border Subtle | `#222222` | Slightly stronger separators |
|
|
28
28
|
| Primary Text | `#F0E6DA` | Warm off-white terminal text |
|
|
@@ -34,6 +34,7 @@ A serious digital system that loves old arcade hardware. Dark, cinematic, playfu
|
|
|
34
34
|
|
|
35
35
|
- **Intentional accents.** One accent color per major section/type. Use for labels, hover, glows, active text — never giant background fills.
|
|
36
36
|
- **Dark backgrounds.** Keep them dark so accents pop.
|
|
37
|
+
- **Uniform terminal black.** In the TUI, Background, Surface, and Surface 2 intentionally remain the same black value. Do not reintroduce subtly different panel background fills; hierarchy comes from spacing, dividers, glyphs, and accent borders.
|
|
37
38
|
- **Visual encoding.** Color semantically encodes categories or states; reinforce with minimal text.
|
|
38
39
|
|
|
39
40
|
## Typography
|
package/AGENTS.md
CHANGED
|
@@ -122,9 +122,9 @@ make clean # rm -f savepoint
|
|
|
122
122
|
| Module | Purpose |
|
|
123
123
|
| ------------------------------------ | ---------------------------------------------------------------------------------------------------- |
|
|
124
124
|
| `main.go` | CLI Entrypoint and root command wiring |
|
|
125
|
-
| `internal/board/` | TUI board
|
|
126
|
-
| `internal/data/` | Task
|
|
127
|
-
| `internal/styles/` |
|
|
125
|
+
| `internal/board/` | TUI board models, layout, rendering, overlays, task transitions, router priority markers, and fsnotify refresh |
|
|
126
|
+
| `internal/data/` | Task/router/config models, frontmatter parsing, checklist state parsing, mtime-guarded writes, discovery, and generic file readers |
|
|
127
|
+
| `internal/styles/` | Atari-Noir palette constants, terminal color fallbacks, shared TUI styles, semantic glyph/tag styles, and footer/header styling |
|
|
128
128
|
| `cmd/` | Additional CLI subcommands (if any) |
|
|
129
129
|
| `templates/` | Default project scaffold markdown, YAML assets, and agent prompt templates |
|
|
130
130
|
| `agent-skills/` | Custom skill guides for different agent phases (`draft-prd`, `audit`, etc.) |
|
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ Small scopes. Small context windows. No wandering.
|
|
|
48
48
|
|
|
49
49
|
Built for a cinematic, technical feel without the bloat.
|
|
50
50
|
|
|
51
|
-
- **Cinematic TUI:** Built with
|
|
51
|
+
- **Cinematic TUI:** Built with Go and Bubble Tea. It feels like a proper terminal tool, lightning-fast and dependency-free.
|
|
52
52
|
- **File-First:** Your project's state lives in markdown and JSON files right next to your code.
|
|
53
53
|
- **Agent-Agnostic:** Claude, Cursor, Aider, Gemini — if it reads markdown and edits files, it works. No MCP server. No per-agent adapters.
|
|
54
54
|
- **Token-Efficient by Design:** Tasks read <2KB of context. Audits stay under ~15KB. No more burning your AI budget on bloated backlogs.
|
package/go.mod
CHANGED
|
@@ -2,7 +2,10 @@ module github.com/opencode/savepoint
|
|
|
2
2
|
|
|
3
3
|
go 1.26.2
|
|
4
4
|
|
|
5
|
-
require
|
|
5
|
+
require (
|
|
6
|
+
github.com/charmbracelet/bubbletea v1.3.10
|
|
7
|
+
github.com/fsnotify/fsnotify v1.10.0
|
|
8
|
+
)
|
|
6
9
|
|
|
7
10
|
require (
|
|
8
11
|
github.com/atotto/clipboard v0.1.4 // indirect
|
package/go.sum
CHANGED
|
@@ -32,6 +32,8 @@ github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w
|
|
|
32
32
|
github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
|
33
33
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
|
34
34
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
|
35
|
+
github.com/fsnotify/fsnotify v1.10.0 h1:Xx/5Ydg9CeBDX/wi4VJqStNtohYjitZhhlHt4h3St1M=
|
|
36
|
+
github.com/fsnotify/fsnotify v1.10.0/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
|
35
37
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
|
36
38
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
|
37
39
|
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
|
package/internal/board/board.go
CHANGED
|
@@ -46,7 +46,6 @@ func newProjectModel(start string) (Model, error) {
|
|
|
46
46
|
|
|
47
47
|
releaseIDs := make([]string, 0, len(releases))
|
|
48
48
|
releaseEpics := make(map[string][]string, len(releases))
|
|
49
|
-
tasks := []data.Task{}
|
|
50
49
|
|
|
51
50
|
for _, release := range releases {
|
|
52
51
|
releaseIDs = append(releaseIDs, release.ID)
|
|
@@ -56,27 +55,56 @@ func newProjectModel(start string) (Model, error) {
|
|
|
56
55
|
}
|
|
57
56
|
for _, epic := range epics {
|
|
58
57
|
releaseEpics[release.ID] = append(releaseEpics[release.ID], epic.ID)
|
|
59
|
-
epicTasks, err := loadEpicTasks(d, root, release.ID, epic.ID)
|
|
60
|
-
if err != nil {
|
|
61
|
-
return Model{}, err
|
|
62
|
-
}
|
|
63
|
-
tasks = append(tasks, epicTasks...)
|
|
64
58
|
}
|
|
65
59
|
}
|
|
66
60
|
|
|
61
|
+
tasks, err := loadAllTasks(root)
|
|
62
|
+
if err != nil {
|
|
63
|
+
return Model{}, err
|
|
64
|
+
}
|
|
65
|
+
|
|
67
66
|
release := firstKnown(routerState.Release, releaseIDs)
|
|
68
67
|
epic := firstKnown(routerState.Epic, releaseEpics[release])
|
|
69
68
|
|
|
70
69
|
model := NewModel(tasks, release, epic)
|
|
71
70
|
model.Root = root
|
|
71
|
+
model.RouterTask = routerState.Task
|
|
72
72
|
model.Releases = releaseIDs
|
|
73
73
|
model.ReleaseEpics = releaseEpics
|
|
74
74
|
model.refreshEpicsForRelease()
|
|
75
75
|
model.refreshTasks()
|
|
76
76
|
|
|
77
|
+
watcher, err := newWatcher(root)
|
|
78
|
+
if err == nil {
|
|
79
|
+
model.Watcher = watcher
|
|
80
|
+
}
|
|
81
|
+
|
|
77
82
|
return model, nil
|
|
78
83
|
}
|
|
79
84
|
|
|
85
|
+
func loadAllTasks(root string) ([]data.Task, error) {
|
|
86
|
+
d := data.NewDiscover()
|
|
87
|
+
releases, err := d.ListReleases(root)
|
|
88
|
+
if err != nil {
|
|
89
|
+
return nil, err
|
|
90
|
+
}
|
|
91
|
+
var tasks []data.Task
|
|
92
|
+
for _, release := range releases {
|
|
93
|
+
epics, err := d.ListEpics(root, release.ID)
|
|
94
|
+
if err != nil {
|
|
95
|
+
return nil, err
|
|
96
|
+
}
|
|
97
|
+
for _, epic := range epics {
|
|
98
|
+
epicTasks, err := loadEpicTasks(d, root, release.ID, epic.ID)
|
|
99
|
+
if err != nil {
|
|
100
|
+
return nil, err
|
|
101
|
+
}
|
|
102
|
+
tasks = append(tasks, epicTasks...)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return tasks, nil
|
|
106
|
+
}
|
|
107
|
+
|
|
80
108
|
func readRouterState(root string) (*data.RouterState, error) {
|
|
81
109
|
content, err := os.ReadFile(filepath.Join(root, "router.md"))
|
|
82
110
|
if err != nil {
|
|
@@ -103,6 +131,14 @@ func loadEpicTasks(d *data.Discover, root, release, epic string) ([]data.Task, e
|
|
|
103
131
|
if err != nil {
|
|
104
132
|
return nil, err
|
|
105
133
|
}
|
|
134
|
+
fi, err := os.Stat(taskInfo.Path)
|
|
135
|
+
if err != nil {
|
|
136
|
+
return nil, err
|
|
137
|
+
}
|
|
138
|
+
task.Path = taskInfo.Path
|
|
139
|
+
task.Mtime = fi.ModTime()
|
|
140
|
+
task.Release = release
|
|
141
|
+
task.Epic = epic
|
|
106
142
|
tasks = append(tasks, *task)
|
|
107
143
|
}
|
|
108
144
|
return tasks, nil
|
|
@@ -68,6 +68,40 @@ next_action: "test"
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
func TestNewProjectModelUsesPathReleaseForTaskWithoutReleaseFrontmatter(t *testing.T) {
|
|
72
|
+
projectRoot := t.TempDir()
|
|
73
|
+
savepointRoot := filepath.Join(projectRoot, ".savepoint")
|
|
74
|
+
writeFile(t, filepath.Join(savepointRoot, "router.md"), `# Agent State Machine
|
|
75
|
+
|
|
76
|
+
## Current state
|
|
77
|
+
|
|
78
|
+
`+"```"+`yaml
|
|
79
|
+
state: task-building
|
|
80
|
+
release: v1.1
|
|
81
|
+
epic: E01-tui-optimisation
|
|
82
|
+
task: E01-tui-optimisation/T001-border-resize-fix
|
|
83
|
+
next_action: "test"
|
|
84
|
+
`+"```"+`
|
|
85
|
+
`)
|
|
86
|
+
writeTaskWithoutRelease(t, savepointRoot, "v1.1", "E01-tui-optimisation", "T001-border-resize-fix", data.ColumnInProgress)
|
|
87
|
+
|
|
88
|
+
model, err := newProjectModel(projectRoot)
|
|
89
|
+
if err != nil {
|
|
90
|
+
t.Fatalf("newProjectModel() error = %v", err)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if model.SelectedRelease != "v1.1" {
|
|
94
|
+
t.Errorf("SelectedRelease = %q, want v1.1", model.SelectedRelease)
|
|
95
|
+
}
|
|
96
|
+
tasks := model.Tasks[data.ColumnInProgress]
|
|
97
|
+
if len(tasks) != 1 {
|
|
98
|
+
t.Fatalf("visible in-progress tasks = %v, want one v1.1 task", tasks)
|
|
99
|
+
}
|
|
100
|
+
if tasks[0].Release != "v1.1" {
|
|
101
|
+
t.Errorf("Task.Release = %q, want v1.1", tasks[0].Release)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
71
105
|
func writeTask(t *testing.T, root, release, epic, task string, column data.ColumnType) {
|
|
72
106
|
t.Helper()
|
|
73
107
|
path := filepath.Join(root, "releases", release, "epics", epic, "tasks", task+".md")
|
|
@@ -88,6 +122,25 @@ depends_on: []
|
|
|
88
122
|
writeFile(t, path, content)
|
|
89
123
|
}
|
|
90
124
|
|
|
125
|
+
func writeTaskWithoutRelease(t *testing.T, root, release, epic, task string, column data.ColumnType) {
|
|
126
|
+
t.Helper()
|
|
127
|
+
path := filepath.Join(root, "releases", release, "epics", epic, "tasks", task+".md")
|
|
128
|
+
phase := ""
|
|
129
|
+
if column == data.ColumnInProgress {
|
|
130
|
+
phase = "phase: build\n"
|
|
131
|
+
}
|
|
132
|
+
content := `---
|
|
133
|
+
id: ` + epic + `/` + task + `
|
|
134
|
+
status: ` + string(column) + `
|
|
135
|
+
` + phase + `objective: "Test task"
|
|
136
|
+
depends_on: []
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
# Test task
|
|
140
|
+
`
|
|
141
|
+
writeFile(t, path, content)
|
|
142
|
+
}
|
|
143
|
+
|
|
91
144
|
func writeFile(t *testing.T, path, content string) {
|
|
92
145
|
t.Helper()
|
|
93
146
|
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
package/internal/board/card.go
CHANGED
|
@@ -17,16 +17,22 @@ const (
|
|
|
17
17
|
)
|
|
18
18
|
|
|
19
19
|
// RenderCard renders a task card with phase glyph, truncated ID+title, and focus styling.
|
|
20
|
-
|
|
20
|
+
// When routerTaskID matches t.ID, a green priority glyph replaces the phase glyph.
|
|
21
|
+
func RenderCard(t data.Task, width int, focused bool, routerTaskID string) string {
|
|
21
22
|
inner := width - cardOverhead
|
|
22
23
|
if inner < 2 {
|
|
23
24
|
inner = 2
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
glyph
|
|
27
|
+
var glyph string
|
|
28
|
+
if routerTaskID != "" && t.ID == routerTaskID {
|
|
29
|
+
glyph = styles.TagDone.Render(glyphBuild)
|
|
30
|
+
} else {
|
|
31
|
+
glyph = phaseGlyphStyled(t.Stage)
|
|
32
|
+
}
|
|
27
33
|
// glyph is 1 rune + 1 space prefix; leave room for "▣ "
|
|
28
34
|
idLine := fmt.Sprintf("%s %s", glyph, truncate(shortID(t.ID), inner-2))
|
|
29
|
-
titleLine := styles.CardMeta.Render(
|
|
35
|
+
titleLine := styles.CardMeta.Render(strings.Join(WrapText(t.Title, inner), "\n"))
|
|
30
36
|
|
|
31
37
|
content := idLine + "\n" + titleLine
|
|
32
38
|
|