savepoint 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +12 -1
- package/.github/workflows/ci.yml +20 -0
- package/.golangci.yml +11 -0
- package/.savepoint/Design.md +40 -38
- package/.savepoint/{audit/v1.1/E02-cross-platform-compatibility/proposals.md → releases/v1.1/epics/E02-cross-platform-compatibility/E02-Audit.md} +48 -38
- package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/E03-Audit.md +195 -0
- package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/E03-Detail.md +14 -1
- package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/tasks/T006-forced-256-color-profile.md +3 -3
- package/.savepoint/{audit/v1.1/E04-epic-navigation/proposals.md → releases/v1.1/epics/E04-epic-navigation/E04-Audit.md} +65 -54
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/E05-Audit.md +237 -0
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/E05-Detail.md +25 -16
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T001-update-agents-md.md +17 -6
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T002-update-router-md.md +15 -5
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T003-update-design-md.md +19 -5
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T004-implement-m-hotkey.md +11 -1
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T005-update-help-overlay.md +9 -6
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T006-tests-and-quality-gates.md +29 -13
- package/.savepoint/releases/v1.1/epics/E06-audit-command/E06-Audit.md +56 -0
- package/.savepoint/releases/v1.1/epics/E06-audit-command/E06-Detail.md +63 -0
- package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T005-proposals.md +44 -0
- package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T007-apply-close.md +35 -0
- package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T009-integration.md +40 -0
- package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T010-audit-file-migration.md +45 -0
- package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T011-model-tab-state.md +26 -0
- package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T012-epic-audit-render.md +33 -0
- package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T013-handle-tab-keys.md +34 -0
- package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T014-tab-indicator.md +33 -0
- package/.savepoint/releases/v1.1/epics/E07-init-command/E07-Audit.md +336 -0
- package/.savepoint/releases/v1.1/epics/E07-init-command/E07-Detail.md +61 -0
- package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T001-cli-entrypoint.md +37 -0
- package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T002-target-validation.md +28 -0
- package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T003-scaffold-writer.md +46 -0
- package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T004-atomic-writes.md +27 -0
- package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T005-magic-prompt.md +25 -0
- package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T006-clipboard.md +26 -0
- package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T007-integration-test.md +26 -0
- package/.savepoint/releases/v1.1/epics/E08-board-command/E08-Audit.md +333 -0
- package/.savepoint/releases/v1.1/epics/E08-board-command/E08-Detail.md +68 -0
- package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T001-cli-entrypoint.md +26 -0
- package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T002-non-tty-fallback.md +27 -0
- package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T003-tui-app-shell.md +28 -0
- package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T004-board-model.md +29 -0
- package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T005-detail-pane.md +27 -0
- package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T006-status-transitions.md +29 -0
- package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T007-theme-fallbacks.md +29 -0
- package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T008-integration-test.md +27 -0
- package/.savepoint/releases/v1.1/epics/E09-doctor-command/E09-Audit.md +207 -0
- package/.savepoint/releases/v1.1/epics/E09-doctor-command/E09-Detail.md +65 -0
- package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T001-cli-entrypoint.md +24 -0
- package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T002-config-router-validation.md +28 -0
- package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T003-structure-checks.md +29 -0
- package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T004-dependency-checks.md +27 -0
- package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T005-audit-orphan-checks.md +28 -0
- package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T006-quality-gates-report.md +31 -0
- package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/E11-Detail.md +36 -0
- package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/tasks/T001-debug-logging.md +25 -0
- package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/tasks/T002-increase-debounce.md +21 -0
- package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/tasks/T003-error-handling.md +22 -0
- package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/tasks/T004-test-verify.md +29 -0
- package/.savepoint/releases/v1.1/epics/E12-validation-fix/E12-Audit.md +444 -0
- package/.savepoint/releases/v1.1/epics/E12-validation-fix/E12-Detail.md +45 -0
- package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T001-default-phase.md +35 -0
- package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T002-default-status.md +19 -0
- package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T003-better-errors.md +29 -0
- package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T004-validate-on-write.md +25 -0
- package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T005-tests.md +37 -0
- package/.savepoint/releases/v1.1/epics/E13-audit-remediation/E13-Audit.md +118 -0
- package/.savepoint/releases/v1.1/epics/E13-audit-remediation/E13-Detail.md +73 -0
- package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T001-safe-cleanup.md +66 -0
- package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T002-bug-fixes.md +35 -0
- package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T003-centralize-duplication.md +60 -0
- package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T004-infrastructure.md +33 -0
- package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T005-decompose-update.md +37 -0
- package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T006-async-io.md +40 -0
- package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T007-test-coverage.md +37 -0
- package/.savepoint/releases/v1.1/epics/E14-structural-improvements/E14-Audit.md +267 -0
- package/.savepoint/releases/v1.1/epics/E14-structural-improvements/E14-Detail.md +54 -0
- package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T001-group-model.md +39 -0
- package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T002-data-interfaces.md +42 -0
- package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T003-discover-orphans.md +33 -0
- package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T004-epic-panel-headings.md +35 -0
- package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T005-shell-tokenization.md +27 -0
- package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T006-unify-enums.md +29 -0
- package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T007-testutil-package.md +28 -0
- package/.savepoint/releases/v1.1/epics/E15-hardening/E15-Audit.md +272 -0
- package/.savepoint/releases/v1.1/epics/E15-hardening/E15-Detail.md +60 -0
- package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T001-benchmarks.md +31 -0
- package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T002-fuzz-targets.md +34 -0
- package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T003-debug-flag.md +30 -0
- package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T004-dist-checksums.md +27 -0
- package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T005-windows-targets.md +28 -0
- package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T006-abbreviation-splitting.md +26 -0
- package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T007-root-test-allowlist.md +33 -0
- package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T008-ci-and-release-automation.md +46 -0
- package/.savepoint/releases/v1.1/epics/_archived/T001-cli-entrypoint.md +25 -0
- package/.savepoint/releases/v1.1/epics/_archived/T002-quality-gates.md +27 -0
- package/.savepoint/releases/v1.1/epics/_archived/T003-snapshot.md +27 -0
- package/.savepoint/releases/v1.1/epics/_archived/T004-ai-reconcile.md +29 -0
- package/.savepoint/releases/v1.1/epics/_archived/T006-tui-review.md +31 -0
- package/.savepoint/releases/v1.1/epics/_archived/T008-skip-handling.md +34 -0
- package/.savepoint/releases/v1.1/v1.1-PRD.md +67 -7
- package/.savepoint/router.md +10 -17
- package/AGENTS.md +39 -24
- package/Makefile +3 -1
- package/README.md +0 -1
- package/agent-skills/savepoint-audit/SKILL.md +86 -34
- package/agent-skills/savepoint-build-task/SKILL.md +7 -2
- package/agent-skills/savepoint-create-plan/SKILL.md +7 -2
- package/agent-skills/savepoint-create-task/SKILL.md +44 -31
- package/agent-skills/savepoint-draft-prd/SKILL.md +7 -2
- package/agent-skills/savepoint-system-design/SKILL.md +7 -2
- package/agent_skills_test.go +91 -0
- package/cmd/board.go +59 -0
- package/cmd/board_test.go +137 -0
- package/cmd/doctor.go +53 -0
- package/cmd/doctor_test.go +146 -0
- package/cmd/init.go +63 -0
- package/cmd/init_test.go +104 -0
- package/internal/board/board.go +44 -36
- package/internal/board/board_test.go +27 -82
- package/internal/board/card.go +43 -23
- package/internal/board/card_test.go +74 -5
- package/internal/board/column.go +75 -15
- package/internal/board/column_test.go +76 -2
- package/internal/board/debug.go +26 -0
- package/internal/board/debug_test.go +108 -0
- package/internal/board/detail.go +33 -47
- package/internal/board/detail_test.go +48 -0
- package/internal/board/epic_panel.go +120 -22
- package/internal/board/epic_panel_test.go +302 -17
- package/internal/board/help.go +1 -0
- package/internal/board/help_test.go +1 -0
- package/internal/board/integration_test.go +266 -0
- package/internal/board/interfaces.go +65 -0
- package/internal/board/interfaces_test.go +114 -0
- package/internal/board/io.go +93 -0
- package/internal/board/model.go +79 -118
- package/internal/board/plain.go +88 -0
- package/internal/board/plain_test.go +117 -0
- package/internal/board/release.go +1 -9
- package/internal/board/release_test.go +6 -6
- package/internal/board/status.go +4 -4
- package/internal/board/theme.go +24 -0
- package/internal/board/theme_test.go +31 -0
- package/internal/board/transitions.go +113 -88
- package/internal/board/transitions_test.go +164 -141
- package/internal/board/tui.go +32 -0
- package/internal/board/update.go +344 -215
- package/internal/board/update_test.go +326 -18
- package/internal/board/util.go +76 -0
- package/internal/board/view.go +31 -28
- package/internal/board/view_test.go +74 -2
- package/internal/board/watch.go +41 -5
- package/internal/buildtool/main.go +45 -15
- package/internal/buildtool/main_test.go +224 -0
- package/internal/data/config.go +17 -3
- package/internal/data/config_test.go +49 -0
- package/internal/data/discover.go +26 -0
- package/internal/data/discover_test.go +34 -10
- package/internal/data/errors.go +4 -0
- package/internal/data/fuzz_test.go +75 -0
- package/internal/data/lifecycle.go +13 -6
- package/internal/data/lifecycle_test.go +14 -11
- package/internal/data/parser.go +22 -6
- package/internal/data/parser_test.go +31 -7
- package/internal/data/task.go +0 -9
- package/internal/data/testdata/fuzz/FuzzSplitFrontmatterBody/68eb66b0fe91e7e3 +2 -0
- package/internal/data/write.go +88 -11
- package/internal/data/write_test.go +167 -0
- package/internal/doctor/checks.go +567 -0
- package/internal/doctor/checks_test.go +716 -0
- package/internal/doctor/gates.go +193 -0
- package/internal/doctor/gates_test.go +166 -0
- package/internal/doctor/interfaces.go +64 -0
- package/internal/doctor/interfaces_test.go +104 -0
- package/internal/doctor/repairs.go +80 -0
- package/internal/doctor/repairs_test.go +81 -0
- package/internal/doctor/report.go +157 -0
- package/internal/doctor/report_test.go +89 -0
- package/internal/init/clipboard.go +146 -0
- package/internal/init/clipboard_test.go +74 -0
- package/internal/init/install.go +16 -0
- package/internal/init/integration_test.go +197 -0
- package/internal/init/prompt.go +14 -0
- package/internal/init/prompt_test.go +77 -0
- package/internal/init/scaffold.go +59 -0
- package/internal/init/scaffold_test.go +179 -0
- package/internal/init/template_freshness_test.go +56 -0
- package/internal/init/validate.go +85 -0
- package/internal/init/validate_test.go +141 -0
- package/internal/init/write.go +73 -0
- package/internal/init/write_test.go +91 -0
- package/internal/styles/styles_test.go +133 -0
- package/internal/testutil/fixture.go +113 -0
- package/internal/testutil/fs.go +26 -0
- package/main.go +120 -4
- package/package.json +2 -2
- package/project-audit/audit_report_glm_5.1.md +411 -0
- package/project-audit/audit_report_opus_4.6.md +406 -0
- package/project-audit/consolidated-audit-report.md +456 -0
- package/templates/project/.savepoint/Design.md +2 -2
- package/templates/project/.savepoint/router.md +10 -10
- package/templates/project/AGENTS.md +33 -21
- package/templates/project/agent-skills/savepoint-audit/SKILL.md +87 -0
- package/templates/project/agent-skills/savepoint-build-task/SKILL.md +44 -0
- package/templates/project/agent-skills/savepoint-create-plan/SKILL.md +33 -0
- package/templates/project/agent-skills/savepoint-create-task/SKILL.md +44 -0
- package/templates/project/agent-skills/savepoint-draft-prd/SKILL.md +37 -0
- package/templates/project/agent-skills/savepoint-system-design/SKILL.md +38 -0
- package/templates/prompts/audit-reconciliation.prompt.md +33 -28
- package/templates/prompts/design.prompt.md +3 -1
- package/.savepoint/audit/v1/E01/proposals.md +0 -168
- package/.savepoint/audit/v1/E01/snapshot.md +0 -78
- package/.savepoint/audit/v1/E01-go-setup/proposals.md +0 -166
- package/.savepoint/audit/v1/E01-go-setup/snapshot.md +0 -71
- package/.savepoint/audit/v1/E01-scaffolding/proposals/AGENTS.md +0 -66
- package/.savepoint/audit/v1/E01-scaffolding/proposals/Design.md +0 -210
- package/.savepoint/audit/v1/E01-scaffolding/proposals/epic-Design.md +0 -117
- package/.savepoint/audit/v1/E01-scaffolding/proposals/quality-review.md +0 -101
- package/.savepoint/audit/v1/E01-scaffolding/snapshot.md +0 -54
- package/.savepoint/audit/v1/E02-data-model/snapshot.md +0 -128
- package/.savepoint/audit/v1/E02-data-readers/proposals.md +0 -123
- package/.savepoint/audit/v1/E02-data-readers/snapshot.md +0 -54
- package/.savepoint/audit/v1/E03-board-tui-core/proposals.md +0 -146
- package/.savepoint/audit/v1/E03-board-tui-core/snapshot.md +0 -57
- package/.savepoint/audit/v1/E03-cli-foundation/snapshot.md +0 -106
- package/.savepoint/audit/v1/E04-board-components/proposals.md +0 -118
- package/.savepoint/audit/v1/E04-board-components/snapshot.md +0 -77
- package/.savepoint/audit/v1/E04-templates-and-prompts/snapshot.md +0 -115
- package/.savepoint/audit/v1/E05-init-command/snapshot.md +0 -125
- package/.savepoint/audit/v1/E05-phase-transitions/proposals.md +0 -83
- package/.savepoint/audit/v1/E05-phase-transitions/snapshot.md +0 -36
- package/.savepoint/audit/v1/E06-atari-noir-layout/proposals.md +0 -130
- package/.savepoint/audit/v1/E06-atari-noir-layout/snapshot.md +0 -84
- package/.savepoint/audit/v1/E06-tui-board/snapshot.md +0 -64
- package/.savepoint/audit/v1/E07-audit-pipeline/snapshot.md +0 -165
- package/.savepoint/audit/v1/E08-board-workflow-cleanup/snapshot.md +0 -65
- package/.savepoint/audit/v1.1/E02-cross-platform-compatibility/snapshot.md +0 -41
- package/.savepoint/audit/v1.1/E04-epic-navigation/snapshot.md +0 -48
- package/ink-cli-ui-design.zip +0 -0
- package/savepoint +0 -0
- package/savepoint.exe +0 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
package board
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"os"
|
|
5
|
+
"path/filepath"
|
|
6
|
+
"strings"
|
|
7
|
+
"testing"
|
|
8
|
+
|
|
9
|
+
"github.com/opencode/savepoint/internal/data"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
func TestRenderPlainTable_warningBanner(t *testing.T) {
|
|
13
|
+
m := NewModel(nil, "", "")
|
|
14
|
+
got := RenderPlainTable(m)
|
|
15
|
+
if !strings.Contains(got, plainNonTTYWarning) {
|
|
16
|
+
t.Errorf("RenderPlainTable missing warning banner, got:\n%s", got)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
func TestRenderPlainTable_columnHeaders(t *testing.T) {
|
|
21
|
+
m := NewModel(nil, "", "")
|
|
22
|
+
got := RenderPlainTable(m)
|
|
23
|
+
for _, header := range []string{"PLANNED", "IN PROGRESS", "DONE"} {
|
|
24
|
+
if !strings.Contains(got, header) {
|
|
25
|
+
t.Errorf("RenderPlainTable missing column header %q", header)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
func TestRenderPlainTable_taskIDsAndTitles(t *testing.T) {
|
|
31
|
+
tasks := []data.Task{
|
|
32
|
+
{ID: "E08/T001", Title: "CLI entrypoint", Column: data.ColumnDone},
|
|
33
|
+
{ID: "E08/T002", Title: "Non-TTY fallback", Column: data.ColumnInProgress},
|
|
34
|
+
{ID: "E08/T003", Title: "TUI app shell", Column: data.ColumnPlanned},
|
|
35
|
+
}
|
|
36
|
+
m := NewModel(tasks, "", "")
|
|
37
|
+
got := RenderPlainTable(m)
|
|
38
|
+
|
|
39
|
+
for _, want := range []string{"E08/T001", "CLI entrypoint", "E08/T002", "Non-TTY fallback", "E08/T003", "TUI app shell"} {
|
|
40
|
+
if !strings.Contains(got, want) {
|
|
41
|
+
t.Errorf("RenderPlainTable missing %q", want)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
func TestRenderPlainTable_noneWhenColumnEmpty(t *testing.T) {
|
|
47
|
+
m := NewModel(nil, "", "")
|
|
48
|
+
got := RenderPlainTable(m)
|
|
49
|
+
if !strings.Contains(got, "(none)") {
|
|
50
|
+
t.Errorf("RenderPlainTable should show (none) for empty columns")
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
func TestRenderPlainTable_auditSignalWhenProposalsExist(t *testing.T) {
|
|
55
|
+
root := t.TempDir()
|
|
56
|
+
epicDir := filepath.Join(root, "releases", "v1", "epics", "E01-test")
|
|
57
|
+
if err := os.MkdirAll(epicDir, 0755); err != nil {
|
|
58
|
+
t.Fatal(err)
|
|
59
|
+
}
|
|
60
|
+
auditContent := "---\ntype: audit\n---\n## Main Findings\nOK\n\n## Proposed Changes\n\n### Target File\nfoo.go\n"
|
|
61
|
+
if err := os.WriteFile(filepath.Join(epicDir, "E01-Audit.md"), []byte(auditContent), 0644); err != nil {
|
|
62
|
+
t.Fatal(err)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
m := NewModel(nil, "", "")
|
|
66
|
+
m.Root = root
|
|
67
|
+
got := RenderPlainTable(m)
|
|
68
|
+
if !strings.Contains(got, plainAuditSignal) {
|
|
69
|
+
t.Errorf("RenderPlainTable missing audit signal when proposals exist, got:\n%s", got)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func TestRenderPlainTable_noAuditSignalWhenNone(t *testing.T) {
|
|
74
|
+
root := t.TempDir()
|
|
75
|
+
m := NewModel(nil, "", "")
|
|
76
|
+
m.Root = root
|
|
77
|
+
got := RenderPlainTable(m)
|
|
78
|
+
if strings.Contains(got, plainAuditSignal) {
|
|
79
|
+
t.Errorf("RenderPlainTable should not show audit signal when no proposals exist")
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
func TestHasAuditProposals_detectsSection(t *testing.T) {
|
|
84
|
+
root := t.TempDir()
|
|
85
|
+
epicDir := filepath.Join(root, "releases", "v1", "epics", "E02-slug")
|
|
86
|
+
if err := os.MkdirAll(epicDir, 0755); err != nil {
|
|
87
|
+
t.Fatal(err)
|
|
88
|
+
}
|
|
89
|
+
content := "## Main Findings\nAll good.\n\n## Proposed Changes\n\n### Target File\nbar.go\n"
|
|
90
|
+
if err := os.WriteFile(filepath.Join(epicDir, "E02-Audit.md"), []byte(content), 0644); err != nil {
|
|
91
|
+
t.Fatal(err)
|
|
92
|
+
}
|
|
93
|
+
if !hasAuditProposals(root) {
|
|
94
|
+
t.Error("hasAuditProposals should return true when Proposed Changes section exists")
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
func TestHasAuditProposals_noProposals(t *testing.T) {
|
|
99
|
+
root := t.TempDir()
|
|
100
|
+
epicDir := filepath.Join(root, "releases", "v1", "epics", "E03-slug")
|
|
101
|
+
if err := os.MkdirAll(epicDir, 0755); err != nil {
|
|
102
|
+
t.Fatal(err)
|
|
103
|
+
}
|
|
104
|
+
content := "## Main Findings\nAll good.\n"
|
|
105
|
+
if err := os.WriteFile(filepath.Join(epicDir, "E03-Audit.md"), []byte(content), 0644); err != nil {
|
|
106
|
+
t.Fatal(err)
|
|
107
|
+
}
|
|
108
|
+
if hasAuditProposals(root) {
|
|
109
|
+
t.Error("hasAuditProposals should return false when no Proposed Changes section")
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
func TestHasAuditProposals_missingRoot(t *testing.T) {
|
|
114
|
+
if hasAuditProposals("/nonexistent/path/xyz") {
|
|
115
|
+
t.Error("hasAuditProposals should return false for missing root")
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -31,12 +31,4 @@ func RenderReleaseDropdown(releases []string, cursor int, width int) string {
|
|
|
31
31
|
return styles.EpicPanel.Width(width).Render(strings.Join(lines, "\n"))
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
func releaseIndex(releases []string, selected string) int {
|
|
36
|
-
for i, r := range releases {
|
|
37
|
-
if r == selected {
|
|
38
|
-
return i
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return 0
|
|
42
|
-
}
|
|
34
|
+
|
|
@@ -40,17 +40,17 @@ func TestRenderReleaseDropdown_hintsPresent(t *testing.T) {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
func
|
|
43
|
+
func TestSliceIndexRelease_found(t *testing.T) {
|
|
44
44
|
releases := []string{"v1", "v2", "v3"}
|
|
45
|
-
if got :=
|
|
46
|
-
t.Errorf("
|
|
45
|
+
if got := sliceIndex(releases, "v2"); got != 1 {
|
|
46
|
+
t.Errorf("sliceIndex = %d, want 1", got)
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
func
|
|
50
|
+
func TestSliceIndexRelease_notFound(t *testing.T) {
|
|
51
51
|
releases := []string{"v1", "v2"}
|
|
52
|
-
if got :=
|
|
53
|
-
t.Errorf("
|
|
52
|
+
if got := sliceIndex(releases, "v9"); got != 0 {
|
|
53
|
+
t.Errorf("sliceIndex = %d, want 0", got)
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
|
package/internal/board/status.go
CHANGED
|
@@ -9,13 +9,13 @@ const statusGlyphDefault = " "
|
|
|
9
9
|
|
|
10
10
|
func statusGlyph(status string) string {
|
|
11
11
|
switch status {
|
|
12
|
-
case string(data.
|
|
12
|
+
case string(data.ColumnPlanned):
|
|
13
13
|
return styles.CardMeta.Render("○")
|
|
14
|
-
case string(data.
|
|
14
|
+
case string(data.ColumnInProgress):
|
|
15
15
|
return styles.GlyphBuild.Render("▶")
|
|
16
|
-
case string(data.
|
|
16
|
+
case string(data.ColumnDone):
|
|
17
17
|
return styles.TagDone.Render("◉")
|
|
18
|
-
case
|
|
18
|
+
case "audited":
|
|
19
19
|
return styles.TagDone.Render("✓")
|
|
20
20
|
default:
|
|
21
21
|
return statusGlyphDefault
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
package board
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"os"
|
|
5
|
+
|
|
6
|
+
"github.com/charmbracelet/lipgloss"
|
|
7
|
+
"github.com/muesli/termenv"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// applyColorProfile detects terminal color support and configures lipgloss.
|
|
11
|
+
// Priority: NO_COLOR → COLORTERM env hint → termenv auto-detect.
|
|
12
|
+
func applyColorProfile() {
|
|
13
|
+
lipgloss.SetColorProfile(detectProfile())
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
func detectProfile() termenv.Profile {
|
|
17
|
+
if os.Getenv("NO_COLOR") != "" {
|
|
18
|
+
return termenv.Ascii
|
|
19
|
+
}
|
|
20
|
+
if c := os.Getenv("COLORTERM"); c == "truecolor" || c == "24bit" {
|
|
21
|
+
return termenv.TrueColor
|
|
22
|
+
}
|
|
23
|
+
return termenv.ColorProfile()
|
|
24
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
package board
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"testing"
|
|
5
|
+
|
|
6
|
+
"github.com/muesli/termenv"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
func TestDetectProfileNOCOLOR(t *testing.T) {
|
|
10
|
+
t.Setenv("NO_COLOR", "1")
|
|
11
|
+
t.Setenv("COLORTERM", "")
|
|
12
|
+
if got := detectProfile(); got != termenv.Ascii {
|
|
13
|
+
t.Fatalf("want Ascii, got %v", got)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
func TestDetectProfileTruecolor(t *testing.T) {
|
|
18
|
+
t.Setenv("NO_COLOR", "")
|
|
19
|
+
t.Setenv("COLORTERM", "truecolor")
|
|
20
|
+
if got := detectProfile(); got != termenv.TrueColor {
|
|
21
|
+
t.Fatalf("want TrueColor, got %v", got)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
func TestDetectProfile24bit(t *testing.T) {
|
|
26
|
+
t.Setenv("NO_COLOR", "")
|
|
27
|
+
t.Setenv("COLORTERM", "24bit")
|
|
28
|
+
if got := detectProfile(); got != termenv.TrueColor {
|
|
29
|
+
t.Fatalf("want TrueColor, got %v", got)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -1,88 +1,113 @@
|
|
|
1
|
-
package board
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"fmt"
|
|
5
|
-
|
|
6
|
-
"github.com/opencode/savepoint/internal/data"
|
|
7
|
-
)
|
|
8
|
-
|
|
9
|
-
// Advance moves a task forward through the phase lifecycle.
|
|
10
|
-
func Advance(t *data.Task) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
case data.
|
|
22
|
-
t.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
1
|
+
package board
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
|
|
6
|
+
"github.com/opencode/savepoint/internal/data"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
// Advance moves a task forward through the phase lifecycle.
|
|
10
|
+
func Advance(t *data.Task) {
|
|
11
|
+
stage := t.Stage
|
|
12
|
+
if stage == "" {
|
|
13
|
+
stage = data.StageBuild
|
|
14
|
+
}
|
|
15
|
+
switch t.Column {
|
|
16
|
+
case data.ColumnPlanned:
|
|
17
|
+
t.Column = data.ColumnInProgress
|
|
18
|
+
t.Stage = data.StageBuild
|
|
19
|
+
case data.ColumnInProgress:
|
|
20
|
+
switch stage {
|
|
21
|
+
case data.StageBuild:
|
|
22
|
+
t.Stage = data.StageTest
|
|
23
|
+
case data.StageTest:
|
|
24
|
+
t.Stage = data.StageAudit
|
|
25
|
+
case data.StageAudit:
|
|
26
|
+
t.Column = data.ColumnDone
|
|
27
|
+
t.Stage = ""
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
t.Status = string(t.Column)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Retreat moves a task backward through the phase lifecycle.
|
|
34
|
+
func Retreat(t *data.Task) {
|
|
35
|
+
stage := t.Stage
|
|
36
|
+
if stage == "" {
|
|
37
|
+
stage = data.StageBuild
|
|
38
|
+
}
|
|
39
|
+
switch t.Column {
|
|
40
|
+
case data.ColumnDone:
|
|
41
|
+
t.Column = data.ColumnInProgress
|
|
42
|
+
t.Stage = data.StageAudit
|
|
43
|
+
case data.ColumnInProgress:
|
|
44
|
+
switch stage {
|
|
45
|
+
case data.StageAudit:
|
|
46
|
+
t.Stage = data.StageTest
|
|
47
|
+
case data.StageTest:
|
|
48
|
+
t.Stage = data.StageBuild
|
|
49
|
+
case data.StageBuild:
|
|
50
|
+
t.Column = data.ColumnPlanned
|
|
51
|
+
t.Stage = ""
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
t.Status = string(t.Column)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
func taskTransitionMessage(prefix string, task data.Task) string {
|
|
58
|
+
if task.Column == data.ColumnInProgress {
|
|
59
|
+
return fmt.Sprintf("%s %s to %s", prefix, shortID(task.ID), task.Stage)
|
|
60
|
+
}
|
|
61
|
+
return fmt.Sprintf("%s %s to %s", prefix, shortID(task.ID), task.Column)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// CanAdvance checks whether a task is allowed to advance to its next phase.
|
|
65
|
+
// It validates phase adjacency and dependency completion.
|
|
66
|
+
// Returns (true, "") if allowed, or (false, reason) if blocked.
|
|
67
|
+
func CanAdvance(t *data.Task, allTasks []data.Task) (bool, string) {
|
|
68
|
+
switch t.Column {
|
|
69
|
+
case data.ColumnPlanned:
|
|
70
|
+
return dependenciesDone(t, allTasks)
|
|
71
|
+
case data.ColumnInProgress:
|
|
72
|
+
stage := t.Stage
|
|
73
|
+
if stage == "" {
|
|
74
|
+
stage = data.StageBuild
|
|
75
|
+
}
|
|
76
|
+
switch stage {
|
|
77
|
+
case data.StageBuild:
|
|
78
|
+
return true, ""
|
|
79
|
+
case data.StageTest:
|
|
80
|
+
return true, ""
|
|
81
|
+
case data.StageAudit:
|
|
82
|
+
return dependenciesDone(t, allTasks)
|
|
83
|
+
default:
|
|
84
|
+
return false, fmt.Sprintf("unknown stage %q", stage)
|
|
85
|
+
}
|
|
86
|
+
case data.ColumnDone:
|
|
87
|
+
return false, "task is already done"
|
|
88
|
+
default:
|
|
89
|
+
return false, fmt.Sprintf("unknown column %q", t.Column)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
func dependenciesDone(t *data.Task, allTasks []data.Task) (bool, string) {
|
|
94
|
+
for _, depID := range t.DependsOn {
|
|
95
|
+
dep := findTask(depID, allTasks)
|
|
96
|
+
if dep == nil {
|
|
97
|
+
return false, fmt.Sprintf("dependency %q not found", depID)
|
|
98
|
+
}
|
|
99
|
+
if dep.Column != data.ColumnDone {
|
|
100
|
+
return false, fmt.Sprintf("dependency %q is not done", depID)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return true, ""
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
func findTask(id string, tasks []data.Task) *data.Task {
|
|
107
|
+
for i := range tasks {
|
|
108
|
+
if tasks[i].ID == id {
|
|
109
|
+
return &tasks[i]
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return nil
|
|
113
|
+
}
|