savepoint 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +15 -1
- package/.golangci.yml +11 -0
- package/.savepoint/Design.md +52 -46
- package/.savepoint/releases/v1/epics/E01-go-setup/tasks/T001-init-module.md +1 -1
- package/.savepoint/releases/v1/epics/E03-board-tui-core/tasks/T005-layout.md +1 -1
- package/.savepoint/releases/v1/epics/E04-board-components/tasks/T002-card.md +1 -1
- package/.savepoint/releases/v1/epics/E04-board-components/tasks/T006-help-overlay.md +1 -1
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/{Design.md → E06-Detail.md} +5 -3
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T002-header-and-dividers.md +1 -1
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T003-footer-status-bar.md +1 -1
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T004-component-refinement.md +1 -1
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T010-auto-refresh-watcher.md +2 -0
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/{Design.md → E01-Detail.md} +9 -1
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/{T007-next-activity-header.md → T001-next-activity-header.md} +13 -12
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T002-rename-epic-design-files.md +9 -9
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T003-rename-release-prd.md +2 -2
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T004-update-instruction-files.md +13 -12
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T005-update-cross-references.md +14 -13
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T006-column-and-detail-scrolling.md +25 -15
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T007-column-focus-border-stability.md +57 -0
- package/.savepoint/releases/v1.1/epics/E02-cross-platform-compatibility/E02-Audit.md +124 -0
- package/.savepoint/releases/v1.1/epics/E02-cross-platform-compatibility/{Design.md → E02-Detail.md} +12 -3
- package/.savepoint/releases/v1.1/epics/E02-cross-platform-compatibility/tasks/T001-fix-makefile.md +11 -8
- package/.savepoint/releases/v1.1/epics/E02-cross-platform-compatibility/tasks/T002-linux-build-target.md +12 -7
- package/.savepoint/releases/v1.1/epics/E02-cross-platform-compatibility/tasks/T003-macos-build-target.md +9 -5
- package/.savepoint/releases/v1.1/epics/E02-cross-platform-compatibility/tasks/T004-smoke-tests-and-artifacts.md +30 -9
- package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/E03-Audit.md +195 -0
- package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/E03-Detail.md +45 -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-Audit.md +167 -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-Audit.md +237 -0
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/E05-Detail.md +54 -0
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T001-update-agents-md.md +45 -0
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T002-update-router-md.md +40 -0
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T003-update-design-md.md +47 -0
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T004-implement-m-hotkey.md +98 -0
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T005-update-help-overlay.md +33 -0
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T006-tests-and-quality-gates.md +62 -0
- 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-Detail.md +43 -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 +28 -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 +28 -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 +139 -0
- package/.savepoint/router.md +29 -108
- package/AGENTS.md +69 -111
- package/Makefile +19 -3
- package/README.md +6 -6
- package/agent-skills/savepoint-audit/SKILL.md +87 -35
- package/agent-skills/savepoint-build-task/SKILL.md +9 -4
- package/agent-skills/savepoint-create-plan/SKILL.md +10 -5
- package/agent-skills/savepoint-create-task/SKILL.md +44 -31
- package/agent-skills/savepoint-draft-prd/SKILL.md +8 -3
- package/agent-skills/savepoint-system-design/SKILL.md +8 -3
- 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 +69 -49
- package/internal/board/board_test.go +83 -67
- package/internal/board/card.go +71 -20
- package/internal/board/card_test.go +141 -12
- package/internal/board/column.go +77 -11
- package/internal/board/column_test.go +63 -13
- package/internal/board/detail.go +107 -72
- package/internal/board/detail_test.go +117 -26
- package/internal/board/epic_panel.go +211 -18
- package/internal/board/epic_panel_test.go +637 -14
- 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/layout.go +12 -2
- package/internal/board/layout_test.go +17 -0
- package/internal/board/model.go +130 -52
- 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/render_policy_test.go +77 -0
- package/internal/board/status.go +23 -0
- 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 +472 -94
- package/internal/board/update_test.go +447 -0
- package/internal/board/util.go +76 -0
- package/internal/board/view.go +139 -22
- package/internal/board/view_test.go +171 -3
- package/internal/board/watch.go +57 -9
- package/internal/buildtool/main.go +211 -0
- package/internal/buildtool/main_test.go +46 -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/lifecycle.go +13 -6
- package/internal/data/lifecycle_test.go +14 -11
- package/internal/data/parser.go +29 -6
- package/internal/data/parser_test.go +66 -7
- package/internal/data/task.go +1 -0
- package/internal/data/write.go +85 -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/palette.go +3 -3
- package/internal/styles/styles.go +39 -12
- 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 +107 -1
- package/package.json +2 -2
- package/project-audit/audit_report_glm_5.1.md +411 -0
- package/project-audit/audit_report_opus_4.6 +406 -0
- package/project-audit/consolidated-audit-report.md +456 -0
- package/savepoint +0 -0
- package/templates/project/.savepoint/Design.md +2 -2
- package/templates/project/.savepoint/router.md +15 -14
- package/templates/project/AGENTS.md +56 -98
- 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 +35 -30
- package/templates/prompts/design.prompt.md +3 -1
- 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/audit/E01-go-setup/proposals.md +0 -166
- package/.savepoint/audit/E01-go-setup/snapshot.md +0 -71
- package/.savepoint/audit/E01-scaffolding/proposals/AGENTS.md +0 -66
- package/.savepoint/audit/E01-scaffolding/proposals/Design.md +0 -210
- package/.savepoint/audit/E01-scaffolding/proposals/epic-Design.md +0 -117
- package/.savepoint/audit/E01-scaffolding/proposals/quality-review.md +0 -101
- package/.savepoint/audit/E01-scaffolding/snapshot.md +0 -54
- package/.savepoint/audit/E02-data-model/snapshot.md +0 -128
- package/.savepoint/audit/E02-data-readers/proposals.md +0 -123
- package/.savepoint/audit/E02-data-readers/snapshot.md +0 -54
- package/.savepoint/audit/E03-board-tui-core/proposals.md +0 -146
- package/.savepoint/audit/E03-board-tui-core/snapshot.md +0 -57
- package/.savepoint/audit/E03-cli-foundation/snapshot.md +0 -106
- package/.savepoint/audit/E04-board-components/proposals.md +0 -118
- package/.savepoint/audit/E04-board-components/snapshot.md +0 -77
- package/.savepoint/audit/E04-templates-and-prompts/snapshot.md +0 -115
- package/.savepoint/audit/E05-init-command/snapshot.md +0 -125
- package/.savepoint/audit/E05-phase-transitions/proposals.md +0 -83
- package/.savepoint/audit/E05-phase-transitions/snapshot.md +0 -36
- package/.savepoint/audit/E06-atari-noir-layout/proposals.md +0 -130
- package/.savepoint/audit/E06-atari-noir-layout/snapshot.md +0 -84
- package/.savepoint/audit/E06-tui-board/snapshot.md +0 -64
- package/.savepoint/audit/E07-audit-pipeline/snapshot.md +0 -165
- package/.savepoint/audit/E08-board-workflow-cleanup/snapshot.md +0 -65
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T001-border-resize-fix.md +0 -36
- package/ink-cli-ui-design.zip +0 -0
- package/main.exe +0 -0
- package/savepoint.exe +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
|
@@ -146,7 +146,7 @@ objective: "Style the board"
|
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
func
|
|
149
|
+
func TestParseTaskFile_allowsPhaseOutsideInProgress(t *testing.T) {
|
|
150
150
|
p := NewParser()
|
|
151
151
|
content := `---
|
|
152
152
|
id: E06/T001
|
|
@@ -158,12 +158,12 @@ objective: "Style the board"
|
|
|
158
158
|
# Task`
|
|
159
159
|
|
|
160
160
|
_, err := p.ParseTaskFile("test.md", content)
|
|
161
|
-
if err
|
|
162
|
-
t.
|
|
161
|
+
if err != nil {
|
|
162
|
+
t.Fatalf("ParseTaskFile() error = %v, want no error for legacy phase field", err)
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
func
|
|
166
|
+
func TestParseTaskFile_includesDefaultBuildForInProgress(t *testing.T) {
|
|
167
167
|
p := NewParser()
|
|
168
168
|
content := `---
|
|
169
169
|
id: E06/T001
|
|
@@ -173,9 +173,33 @@ objective: "Style the board"
|
|
|
173
173
|
|
|
174
174
|
# Task`
|
|
175
175
|
|
|
176
|
-
|
|
177
|
-
if err
|
|
178
|
-
t.
|
|
176
|
+
task, err := p.ParseTaskFile("test.md", content)
|
|
177
|
+
if err != nil {
|
|
178
|
+
t.Fatalf("ParseTaskFile() error = %v", err)
|
|
179
|
+
}
|
|
180
|
+
if task.Stage != StageBuild {
|
|
181
|
+
t.Fatalf("ParseTaskFile() expected StageBuild default, got %q", task.Stage)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
func TestParseTaskFile_prefersPhaseOverLegacyStage(t *testing.T) {
|
|
186
|
+
p := NewParser()
|
|
187
|
+
content := `---
|
|
188
|
+
id: E06/T001
|
|
189
|
+
status: in_progress
|
|
190
|
+
stage: build
|
|
191
|
+
phase: test
|
|
192
|
+
objective: "Style the board"
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
# Task`
|
|
196
|
+
|
|
197
|
+
task, err := p.ParseTaskFile("test.md", content)
|
|
198
|
+
if err != nil {
|
|
199
|
+
t.Fatalf("ParseTaskFile() error = %v", err)
|
|
200
|
+
}
|
|
201
|
+
if task.Stage != StageTest {
|
|
202
|
+
t.Fatalf("Task.Stage = %q, want test from phase", task.Stage)
|
|
179
203
|
}
|
|
180
204
|
}
|
|
181
205
|
|
|
@@ -220,3 +244,38 @@ Notes here.`
|
|
|
220
244
|
t.Errorf("Task.Checklist[1] = %+v, want {Text:\"Second checklist item.\", Done:true}", task.Checklist[1])
|
|
221
245
|
}
|
|
222
246
|
}
|
|
247
|
+
|
|
248
|
+
func TestParseTaskFile_joinsHardWrappedChecklistItems(t *testing.T) {
|
|
249
|
+
p := NewParser()
|
|
250
|
+
content := `---
|
|
251
|
+
id: E06/T001
|
|
252
|
+
status: planned
|
|
253
|
+
objective: "Style the board"
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
# Task
|
|
257
|
+
|
|
258
|
+
## Implementation Plan
|
|
259
|
+
|
|
260
|
+
- [ ] First sentence spans across a hard markdown line break
|
|
261
|
+
before it ends. Second sentence stays in the same checklist item.
|
|
262
|
+
- [x] Already checked sentence wraps
|
|
263
|
+
without becoming another checklist item.
|
|
264
|
+
`
|
|
265
|
+
|
|
266
|
+
task, err := p.ParseTaskFile("test.md", content)
|
|
267
|
+
if err != nil {
|
|
268
|
+
t.Fatalf("ParseTaskFile() error = %v", err)
|
|
269
|
+
}
|
|
270
|
+
if len(task.Checklist) != 2 {
|
|
271
|
+
t.Fatalf("Task.Checklist len = %d, want 2", len(task.Checklist))
|
|
272
|
+
}
|
|
273
|
+
wantFirst := "First sentence spans across a hard markdown line break before it ends. Second sentence stays in the same checklist item."
|
|
274
|
+
if task.Checklist[0].Text != wantFirst || task.Checklist[0].Done {
|
|
275
|
+
t.Errorf("Task.Checklist[0] = %+v, want text %q and Done=false", task.Checklist[0], wantFirst)
|
|
276
|
+
}
|
|
277
|
+
wantSecond := "Already checked sentence wraps without becoming another checklist item."
|
|
278
|
+
if task.Checklist[1].Text != wantSecond || !task.Checklist[1].Done {
|
|
279
|
+
t.Errorf("Task.Checklist[1] = %+v, want text %q and Done=true", task.Checklist[1], wantSecond)
|
|
280
|
+
}
|
|
281
|
+
}
|
package/internal/data/task.go
CHANGED
|
@@ -35,6 +35,7 @@ type Task struct {
|
|
|
35
35
|
ID string `yaml:"id"`
|
|
36
36
|
Title string `yaml:"title"`
|
|
37
37
|
Description string `yaml:"description,omitempty"`
|
|
38
|
+
Status string `yaml:"status,omitempty"`
|
|
38
39
|
Epic string `yaml:"epic"`
|
|
39
40
|
Release string `yaml:"release"`
|
|
40
41
|
Column ColumnType `yaml:"column"`
|
package/internal/data/write.go
CHANGED
|
@@ -11,9 +11,88 @@ import (
|
|
|
11
11
|
)
|
|
12
12
|
|
|
13
13
|
var ErrMtimeConflict = fmt.Errorf("file modified since last read")
|
|
14
|
+
var ErrProposalNotFound = fmt.Errorf("target text not found in file")
|
|
15
|
+
|
|
16
|
+
// ApplyProposal replaces the first occurrence of old with newText in the file at path.
|
|
17
|
+
func ApplyProposal(path, old, newText string) error {
|
|
18
|
+
content, err := os.ReadFile(path)
|
|
19
|
+
if err != nil {
|
|
20
|
+
return fmt.Errorf("read %s: %w", path, err)
|
|
21
|
+
}
|
|
22
|
+
normalized := normalizeLineEndings(string(content))
|
|
23
|
+
if !strings.Contains(normalized, old) {
|
|
24
|
+
return fmt.Errorf("%w: %s", ErrProposalNotFound, path)
|
|
25
|
+
}
|
|
26
|
+
updated := strings.Replace(normalized, old, newText, 1)
|
|
27
|
+
return os.WriteFile(path, []byte(updated), 0644)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// UpdateEpicStatus sets the status field in the frontmatter of an E##-Detail.md file.
|
|
31
|
+
func UpdateEpicStatus(path, status string) error {
|
|
32
|
+
return updateFrontmatterField(path, "status", status)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// UpdateLastAudited sets the last_audited field in the frontmatter of Design.md.
|
|
36
|
+
func UpdateLastAudited(path, value string) error {
|
|
37
|
+
return updateFrontmatterField(path, "last_audited", value)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// SplitFrontmatterBody splits content into frontmatter YAML and body.
|
|
41
|
+
func SplitFrontmatterBody(content string) (yamlStr string, body string, err error) {
|
|
42
|
+
normalized := normalizeLineEndings(content)
|
|
43
|
+
raw, err := extractFrontmatter(normalized)
|
|
44
|
+
if err != nil {
|
|
45
|
+
return "", "", err
|
|
46
|
+
}
|
|
47
|
+
delimLen := 4
|
|
48
|
+
bodyStart := delimLen + len(raw) + delimLen
|
|
49
|
+
body = ""
|
|
50
|
+
if bodyStart < len(normalized) {
|
|
51
|
+
body = normalized[bodyStart:]
|
|
52
|
+
}
|
|
53
|
+
return raw, body, nil
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
func updateFrontmatterField(path, key, value string) error {
|
|
57
|
+
content, err := os.ReadFile(path)
|
|
58
|
+
if err != nil {
|
|
59
|
+
return fmt.Errorf("read %s: %w", path, err)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
normalized := normalizeLineEndings(string(content))
|
|
63
|
+
|
|
64
|
+
raw, body, err := SplitFrontmatterBody(normalized)
|
|
65
|
+
if err != nil {
|
|
66
|
+
return fmt.Errorf("extract frontmatter: %w", err)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
var doc yaml.Node
|
|
70
|
+
if err := yaml.Unmarshal([]byte(raw), &doc); err != nil {
|
|
71
|
+
return fmt.Errorf("parse yaml: %w", err)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if doc.Kind != yaml.DocumentNode || len(doc.Content) == 0 {
|
|
75
|
+
return fmt.Errorf("unexpected yaml structure")
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
mapping := doc.Content[0]
|
|
79
|
+
if mapping.Kind != yaml.MappingNode {
|
|
80
|
+
return fmt.Errorf("frontmatter is not a mapping")
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
setMappingField(mapping, key, value)
|
|
84
|
+
|
|
85
|
+
out, err := yaml.Marshal(&doc)
|
|
86
|
+
if err != nil {
|
|
87
|
+
return fmt.Errorf("marshal yaml: %w", err)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
newContent := "---\n" + strings.TrimSpace(string(out)) + "\n---" + body
|
|
91
|
+
return os.WriteFile(path, []byte(newContent), 0644)
|
|
92
|
+
}
|
|
14
93
|
|
|
15
94
|
func WriteTaskStatus(path string, task *Task, expectedMtime time.Time) error {
|
|
16
|
-
if err := ValidateTaskLifecycle(
|
|
95
|
+
if err := ValidateTaskLifecycle(task); err != nil {
|
|
17
96
|
return err
|
|
18
97
|
}
|
|
19
98
|
|
|
@@ -31,9 +110,9 @@ func WriteTaskStatus(path string, task *Task, expectedMtime time.Time) error {
|
|
|
31
110
|
return fmt.Errorf("read %s: %w", path, err)
|
|
32
111
|
}
|
|
33
112
|
|
|
34
|
-
normalized :=
|
|
113
|
+
normalized := normalizeLineEndings(string(content))
|
|
35
114
|
|
|
36
|
-
raw, err :=
|
|
115
|
+
raw, body, err := SplitFrontmatterBody(normalized)
|
|
37
116
|
if err != nil {
|
|
38
117
|
return fmt.Errorf("extract frontmatter: %w", err)
|
|
39
118
|
}
|
|
@@ -56,8 +135,10 @@ func WriteTaskStatus(path string, task *Task, expectedMtime time.Time) error {
|
|
|
56
135
|
|
|
57
136
|
if task.Stage == "" {
|
|
58
137
|
removeMappingField(mapping, "phase")
|
|
138
|
+
removeMappingField(mapping, "stage")
|
|
59
139
|
} else {
|
|
60
140
|
setMappingField(mapping, "phase", string(task.Stage))
|
|
141
|
+
removeMappingField(mapping, "stage")
|
|
61
142
|
}
|
|
62
143
|
|
|
63
144
|
out, err := yaml.Marshal(&doc)
|
|
@@ -65,13 +146,6 @@ func WriteTaskStatus(path string, task *Task, expectedMtime time.Time) error {
|
|
|
65
146
|
return fmt.Errorf("marshal yaml: %w", err)
|
|
66
147
|
}
|
|
67
148
|
|
|
68
|
-
delimLen := 4
|
|
69
|
-
bodyStart := delimLen + len(raw) + delimLen
|
|
70
|
-
body := ""
|
|
71
|
-
if bodyStart < len(normalized) {
|
|
72
|
-
body = normalized[bodyStart:]
|
|
73
|
-
}
|
|
74
|
-
|
|
75
149
|
newContent := "---\n" + strings.TrimSpace(string(out)) + "\n---" + body
|
|
76
150
|
|
|
77
151
|
return os.WriteFile(path, []byte(newContent), 0644)
|
|
@@ -115,7 +189,7 @@ func WriteRouterState(root string, state *RouterState, expectedMtime time.Time)
|
|
|
115
189
|
return fmt.Errorf("read %s: %w", path, err)
|
|
116
190
|
}
|
|
117
191
|
|
|
118
|
-
normalized :=
|
|
192
|
+
normalized := normalizeLineEndings(string(content))
|
|
119
193
|
|
|
120
194
|
startIdx := strings.Index(normalized, stateBlockStart)
|
|
121
195
|
if startIdx == -1 {
|
|
@@ -206,6 +206,79 @@ objective: "No phase yet"
|
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
+
func TestWriteTaskStatus_defaultsInProgressPhaseWhenStageMissing(t *testing.T) {
|
|
210
|
+
dir := t.TempDir()
|
|
211
|
+
path := filepath.Join(dir, "task.md")
|
|
212
|
+
content := `---
|
|
213
|
+
id: E01/T010
|
|
214
|
+
status: planned
|
|
215
|
+
objective: "No phase yet"
|
|
216
|
+
---`
|
|
217
|
+
|
|
218
|
+
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
|
|
219
|
+
t.Fatal(err)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
fi, _ := os.Stat(path)
|
|
223
|
+
|
|
224
|
+
task := &Task{
|
|
225
|
+
ID: "E01/T010",
|
|
226
|
+
Column: ColumnInProgress,
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if err := WriteTaskStatus(path, task, fi.ModTime()); err != nil {
|
|
230
|
+
t.Fatalf("WriteTaskStatus() error = %v", err)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
result, _ := os.ReadFile(path)
|
|
234
|
+
|
|
235
|
+
if !strings.Contains(string(result), "phase: build") {
|
|
236
|
+
t.Error("phase field should default to build for in_progress writes")
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
func TestWriteTaskStatus_removesLegacyStageField(t *testing.T) {
|
|
241
|
+
dir := t.TempDir()
|
|
242
|
+
path := filepath.Join(dir, "task.md")
|
|
243
|
+
content := `---
|
|
244
|
+
id: E01/T009
|
|
245
|
+
status: in_progress
|
|
246
|
+
stage: build
|
|
247
|
+
phase: build
|
|
248
|
+
objective: "Legacy mixed fields"
|
|
249
|
+
---`
|
|
250
|
+
|
|
251
|
+
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
|
|
252
|
+
t.Fatal(err)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
fi, _ := os.Stat(path)
|
|
256
|
+
task := &Task{
|
|
257
|
+
ID: "E01/T009",
|
|
258
|
+
Column: ColumnInProgress,
|
|
259
|
+
Stage: StageTest,
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if err := WriteTaskStatus(path, task, fi.ModTime()); err != nil {
|
|
263
|
+
t.Fatalf("WriteTaskStatus() error = %v", err)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
result, _ := os.ReadFile(path)
|
|
267
|
+
if strings.Contains(string(result), "stage:") {
|
|
268
|
+
t.Error("legacy stage field should be removed")
|
|
269
|
+
}
|
|
270
|
+
if !strings.Contains(string(result), "phase: test") {
|
|
271
|
+
t.Error("phase field should be updated to test")
|
|
272
|
+
}
|
|
273
|
+
parsed, err := NewParser().ParseTaskFile(path, string(result))
|
|
274
|
+
if err != nil {
|
|
275
|
+
t.Fatalf("ParseTaskFile() error = %v", err)
|
|
276
|
+
}
|
|
277
|
+
if parsed.Stage != StageTest {
|
|
278
|
+
t.Errorf("Stage = %q, want test", parsed.Stage)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
209
282
|
func TestWriteTaskStatus_preservesBodyWithMultipleLines(t *testing.T) {
|
|
210
283
|
dir := t.TempDir()
|
|
211
284
|
path := filepath.Join(dir, "task.md")
|
|
@@ -408,6 +481,100 @@ next_action: "Do the thing"
|
|
|
408
481
|
}
|
|
409
482
|
}
|
|
410
483
|
|
|
484
|
+
func TestApplyProposal_replacesText(t *testing.T) {
|
|
485
|
+
dir := t.TempDir()
|
|
486
|
+
path := filepath.Join(dir, "Design.md")
|
|
487
|
+
content := "# Architecture\n\nOld section text.\n\nMore content."
|
|
488
|
+
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
|
|
489
|
+
t.Fatal(err)
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if err := ApplyProposal(path, "Old section text.", "New section text."); err != nil {
|
|
493
|
+
t.Fatalf("ApplyProposal() error = %v", err)
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
result, _ := os.ReadFile(path)
|
|
497
|
+
if !strings.Contains(string(result), "New section text.") {
|
|
498
|
+
t.Error("replacement not applied")
|
|
499
|
+
}
|
|
500
|
+
if strings.Contains(string(result), "Old section text.") {
|
|
501
|
+
t.Error("old text still present")
|
|
502
|
+
}
|
|
503
|
+
if !strings.Contains(string(result), "More content.") {
|
|
504
|
+
t.Error("surrounding content not preserved")
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
func TestApplyProposal_missingTarget(t *testing.T) {
|
|
509
|
+
dir := t.TempDir()
|
|
510
|
+
path := filepath.Join(dir, "Design.md")
|
|
511
|
+
if err := os.WriteFile(path, []byte("some content"), 0644); err != nil {
|
|
512
|
+
t.Fatal(err)
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
err := ApplyProposal(path, "not present", "replacement")
|
|
516
|
+
if err == nil {
|
|
517
|
+
t.Fatal("ApplyProposal() expected error for missing target")
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
func TestUpdateEpicStatus_setsStatusField(t *testing.T) {
|
|
522
|
+
dir := t.TempDir()
|
|
523
|
+
path := filepath.Join(dir, "E06-Detail.md")
|
|
524
|
+
content := "---\ntype: epic-design\nstatus: planned\n---\n\n# E06 Body"
|
|
525
|
+
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
|
|
526
|
+
t.Fatal(err)
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if err := UpdateEpicStatus(path, "audited"); err != nil {
|
|
530
|
+
t.Fatalf("UpdateEpicStatus() error = %v", err)
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
result, _ := os.ReadFile(path)
|
|
534
|
+
if !strings.Contains(string(result), "status: audited") {
|
|
535
|
+
t.Error("status not updated to audited")
|
|
536
|
+
}
|
|
537
|
+
if !strings.Contains(string(result), "# E06 Body") {
|
|
538
|
+
t.Error("body not preserved")
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
func TestUpdateLastAudited_setsField(t *testing.T) {
|
|
543
|
+
dir := t.TempDir()
|
|
544
|
+
path := filepath.Join(dir, "Design.md")
|
|
545
|
+
content := "---\ntype: project-design\nstatus: active\nlast_audited: v1.1/E05-tasking-permissions\n---\n\n# Body"
|
|
546
|
+
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
|
|
547
|
+
t.Fatal(err)
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if err := UpdateLastAudited(path, "v1.1/E06-audit-command"); err != nil {
|
|
551
|
+
t.Fatalf("UpdateLastAudited() error = %v", err)
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
result, _ := os.ReadFile(path)
|
|
555
|
+
if !strings.Contains(string(result), "last_audited: v1.1/E06-audit-command") {
|
|
556
|
+
t.Error("last_audited not updated")
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
func TestUpdateLastAudited_addsFieldIfMissing(t *testing.T) {
|
|
561
|
+
dir := t.TempDir()
|
|
562
|
+
path := filepath.Join(dir, "Design.md")
|
|
563
|
+
content := "---\ntype: project-design\nstatus: active\n---\n\n# Body"
|
|
564
|
+
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
|
|
565
|
+
t.Fatal(err)
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if err := UpdateLastAudited(path, "v1.1/E06-audit-command"); err != nil {
|
|
569
|
+
t.Fatalf("UpdateLastAudited() error = %v", err)
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
result, _ := os.ReadFile(path)
|
|
573
|
+
if !strings.Contains(string(result), "last_audited: v1.1/E06-audit-command") {
|
|
574
|
+
t.Error("last_audited not added")
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
411
578
|
func TestWriteTaskStatus_noFrontmatter(t *testing.T) {
|
|
412
579
|
dir := t.TempDir()
|
|
413
580
|
path := filepath.Join(dir, "task.md")
|