savepoint 1.0.0 → 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 +8 -1
- package/.savepoint/Design.md +26 -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/v1/E06-atari-noir-layout/proposals.md +130 -0
- package/.savepoint/audit/v1/E06-atari-noir-layout/snapshot.md +84 -0
- 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/config.yml +3 -3
- 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/E06-Detail.md +62 -0
- 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/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 +27 -22
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/E01-Detail.md +40 -0
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T001-next-activity-header.md +56 -0
- 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 +51 -0
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T005-update-cross-references.md +45 -0
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T006-column-and-detail-scrolling.md +68 -0
- 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/E02-Detail.md +49 -0
- package/.savepoint/releases/v1.1/epics/E02-cross-platform-compatibility/tasks/T001-fix-makefile.md +37 -0
- package/.savepoint/releases/v1.1/epics/E02-cross-platform-compatibility/tasks/T002-linux-build-target.md +38 -0
- package/.savepoint/releases/v1.1/epics/E02-cross-platform-compatibility/tasks/T003-macos-build-target.md +36 -0
- package/.savepoint/releases/v1.1/epics/E02-cross-platform-compatibility/tasks/T004-smoke-tests-and-artifacts.md +59 -0
- 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/.savepoint/visual-identity.md +4 -3
- package/AGENTS.md +56 -113
- package/Makefile +19 -3
- package/README.md +7 -6
- 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/go.mod +4 -1
- package/go.sum +2 -0
- package/internal/board/board.go +66 -14
- package/internal/board/board_test.go +124 -0
- package/internal/board/card.go +40 -3
- package/internal/board/card_test.go +121 -14
- package/internal/board/column.go +40 -5
- package/internal/board/column_test.go +65 -10
- package/internal/board/detail.go +115 -23
- package/internal/board/detail_test.go +132 -25
- 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 +146 -23
- package/internal/board/render_policy_test.go +77 -0
- package/internal/board/status.go +23 -0
- package/internal/board/update.go +300 -9
- package/internal/board/update_test.go +166 -0
- package/internal/board/view.go +141 -17
- package/internal/board/view_test.go +161 -3
- package/internal/board/watch.go +100 -0
- package/internal/buildtool/main.go +219 -0
- package/internal/data/parser.go +39 -1
- package/internal/data/parser_test.go +43 -2
- package/internal/data/task.go +22 -2
- package/internal/styles/palette.go +9 -7
- package/internal/styles/styles.go +42 -25
- package/main.go +9 -0
- package/package.json +5 -4
- 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/epics/E06-atari-noir-layout/Design.md +0 -42
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/Design.md +0 -26
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T001-border-resize-fix.md +0 -35
- 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-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
package/Makefile
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
.PHONY: build test run clean
|
|
1
|
+
.PHONY: build test run clean build-linux build-darwin build-all dist smoke-test
|
|
2
|
+
|
|
3
|
+
VERSION ?=
|
|
2
4
|
|
|
3
5
|
build:
|
|
4
|
-
go
|
|
6
|
+
go run ./internal/buildtool -version "$(VERSION)" build
|
|
5
7
|
|
|
6
8
|
test:
|
|
7
9
|
go test ./...
|
|
@@ -10,4 +12,18 @@ run:
|
|
|
10
12
|
go run main.go
|
|
11
13
|
|
|
12
14
|
clean:
|
|
13
|
-
|
|
15
|
+
go run ./internal/buildtool -version "$(VERSION)" clean
|
|
16
|
+
|
|
17
|
+
build-linux:
|
|
18
|
+
go run ./internal/buildtool -version "$(VERSION)" build-linux
|
|
19
|
+
|
|
20
|
+
build-darwin:
|
|
21
|
+
go run ./internal/buildtool -version "$(VERSION)" build-darwin
|
|
22
|
+
|
|
23
|
+
build-all: build-linux build-darwin
|
|
24
|
+
|
|
25
|
+
dist:
|
|
26
|
+
go run ./internal/buildtool -version "$(VERSION)" dist
|
|
27
|
+
|
|
28
|
+
smoke-test:
|
|
29
|
+
go run ./internal/buildtool -version "$(VERSION)" smoke-test
|
package/README.md
CHANGED
|
@@ -34,11 +34,11 @@ Savepoint turns your project into a series of hard gates, enforced by **six bund
|
|
|
34
34
|
|
|
35
35
|
Small scopes. Small context windows. No wandering.
|
|
36
36
|
|
|
37
|
-
- **The PRD Gate (`draft-prd`):** The agent interviews you to ensure your idea is crisp enough for a V1 before writing any architecture.
|
|
38
|
-
- **The Design Gate (`system-design`):** Write down what you actually want. If you can't explain it simply in a markdown file, the AI is going to make a mess of it.
|
|
39
|
-
- **The Plan Gate (`create-plan` & `create-task`):** Break the idea down into small, manageable steps. No giant leaps. This becomes the checklist the AI _must_ follow.
|
|
40
|
-
- **The Build Gate (`build-task`):** The AI writes code for one small step at a time. The scope stays tight so it doesn't wander off into the weeds. It logs "Drift Notes" instead of cowboy-coding architectural changes.
|
|
41
|
-
- **The Audit Gate (`audit`):** Before moving on, we stop and check. Does the code match the plan? We don't advance until the map matches the territory.
|
|
37
|
+
- **The PRD Gate (`savepoint-draft-prd`):** The agent interviews you to ensure your idea is crisp enough for a V1 before writing any architecture.
|
|
38
|
+
- **The Design Gate (`savepoint-system-design`):** Write down what you actually want. If you can't explain it simply in a markdown file, the AI is going to make a mess of it.
|
|
39
|
+
- **The Plan Gate (`savepoint-create-plan` & `savepoint-create-task`):** Break the idea down into small, manageable steps. No giant leaps. This becomes the checklist the AI _must_ follow.
|
|
40
|
+
- **The Build Gate (`savepoint-build-task`):** The AI writes code for one small step at a time. The scope stays tight so it doesn't wander off into the weeds. It logs "Drift Notes" instead of cowboy-coding architectural changes.
|
|
41
|
+
- **The Audit Gate (`savepoint-audit`):** Before moving on, we stop and check. Does the code match the plan? We don't advance until the map matches the territory.
|
|
42
42
|
|
|
43
43
|
> 🔒 **The Audit Loop** — When the last task in an epic moves to `done`, the next epic stays _locked_ until your docs (`Design.md`, `AGENTS.md`, and the epic's own design) are reconciled with the actual code via the audit agent. **No existing markdown-first task tool has this gate.**
|
|
44
44
|
|
|
@@ -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.
|
|
@@ -76,3 +76,4 @@ I’m sharing it to prove a point: The real power of AI isn't just the size of t
|
|
|
76
76
|
|
|
77
77
|
**License:** MIT
|
|
78
78
|
**Status:** Recursive Construction (v1 MVP in progress)
|
|
79
|
+
us:** Recursive Construction (v1 MVP in progress)
|
|
@@ -10,8 +10,8 @@ The Audit Gate is Savepoint's wedge. It prevents projects from degrading into ch
|
|
|
10
10
|
This skill is activated when the `.savepoint/router.md` state is `audit-pending`.
|
|
11
11
|
|
|
12
12
|
## Input
|
|
13
|
-
- `.savepoint/audit/{E##-slug}/snapshot.md` (what changed).
|
|
14
|
-
- `.savepoint/releases/
|
|
13
|
+
- `.savepoint/audit/{release}/{E##-slug}/snapshot.md` (what changed).
|
|
14
|
+
- `.savepoint/releases/{release}/epics/{E##-slug}/E##-Detail.md` (the epic design).
|
|
15
15
|
- `.savepoint/Design.md` (project architecture).
|
|
16
16
|
- `AGENTS.md` (agent guide and codebase map).
|
|
17
17
|
- The source and test files modified during the Epic.
|
|
@@ -21,15 +21,15 @@ This skill is activated when the `.savepoint/router.md` state is `audit-pending`
|
|
|
21
21
|
1. **Fresh Eyes Check:** If you are the exact same agent session that just built the `build-task` code for this Epic, you MUST STOP. Tell the user: "Epic complete. Start a new agent session for the audit."
|
|
22
22
|
2. **Verify ACs:** Review the completed tasks for the Epic. Ensure the Acceptance Criteria were actually met by the committed code.
|
|
23
23
|
3. **Process Drift Notes (Reconciliation):** Read every task file in the Epic and look for `## Drift Notes`.
|
|
24
|
-
4. **Draft Proposals:** Based on the code changes and the Drift Notes, write exactly ONE file: `.savepoint/audit/{E##-slug}/proposals.md`. It must contain:
|
|
24
|
+
4. **Draft Proposals:** Based on the code changes and the Drift Notes, write exactly ONE file: `.savepoint/audit/{release}/{E##-slug}/proposals.md`. It must contain:
|
|
25
25
|
* **Design.md section:** Propose updates to merge the epic's architectural changes into the project-level `Design.md`.
|
|
26
26
|
* **AGENTS.md section:** Propose updates to refresh the Codebase Map table with new or changed modules.
|
|
27
|
-
* **Epic-
|
|
27
|
+
* **Epic-E##-Detail.md section:** Add "Implemented as:" notes showing where reality deviated from the original plan.
|
|
28
28
|
* **Quality Review section:** List any minor code-style infractions that must be fixed before the next Epic.
|
|
29
29
|
5. **Review Format:** Use `## Target File`, `## Replace`, and `## With` formatting in the proposals document so it is easy for a human (or an agent) to apply later.
|
|
30
|
-
6. **Handoff:** Do not apply the proposals yourself. Do not mark the epic audited. Stop and prompt the user to review `.savepoint/audit/{E##-slug}/proposals.md` (often via the TUI). Once the user approves, the proposals are applied, and the router moves to the next Epic.
|
|
30
|
+
6. **Handoff:** Do not apply the proposals yourself. Do not mark the epic audited. Stop and prompt the user to review `.savepoint/audit/{release}/{E##-slug}/proposals.md` (often via the TUI). Once the user approves, the proposals are applied, and the router moves to the next Epic.
|
|
31
31
|
|
|
32
32
|
## Constraints
|
|
33
33
|
- **Do not write product code.** You are an auditor.
|
|
34
34
|
- **Do not apply the changes immediately.** Write the proposals document first.
|
|
35
|
-
- **One proposals file.** Do not create multiple proposal files.
|
|
35
|
+
- **One proposals file.** Do not create multiple proposal files.
|
|
@@ -7,11 +7,11 @@ Act as a disciplined coding agent that strictly follows Savepoint's implementati
|
|
|
7
7
|
The `build-task` skill is the execution engine. It reads the detailed task plan, writes the code, and proves that the Acceptance Criteria (ACs) have been met. It is strictly constrained to the scope of the single active task. It does not rewrite architecture, and it does not fix unrelated bugs.
|
|
8
8
|
|
|
9
9
|
## Trigger
|
|
10
|
-
This skill is activated when the `.savepoint/router.md` state is `
|
|
10
|
+
This skill is activated when the `.savepoint/router.md` state is `task-building` and points to a specific task file.
|
|
11
11
|
|
|
12
12
|
## Input
|
|
13
13
|
- `.savepoint/router.md` (Current state).
|
|
14
|
-
- The active epic
|
|
14
|
+
- The active epic E##-Detail.md: `.savepoint/releases/v1/epics/{E##-epic}/E##-Detail.md`.
|
|
15
15
|
- The active task file: `.savepoint/releases/v1/epics/{E##-epic}/tasks/{T###}-*.md`.
|
|
16
16
|
- Directly touched source/test files.
|
|
17
17
|
|
|
@@ -7,18 +7,18 @@ Turn the Product Requirements Document (PRD) and the architectural `Design.md` i
|
|
|
7
7
|
Savepoint enforces small scopes to prevent AI agents from "wandering." The `create-plan` skill acts as the Technical Project Manager. It takes the grand vision and the architectural blueprint and slices it into a sequence of achievable, testable milestones (Epics).
|
|
8
8
|
|
|
9
9
|
## Trigger
|
|
10
|
-
This skill is activated when the `.savepoint/router.md` state is `
|
|
10
|
+
This skill is activated when the `.savepoint/router.md` state is `pre-implementation`.
|
|
11
11
|
|
|
12
12
|
## Input
|
|
13
13
|
- `.savepoint/PRD.md` (Vision and constraints)
|
|
14
14
|
- `.savepoint/Design.md` (Architecture and layout)
|
|
15
|
-
- `.savepoint/releases/v1/PRD.md` (Optional: Release-scoped PRD)
|
|
15
|
+
- `.savepoint/releases/v1/v1-PRD.md` (Optional: Release-scoped PRD)
|
|
16
16
|
|
|
17
17
|
## Workflow
|
|
18
18
|
|
|
19
19
|
1. **Read the Context:** Consume the PRD and Design documents to understand the scope and technical constraints.
|
|
20
20
|
2. **Define Epics:** Group the work into high-level features or milestones. Name them clearly (e.g., `E01-scaffolding`, `E02-database`, `E03-auth`). Each Epic must represent a deliverable slice of value.
|
|
21
|
-
3. **Draft Epic Designs:** For each Epic, create a shell `
|
|
21
|
+
3. **Draft Epic Designs:** For each Epic, create a shell `E##-Detail.md` inside `.savepoint/releases/v1/epics/{E##-epic-name}/E##-Detail.md`. This file should describe the *delta* (what this specific epic adds to the overall architecture).
|
|
22
22
|
4. **Breakdown Tasks (High Level):** Inside each Epic folder, list out the high-level tasks required to complete it. Do not write full implementation plans yet—just identify the discrete chunks of work (e.g., `T001-setup-repo.md`, `T002-init-db.md`).
|
|
23
23
|
5. **Order and Dependency:** Ensure the Epics and tasks are ordered logically. You cannot build the frontend auth UI (E03) before the database models (E02) exist.
|
|
24
24
|
6. **Handoff:** Update `.savepoint/router.md` to `state: task-breakdown` and point it at the first Epic. Prompt the user to review the Epic list.
|
|
@@ -7,10 +7,10 @@ Take high-level tasks identified during the planning phase and build detailed, a
|
|
|
7
7
|
A task plan is a contract between the planner and the builder. If the task plan is vague, the resulting code will be buggy. The `create-task` skill acts as a Senior Engineer writing tickets for a Junior Developer (the `build-task` agent). It must define exactly *what* constitutes success and *how* to achieve it, without actually writing the code.
|
|
8
8
|
|
|
9
9
|
## Trigger
|
|
10
|
-
This skill is activated when the `.savepoint/router.md` state is `task-breakdown` and the router points to a specific
|
|
10
|
+
This skill is activated when the `.savepoint/router.md` state is `epic-task-breakdown` and the router points to a specific epic.
|
|
11
11
|
|
|
12
12
|
## Input
|
|
13
|
-
- `.savepoint/releases/v1/epics/{E##-epic}/
|
|
13
|
+
- `.savepoint/releases/v1/epics/{E##-epic}/E##-Detail.md` (The active Epic's design).
|
|
14
14
|
- The high-level task markdown file (e.g., `.savepoint/releases/v1/epics/{E##-epic}/tasks/T001-slug.md`).
|
|
15
15
|
|
|
16
16
|
## Workflow
|
|
@@ -7,7 +7,7 @@ Help the user write a structured, sufficiently detailed Product Requirements Doc
|
|
|
7
7
|
In the Savepoint workflow, the PRD is the absolute source of truth. If the PRD is a vague brain-dump, the resulting architecture and code will be a mess. Your job as the `draft-prd` agent is to act as a strict Product Manager. You do not write code. You interrogate the user's idea until it is crisp enough to build a V1.
|
|
8
8
|
|
|
9
9
|
## Trigger
|
|
10
|
-
This skill is activated when the `.savepoint/router.md` state is `
|
|
10
|
+
This skill is activated when the `.savepoint/router.md` state is `pre-implementation` or when the user explicitly asks you to help them write or refine their PRD.
|
|
11
11
|
|
|
12
12
|
## Input
|
|
13
13
|
- The user's initial idea, brain-dump, or `.txt` file outline.
|
|
@@ -7,7 +7,7 @@ Translate the Product Requirements Document (PRD) into the initial architectural
|
|
|
7
7
|
Before any tasks can be planned, the project needs a high-level technical direction. The `system-design` skill acts as the Staff Engineer. It reads the vision and constraints from the PRD and makes authoritative technical decisions regarding architecture, directory layout, dependency strategies, and workflow rules.
|
|
8
8
|
|
|
9
9
|
## Trigger
|
|
10
|
-
This skill is activated when the `.savepoint/router.md` state is `design` or when the user explicitly asks to design the system based on the PRD.
|
|
10
|
+
This skill is activated when the `.savepoint/router.md` state is `epic-design` or when the user explicitly asks to design the system based on the PRD.
|
|
11
11
|
|
|
12
12
|
## Input
|
|
13
13
|
- `.savepoint/PRD.md` (The source of truth for "what" and "why").
|
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
|
@@ -6,10 +6,14 @@ import (
|
|
|
6
6
|
"path/filepath"
|
|
7
7
|
|
|
8
8
|
tea "github.com/charmbracelet/bubbletea"
|
|
9
|
+
"github.com/charmbracelet/lipgloss"
|
|
10
|
+
"github.com/muesli/termenv"
|
|
9
11
|
"github.com/opencode/savepoint/internal/data"
|
|
10
12
|
)
|
|
11
13
|
|
|
12
14
|
func Run() error {
|
|
15
|
+
lipgloss.SetColorProfile(termenv.ANSI256)
|
|
16
|
+
|
|
13
17
|
model, err := newProjectModel(".")
|
|
14
18
|
if err != nil {
|
|
15
19
|
return err
|
|
@@ -39,42 +43,77 @@ func newProjectModel(start string) (Model, error) {
|
|
|
39
43
|
return Model{}, err
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
|
|
46
|
+
tasks, releaseIDs, releaseEpics, epicStatuses, err := loadBoardData(root)
|
|
47
|
+
if err != nil {
|
|
48
|
+
return Model{}, err
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
release := firstKnown(routerState.Release, releaseIDs)
|
|
52
|
+
epic := firstKnown(routerState.Epic, releaseEpics[release])
|
|
53
|
+
|
|
54
|
+
model := NewModel(tasks, release, epic)
|
|
55
|
+
model.Root = root
|
|
56
|
+
model.RouterTask = routerState.Task
|
|
57
|
+
model.RouterState = routerState
|
|
58
|
+
model.Releases = releaseIDs
|
|
59
|
+
model.ReleaseEpics = releaseEpics
|
|
60
|
+
model.EpicStatus = epicStatuses
|
|
61
|
+
model.refreshEpicsForRelease()
|
|
62
|
+
model.refreshTasks()
|
|
63
|
+
|
|
64
|
+
watcher, err := newWatcher(root)
|
|
43
65
|
if err != nil {
|
|
44
66
|
return Model{}, err
|
|
45
67
|
}
|
|
68
|
+
model.Watcher = watcher
|
|
69
|
+
|
|
70
|
+
return model, nil
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func loadBoardData(root string) ([]data.Task, []string, map[string][]string, map[string]string, error) {
|
|
74
|
+
d := data.NewDiscover()
|
|
75
|
+
releases, err := d.ListReleases(root)
|
|
76
|
+
if err != nil {
|
|
77
|
+
return nil, nil, nil, nil, err
|
|
78
|
+
}
|
|
46
79
|
|
|
47
80
|
releaseIDs := make([]string, 0, len(releases))
|
|
48
81
|
releaseEpics := make(map[string][]string, len(releases))
|
|
49
|
-
tasks
|
|
82
|
+
var tasks []data.Task
|
|
83
|
+
epicStatuses := make(map[string]string)
|
|
50
84
|
|
|
51
85
|
for _, release := range releases {
|
|
52
86
|
releaseIDs = append(releaseIDs, release.ID)
|
|
53
87
|
epics, err := d.ListEpics(root, release.ID)
|
|
54
88
|
if err != nil {
|
|
55
|
-
return
|
|
89
|
+
return nil, nil, nil, nil, err
|
|
56
90
|
}
|
|
57
91
|
for _, epic := range epics {
|
|
58
92
|
releaseEpics[release.ID] = append(releaseEpics[release.ID], epic.ID)
|
|
59
93
|
epicTasks, err := loadEpicTasks(d, root, release.ID, epic.ID)
|
|
60
94
|
if err != nil {
|
|
61
|
-
return
|
|
95
|
+
return nil, nil, nil, nil, err
|
|
62
96
|
}
|
|
63
97
|
tasks = append(tasks, epicTasks...)
|
|
98
|
+
|
|
99
|
+
detailPath := filepath.Join(epic.Path, shortID(epic.ID)+"-Detail.md")
|
|
100
|
+
if raw, err := os.ReadFile(detailPath); err == nil {
|
|
101
|
+
parser := data.NewParser()
|
|
102
|
+
if fm, err := parser.ParseFrontmatter(string(raw)); err == nil {
|
|
103
|
+
if status, ok := fm["status"].(string); ok {
|
|
104
|
+
epicStatuses[epic.ID] = status
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
64
108
|
}
|
|
65
109
|
}
|
|
66
110
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
model := NewModel(tasks, release, epic)
|
|
71
|
-
model.Root = root
|
|
72
|
-
model.Releases = releaseIDs
|
|
73
|
-
model.ReleaseEpics = releaseEpics
|
|
74
|
-
model.refreshEpicsForRelease()
|
|
75
|
-
model.refreshTasks()
|
|
111
|
+
return tasks, releaseIDs, releaseEpics, epicStatuses, nil
|
|
112
|
+
}
|
|
76
113
|
|
|
77
|
-
|
|
114
|
+
func loadAllTasks(root string) ([]data.Task, error) {
|
|
115
|
+
tasks, _, _, _, err := loadBoardData(root)
|
|
116
|
+
return tasks, err
|
|
78
117
|
}
|
|
79
118
|
|
|
80
119
|
func readRouterState(root string) (*data.RouterState, error) {
|
|
@@ -103,6 +142,14 @@ func loadEpicTasks(d *data.Discover, root, release, epic string) ([]data.Task, e
|
|
|
103
142
|
if err != nil {
|
|
104
143
|
return nil, err
|
|
105
144
|
}
|
|
145
|
+
fi, err := os.Stat(taskInfo.Path)
|
|
146
|
+
if err != nil {
|
|
147
|
+
return nil, err
|
|
148
|
+
}
|
|
149
|
+
task.Path = taskInfo.Path
|
|
150
|
+
task.Mtime = fi.ModTime()
|
|
151
|
+
task.Release = release
|
|
152
|
+
task.Epic = epic
|
|
106
153
|
tasks = append(tasks, *task)
|
|
107
154
|
}
|
|
108
155
|
return tasks, nil
|
|
@@ -114,6 +161,11 @@ func firstKnown(preferred string, values []string) string {
|
|
|
114
161
|
return preferred
|
|
115
162
|
}
|
|
116
163
|
}
|
|
164
|
+
for _, value := range values {
|
|
165
|
+
if shortID(value) == shortID(preferred) {
|
|
166
|
+
return value
|
|
167
|
+
}
|
|
168
|
+
}
|
|
117
169
|
if len(values) == 0 {
|
|
118
170
|
return ""
|
|
119
171
|
}
|
|
@@ -66,6 +66,111 @@ next_action: "test"
|
|
|
66
66
|
if len(tasks) != 1 || tasks[0].ID != "E03-live/T001-live" {
|
|
67
67
|
t.Errorf("visible in-progress tasks = %v, want E03-live/T001-live", tasks)
|
|
68
68
|
}
|
|
69
|
+
if model.Watcher == nil {
|
|
70
|
+
t.Fatal("Watcher is nil, want auto-refresh watcher")
|
|
71
|
+
}
|
|
72
|
+
t.Cleanup(func() { model.Watcher.Close() })
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
func TestNewProjectModelUsesPathReleaseForTaskWithoutReleaseFrontmatter(t *testing.T) {
|
|
76
|
+
projectRoot := t.TempDir()
|
|
77
|
+
savepointRoot := filepath.Join(projectRoot, ".savepoint")
|
|
78
|
+
writeFile(t, filepath.Join(savepointRoot, "router.md"), `# Agent State Machine
|
|
79
|
+
|
|
80
|
+
## Current state
|
|
81
|
+
|
|
82
|
+
`+"```"+`yaml
|
|
83
|
+
state: task-building
|
|
84
|
+
release: v1.1
|
|
85
|
+
epic: E01-tui-optimisation
|
|
86
|
+
task: E01-tui-optimisation/T001-border-resize-fix
|
|
87
|
+
next_action: "test"
|
|
88
|
+
`+"```"+`
|
|
89
|
+
`)
|
|
90
|
+
writeTaskWithoutRelease(t, savepointRoot, "v1.1", "E01-tui-optimisation", "T001-border-resize-fix", data.ColumnInProgress)
|
|
91
|
+
|
|
92
|
+
model, err := newProjectModel(projectRoot)
|
|
93
|
+
if err != nil {
|
|
94
|
+
t.Fatalf("newProjectModel() error = %v", err)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if model.SelectedRelease != "v1.1" {
|
|
98
|
+
t.Errorf("SelectedRelease = %q, want v1.1", model.SelectedRelease)
|
|
99
|
+
}
|
|
100
|
+
tasks := model.Tasks[data.ColumnInProgress]
|
|
101
|
+
if len(tasks) != 1 {
|
|
102
|
+
t.Fatalf("visible in-progress tasks = %v, want one v1.1 task", tasks)
|
|
103
|
+
}
|
|
104
|
+
if tasks[0].Release != "v1.1" {
|
|
105
|
+
t.Errorf("Task.Release = %q, want v1.1", tasks[0].Release)
|
|
106
|
+
}
|
|
107
|
+
if model.Watcher != nil {
|
|
108
|
+
t.Cleanup(func() { model.Watcher.Close() })
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
func TestNewProjectModelResolvesShortRouterEpicToFullEpicID(t *testing.T) {
|
|
113
|
+
projectRoot := t.TempDir()
|
|
114
|
+
savepointRoot := filepath.Join(projectRoot, ".savepoint")
|
|
115
|
+
writeFile(t, filepath.Join(savepointRoot, "router.md"), `# Agent State Machine
|
|
116
|
+
|
|
117
|
+
## Current state
|
|
118
|
+
|
|
119
|
+
`+"```"+`yaml
|
|
120
|
+
state: task-building
|
|
121
|
+
release: v1.1
|
|
122
|
+
epic: E03
|
|
123
|
+
task: T001
|
|
124
|
+
next_action: "Build v1.1 E03/T001"
|
|
125
|
+
`+"```"+`
|
|
126
|
+
`)
|
|
127
|
+
writeTask(t, savepointRoot, "v1.1", "E01-tui-optimisation", "T007-column-focus-border-stability", data.ColumnInProgress)
|
|
128
|
+
writeTask(t, savepointRoot, "v1.1", "E03-ui-visual-refinement", "T001-border-resize-fix", data.ColumnInProgress)
|
|
129
|
+
|
|
130
|
+
model, err := newProjectModel(projectRoot)
|
|
131
|
+
if err != nil {
|
|
132
|
+
t.Fatalf("newProjectModel() error = %v", err)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if model.SelectedEpic != "E03-ui-visual-refinement" {
|
|
136
|
+
t.Errorf("SelectedEpic = %q, want E03-ui-visual-refinement", model.SelectedEpic)
|
|
137
|
+
}
|
|
138
|
+
tasks := model.Tasks[data.ColumnInProgress]
|
|
139
|
+
if len(tasks) != 1 || tasks[0].ID != "E03-ui-visual-refinement/T001-border-resize-fix" {
|
|
140
|
+
t.Errorf("visible in-progress tasks = %v, want E03-ui-visual-refinement/T001-border-resize-fix", tasks)
|
|
141
|
+
}
|
|
142
|
+
if model.Watcher != nil {
|
|
143
|
+
t.Cleanup(func() { model.Watcher.Close() })
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
func TestUpdateReloadMsgRefreshesReleaseEpicIndex(t *testing.T) {
|
|
148
|
+
m := NewModel(nil, "v1", "E01-old")
|
|
149
|
+
m.Releases = []string{"v1"}
|
|
150
|
+
m.ReleaseEpics = map[string][]string{"v1": []string{"E01-old"}}
|
|
151
|
+
|
|
152
|
+
task := data.Task{
|
|
153
|
+
ID: "E02-new/T001-new",
|
|
154
|
+
Release: "v1",
|
|
155
|
+
Epic: "E02-new",
|
|
156
|
+
Column: data.ColumnPlanned,
|
|
157
|
+
}
|
|
158
|
+
got, _ := m.Update(reloadMsg{
|
|
159
|
+
tasks: []data.Task{task},
|
|
160
|
+
releases: []string{"v1"},
|
|
161
|
+
releaseEpics: map[string][]string{"v1": []string{"E02-new"}},
|
|
162
|
+
})
|
|
163
|
+
updated := requireModel(t, got)
|
|
164
|
+
|
|
165
|
+
if updated.SelectedEpic != "E02-new" {
|
|
166
|
+
t.Errorf("SelectedEpic = %q, want E02-new", updated.SelectedEpic)
|
|
167
|
+
}
|
|
168
|
+
if len(updated.Epics) != 1 || updated.Epics[0] != "E02-new" {
|
|
169
|
+
t.Errorf("Epics = %v, want [E02-new]", updated.Epics)
|
|
170
|
+
}
|
|
171
|
+
if len(updated.Tasks[data.ColumnPlanned]) != 1 {
|
|
172
|
+
t.Errorf("planned tasks = %v, want reloaded task visible", updated.Tasks[data.ColumnPlanned])
|
|
173
|
+
}
|
|
69
174
|
}
|
|
70
175
|
|
|
71
176
|
func writeTask(t *testing.T, root, release, epic, task string, column data.ColumnType) {
|
|
@@ -88,6 +193,25 @@ depends_on: []
|
|
|
88
193
|
writeFile(t, path, content)
|
|
89
194
|
}
|
|
90
195
|
|
|
196
|
+
func writeTaskWithoutRelease(t *testing.T, root, release, epic, task string, column data.ColumnType) {
|
|
197
|
+
t.Helper()
|
|
198
|
+
path := filepath.Join(root, "releases", release, "epics", epic, "tasks", task+".md")
|
|
199
|
+
phase := ""
|
|
200
|
+
if column == data.ColumnInProgress {
|
|
201
|
+
phase = "phase: build\n"
|
|
202
|
+
}
|
|
203
|
+
content := `---
|
|
204
|
+
id: ` + epic + `/` + task + `
|
|
205
|
+
status: ` + string(column) + `
|
|
206
|
+
` + phase + `objective: "Test task"
|
|
207
|
+
depends_on: []
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
# Test task
|
|
211
|
+
`
|
|
212
|
+
writeFile(t, path, content)
|
|
213
|
+
}
|
|
214
|
+
|
|
91
215
|
func writeFile(t *testing.T, path, content string) {
|
|
92
216
|
t.Helper()
|
|
93
217
|
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
package/internal/board/card.go
CHANGED
|
@@ -17,16 +17,26 @@ const (
|
|
|
17
17
|
)
|
|
18
18
|
|
|
19
19
|
// RenderCard renders a task card with phase glyph, truncated ID+title, and focus styling.
|
|
20
|
-
|
|
20
|
+
// When router state matches t's release/epic/task, a green priority glyph replaces the phase glyph.
|
|
21
|
+
func RenderCard(t data.Task, width int, focused bool, routerState *data.RouterState) 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 t.Status != "" {
|
|
29
|
+
glyph = statusGlyph(t.Status)
|
|
30
|
+
} else if t.Column == data.ColumnDone {
|
|
31
|
+
glyph = styles.GlyphBuild.Render(glyphBuild)
|
|
32
|
+
} else if isRouterPriority(t, routerState) {
|
|
33
|
+
glyph = styles.TagDone.Render(glyphBuild)
|
|
34
|
+
} else {
|
|
35
|
+
glyph = phaseGlyphStyled(t.Stage)
|
|
36
|
+
}
|
|
27
37
|
// glyph is 1 rune + 1 space prefix; leave room for "▣ "
|
|
28
38
|
idLine := fmt.Sprintf("%s %s", glyph, truncate(shortID(t.ID), inner-2))
|
|
29
|
-
titleLine := styles.CardMeta.Render(
|
|
39
|
+
titleLine := styles.CardMeta.Render(strings.Join(WrapText(t.Title, inner), "\n"))
|
|
30
40
|
|
|
31
41
|
content := idLine + "\n" + titleLine
|
|
32
42
|
|
|
@@ -47,6 +57,33 @@ func phaseGlyphStyled(stage data.ProgressStage) string {
|
|
|
47
57
|
}
|
|
48
58
|
}
|
|
49
59
|
|
|
60
|
+
func isRouterPriority(t data.Task, state *data.RouterState) bool {
|
|
61
|
+
if state == nil || state.Task == "" {
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
if shortID(t.ID) != shortID(state.Task) {
|
|
65
|
+
return false
|
|
66
|
+
}
|
|
67
|
+
if state.Release != "" && t.Release != "" && t.Release != state.Release {
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
70
|
+
routerEpic := state.Epic
|
|
71
|
+
if routerEpic == "" {
|
|
72
|
+
routerEpic = taskEpic(state.Task)
|
|
73
|
+
}
|
|
74
|
+
if routerEpic != "" && t.Epic != "" && shortID(t.Epic) != shortID(routerEpic) {
|
|
75
|
+
return false
|
|
76
|
+
}
|
|
77
|
+
return true
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
func taskEpic(taskID string) string {
|
|
81
|
+
if idx := strings.LastIndex(taskID, "/"); idx >= 0 {
|
|
82
|
+
return taskID[:idx]
|
|
83
|
+
}
|
|
84
|
+
return ""
|
|
85
|
+
}
|
|
86
|
+
|
|
50
87
|
// shortID strips the epic prefix and slug from a task ID.
|
|
51
88
|
// "E06-atari-noir-layout/T004-component-refinement" → "T004"
|
|
52
89
|
func shortID(id string) string {
|