savepoint 1.0.2 → 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.
Files changed (233) hide show
  1. package/.claude/settings.local.json +12 -1
  2. package/.golangci.yml +11 -0
  3. package/.savepoint/Design.md +37 -36
  4. package/.savepoint/{audit/v1.1/E02-cross-platform-compatibility/proposals.md → releases/v1.1/epics/E02-cross-platform-compatibility/E02-Audit.md} +48 -38
  5. package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/E03-Audit.md +195 -0
  6. package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/E03-Detail.md +14 -1
  7. package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/tasks/T006-forced-256-color-profile.md +3 -3
  8. package/.savepoint/{audit/v1.1/E04-epic-navigation/proposals.md → releases/v1.1/epics/E04-epic-navigation/E04-Audit.md} +65 -54
  9. package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/E05-Audit.md +237 -0
  10. package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/E05-Detail.md +25 -16
  11. package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T001-update-agents-md.md +17 -6
  12. package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T002-update-router-md.md +15 -5
  13. package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T003-update-design-md.md +19 -5
  14. package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T004-implement-m-hotkey.md +11 -1
  15. package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T005-update-help-overlay.md +9 -6
  16. package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T006-tests-and-quality-gates.md +29 -13
  17. package/.savepoint/releases/v1.1/epics/E06-audit-command/E06-Audit.md +56 -0
  18. package/.savepoint/releases/v1.1/epics/E06-audit-command/E06-Detail.md +63 -0
  19. package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T005-proposals.md +44 -0
  20. package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T007-apply-close.md +35 -0
  21. package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T009-integration.md +40 -0
  22. package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T010-audit-file-migration.md +45 -0
  23. package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T011-model-tab-state.md +26 -0
  24. package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T012-epic-audit-render.md +33 -0
  25. package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T013-handle-tab-keys.md +34 -0
  26. package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T014-tab-indicator.md +33 -0
  27. package/.savepoint/releases/v1.1/epics/E07-init-command/E07-Audit.md +336 -0
  28. package/.savepoint/releases/v1.1/epics/E07-init-command/E07-Detail.md +61 -0
  29. package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T001-cli-entrypoint.md +37 -0
  30. package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T002-target-validation.md +28 -0
  31. package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T003-scaffold-writer.md +46 -0
  32. package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T004-atomic-writes.md +27 -0
  33. package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T005-magic-prompt.md +25 -0
  34. package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T006-clipboard.md +26 -0
  35. package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T007-integration-test.md +26 -0
  36. package/.savepoint/releases/v1.1/epics/E08-board-command/E08-Audit.md +333 -0
  37. package/.savepoint/releases/v1.1/epics/E08-board-command/E08-Detail.md +68 -0
  38. package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T001-cli-entrypoint.md +26 -0
  39. package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T002-non-tty-fallback.md +27 -0
  40. package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T003-tui-app-shell.md +28 -0
  41. package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T004-board-model.md +29 -0
  42. package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T005-detail-pane.md +27 -0
  43. package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T006-status-transitions.md +29 -0
  44. package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T007-theme-fallbacks.md +29 -0
  45. package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T008-integration-test.md +27 -0
  46. package/.savepoint/releases/v1.1/epics/E09-doctor-command/E09-Audit.md +207 -0
  47. package/.savepoint/releases/v1.1/epics/E09-doctor-command/E09-Detail.md +65 -0
  48. package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T001-cli-entrypoint.md +24 -0
  49. package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T002-config-router-validation.md +28 -0
  50. package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T003-structure-checks.md +29 -0
  51. package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T004-dependency-checks.md +27 -0
  52. package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T005-audit-orphan-checks.md +28 -0
  53. package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T006-quality-gates-report.md +31 -0
  54. package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/E11-Detail.md +36 -0
  55. package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/tasks/T001-debug-logging.md +25 -0
  56. package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/tasks/T002-increase-debounce.md +21 -0
  57. package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/tasks/T003-error-handling.md +22 -0
  58. package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/tasks/T004-test-verify.md +29 -0
  59. package/.savepoint/releases/v1.1/epics/E12-validation-fix/E12-Audit.md +444 -0
  60. package/.savepoint/releases/v1.1/epics/E12-validation-fix/E12-Detail.md +45 -0
  61. package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T001-default-phase.md +35 -0
  62. package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T002-default-status.md +19 -0
  63. package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T003-better-errors.md +29 -0
  64. package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T004-validate-on-write.md +25 -0
  65. package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T005-tests.md +37 -0
  66. package/.savepoint/releases/v1.1/epics/E13-audit-remediation/E13-Audit.md +118 -0
  67. package/.savepoint/releases/v1.1/epics/E13-audit-remediation/E13-Detail.md +73 -0
  68. package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T001-safe-cleanup.md +66 -0
  69. package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T002-bug-fixes.md +35 -0
  70. package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T003-centralize-duplication.md +60 -0
  71. package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T004-infrastructure.md +33 -0
  72. package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T005-decompose-update.md +37 -0
  73. package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T006-async-io.md +40 -0
  74. package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T007-test-coverage.md +37 -0
  75. package/.savepoint/releases/v1.1/epics/E14-structural-improvements/E14-Audit.md +267 -0
  76. package/.savepoint/releases/v1.1/epics/E14-structural-improvements/E14-Detail.md +54 -0
  77. package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T001-group-model.md +39 -0
  78. package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T002-data-interfaces.md +42 -0
  79. package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T003-discover-orphans.md +33 -0
  80. package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T004-epic-panel-headings.md +35 -0
  81. package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T005-shell-tokenization.md +27 -0
  82. package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T006-unify-enums.md +29 -0
  83. package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T007-testutil-package.md +28 -0
  84. package/.savepoint/releases/v1.1/epics/E15-hardening/E15-Detail.md +43 -0
  85. package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T001-benchmarks.md +31 -0
  86. package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T002-fuzz-targets.md +28 -0
  87. package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T003-debug-flag.md +30 -0
  88. package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T004-dist-checksums.md +27 -0
  89. package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T005-windows-targets.md +28 -0
  90. package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T006-abbreviation-splitting.md +26 -0
  91. package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T007-root-test-allowlist.md +28 -0
  92. package/.savepoint/releases/v1.1/epics/_archived/T001-cli-entrypoint.md +25 -0
  93. package/.savepoint/releases/v1.1/epics/_archived/T002-quality-gates.md +27 -0
  94. package/.savepoint/releases/v1.1/epics/_archived/T003-snapshot.md +27 -0
  95. package/.savepoint/releases/v1.1/epics/_archived/T004-ai-reconcile.md +29 -0
  96. package/.savepoint/releases/v1.1/epics/_archived/T006-tui-review.md +31 -0
  97. package/.savepoint/releases/v1.1/epics/_archived/T008-skip-handling.md +34 -0
  98. package/.savepoint/releases/v1.1/v1.1-PRD.md +67 -7
  99. package/.savepoint/router.md +9 -16
  100. package/AGENTS.md +38 -23
  101. package/README.md +0 -1
  102. package/agent-skills/savepoint-audit/SKILL.md +86 -34
  103. package/agent-skills/savepoint-build-task/SKILL.md +7 -2
  104. package/agent-skills/savepoint-create-plan/SKILL.md +7 -2
  105. package/agent-skills/savepoint-create-task/SKILL.md +44 -31
  106. package/agent-skills/savepoint-draft-prd/SKILL.md +7 -2
  107. package/agent-skills/savepoint-system-design/SKILL.md +7 -2
  108. package/agent_skills_test.go +91 -0
  109. package/cmd/board.go +59 -0
  110. package/cmd/board_test.go +137 -0
  111. package/cmd/doctor.go +53 -0
  112. package/cmd/doctor_test.go +146 -0
  113. package/cmd/init.go +63 -0
  114. package/cmd/init_test.go +104 -0
  115. package/internal/board/board.go +40 -36
  116. package/internal/board/board_test.go +27 -82
  117. package/internal/board/card.go +43 -23
  118. package/internal/board/card_test.go +41 -5
  119. package/internal/board/column.go +44 -13
  120. package/internal/board/column_test.go +5 -2
  121. package/internal/board/detail.go +0 -47
  122. package/internal/board/epic_panel.go +118 -22
  123. package/internal/board/epic_panel_test.go +302 -17
  124. package/internal/board/help.go +1 -0
  125. package/internal/board/help_test.go +1 -0
  126. package/internal/board/integration_test.go +266 -0
  127. package/internal/board/interfaces.go +65 -0
  128. package/internal/board/interfaces_test.go +114 -0
  129. package/internal/board/io.go +93 -0
  130. package/internal/board/model.go +79 -118
  131. package/internal/board/plain.go +88 -0
  132. package/internal/board/plain_test.go +117 -0
  133. package/internal/board/release.go +1 -9
  134. package/internal/board/release_test.go +6 -6
  135. package/internal/board/status.go +4 -4
  136. package/internal/board/theme.go +24 -0
  137. package/internal/board/theme_test.go +31 -0
  138. package/internal/board/transitions.go +113 -88
  139. package/internal/board/transitions_test.go +164 -141
  140. package/internal/board/tui.go +32 -0
  141. package/internal/board/update.go +325 -215
  142. package/internal/board/update_test.go +299 -18
  143. package/internal/board/util.go +76 -0
  144. package/internal/board/view.go +31 -28
  145. package/internal/board/view_test.go +12 -2
  146. package/internal/board/watch.go +35 -5
  147. package/internal/buildtool/main.go +2 -10
  148. package/internal/buildtool/main_test.go +46 -0
  149. package/internal/data/config.go +17 -3
  150. package/internal/data/config_test.go +49 -0
  151. package/internal/data/discover.go +26 -0
  152. package/internal/data/discover_test.go +34 -10
  153. package/internal/data/errors.go +4 -0
  154. package/internal/data/lifecycle.go +13 -6
  155. package/internal/data/lifecycle_test.go +14 -11
  156. package/internal/data/parser.go +21 -6
  157. package/internal/data/parser_test.go +31 -7
  158. package/internal/data/task.go +0 -9
  159. package/internal/data/write.go +85 -11
  160. package/internal/data/write_test.go +167 -0
  161. package/internal/doctor/checks.go +567 -0
  162. package/internal/doctor/checks_test.go +716 -0
  163. package/internal/doctor/gates.go +193 -0
  164. package/internal/doctor/gates_test.go +166 -0
  165. package/internal/doctor/interfaces.go +64 -0
  166. package/internal/doctor/interfaces_test.go +104 -0
  167. package/internal/doctor/repairs.go +80 -0
  168. package/internal/doctor/repairs_test.go +81 -0
  169. package/internal/doctor/report.go +157 -0
  170. package/internal/doctor/report_test.go +89 -0
  171. package/internal/init/clipboard.go +146 -0
  172. package/internal/init/clipboard_test.go +74 -0
  173. package/internal/init/install.go +16 -0
  174. package/internal/init/integration_test.go +197 -0
  175. package/internal/init/prompt.go +14 -0
  176. package/internal/init/prompt_test.go +77 -0
  177. package/internal/init/scaffold.go +59 -0
  178. package/internal/init/scaffold_test.go +179 -0
  179. package/internal/init/template_freshness_test.go +56 -0
  180. package/internal/init/validate.go +85 -0
  181. package/internal/init/validate_test.go +141 -0
  182. package/internal/init/write.go +73 -0
  183. package/internal/init/write_test.go +91 -0
  184. package/internal/styles/styles_test.go +133 -0
  185. package/internal/testutil/fixture.go +113 -0
  186. package/internal/testutil/fs.go +26 -0
  187. package/main.go +101 -4
  188. package/package.json +2 -2
  189. package/project-audit/audit_report_glm_5.1.md +411 -0
  190. package/project-audit/audit_report_opus_4.6 +406 -0
  191. package/project-audit/consolidated-audit-report.md +456 -0
  192. package/savepoint +0 -0
  193. package/templates/project/.savepoint/Design.md +2 -2
  194. package/templates/project/.savepoint/router.md +10 -10
  195. package/templates/project/AGENTS.md +33 -21
  196. package/templates/project/agent-skills/savepoint-audit/SKILL.md +87 -0
  197. package/templates/project/agent-skills/savepoint-build-task/SKILL.md +44 -0
  198. package/templates/project/agent-skills/savepoint-create-plan/SKILL.md +33 -0
  199. package/templates/project/agent-skills/savepoint-create-task/SKILL.md +44 -0
  200. package/templates/project/agent-skills/savepoint-draft-prd/SKILL.md +37 -0
  201. package/templates/project/agent-skills/savepoint-system-design/SKILL.md +38 -0
  202. package/templates/prompts/audit-reconciliation.prompt.md +33 -28
  203. package/templates/prompts/design.prompt.md +3 -1
  204. package/.savepoint/audit/v1/E01/proposals.md +0 -168
  205. package/.savepoint/audit/v1/E01/snapshot.md +0 -78
  206. package/.savepoint/audit/v1/E01-go-setup/proposals.md +0 -166
  207. package/.savepoint/audit/v1/E01-go-setup/snapshot.md +0 -71
  208. package/.savepoint/audit/v1/E01-scaffolding/proposals/AGENTS.md +0 -66
  209. package/.savepoint/audit/v1/E01-scaffolding/proposals/Design.md +0 -210
  210. package/.savepoint/audit/v1/E01-scaffolding/proposals/epic-Design.md +0 -117
  211. package/.savepoint/audit/v1/E01-scaffolding/proposals/quality-review.md +0 -101
  212. package/.savepoint/audit/v1/E01-scaffolding/snapshot.md +0 -54
  213. package/.savepoint/audit/v1/E02-data-model/snapshot.md +0 -128
  214. package/.savepoint/audit/v1/E02-data-readers/proposals.md +0 -123
  215. package/.savepoint/audit/v1/E02-data-readers/snapshot.md +0 -54
  216. package/.savepoint/audit/v1/E03-board-tui-core/proposals.md +0 -146
  217. package/.savepoint/audit/v1/E03-board-tui-core/snapshot.md +0 -57
  218. package/.savepoint/audit/v1/E03-cli-foundation/snapshot.md +0 -106
  219. package/.savepoint/audit/v1/E04-board-components/proposals.md +0 -118
  220. package/.savepoint/audit/v1/E04-board-components/snapshot.md +0 -77
  221. package/.savepoint/audit/v1/E04-templates-and-prompts/snapshot.md +0 -115
  222. package/.savepoint/audit/v1/E05-init-command/snapshot.md +0 -125
  223. package/.savepoint/audit/v1/E05-phase-transitions/proposals.md +0 -83
  224. package/.savepoint/audit/v1/E05-phase-transitions/snapshot.md +0 -36
  225. package/.savepoint/audit/v1/E06-atari-noir-layout/proposals.md +0 -130
  226. package/.savepoint/audit/v1/E06-atari-noir-layout/snapshot.md +0 -84
  227. package/.savepoint/audit/v1/E06-tui-board/snapshot.md +0 -64
  228. package/.savepoint/audit/v1/E07-audit-pipeline/snapshot.md +0 -165
  229. package/.savepoint/audit/v1/E08-board-workflow-cleanup/snapshot.md +0 -65
  230. package/.savepoint/audit/v1.1/E02-cross-platform-compatibility/snapshot.md +0 -41
  231. package/.savepoint/audit/v1.1/E04-epic-navigation/snapshot.md +0 -48
  232. package/ink-cli-ui-design.zip +0 -0
  233. package/savepoint.exe +0 -0
@@ -1,35 +1,87 @@
1
- # Savepoint Skill: Audit (`audit`)
2
-
3
- ## Objective
4
- At the end of an epic, review the implemented code against the Epic objectives and Task Acceptance Criteria, and reconcile any documentation "Drift".
5
-
6
- ## Context
7
- The Audit Gate is Savepoint's wedge. It prevents projects from degrading into chaos. The `audit` agent acts as the Quality Assurance and Documentation Lead. It reviews the work of the `build-task` agent with "fresh eyes," ensuring that the `Design.md` and the `AGENTS.md` Codebase Map reflect the actual reality of the codebase before the next Epic begins.
8
-
9
- ## Trigger
10
- This skill is activated when the `.savepoint/router.md` state is `audit-pending`.
11
-
12
- ## Input
13
- - `.savepoint/audit/{release}/{E##-slug}/snapshot.md` (what changed).
1
+ ---
2
+ name: savepoint-audit
3
+ description: Performs Savepoint audit-pending work for a completed epic, reviewing implementation against task acceptance criteria and writing the required E##-Audit.md handoff file.
4
+ ---
5
+
6
+ # Savepoint Skill: Audit (`audit`)
7
+
8
+ ## Objective
9
+ At the end of an epic, review the implemented code against the Epic objectives and Task Acceptance Criteria, and reconcile any documentation "Drift".
10
+
11
+ ## Context
12
+ The Audit Gate is Savepoint's wedge. It prevents projects from degrading into chaos. The `audit` agent acts as the Quality Assurance and Documentation Lead. It reviews the work of the `build-task` agent with "fresh eyes," ensuring that the `Design.md` and the `AGENTS.md` Codebase Map reflect the actual reality of the codebase before the next Epic begins.
13
+
14
+ ## Trigger
15
+ This skill is activated when the `.savepoint/router.md` state is `audit-pending`.
16
+
17
+ ## Input
14
18
  - `.savepoint/releases/{release}/epics/{E##-slug}/E##-Detail.md` (the epic design).
15
- - `.savepoint/Design.md` (project architecture).
16
- - `AGENTS.md` (agent guide and codebase map).
17
- - The source and test files modified during the Epic.
18
-
19
- ## Workflow
20
-
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
- 2. **Verify ACs:** Review the completed tasks for the Epic. Ensure the Acceptance Criteria were actually met by the committed code.
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/{release}/{E##-slug}/proposals.md`. It must contain:
25
- * **Design.md section:** Propose updates to merge the epic's architectural changes into the project-level `Design.md`.
26
- * **AGENTS.md section:** Propose updates to refresh the Codebase Map table with new or changed modules.
27
- * **Epic-E##-Detail.md section:** Add "Implemented as:" notes showing where reality deviated from the original plan.
28
- * **Quality Review section:** List any minor code-style infractions that must be fixed before the next Epic.
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/{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
-
32
- ## Constraints
33
- - **Do not write product code.** You are an auditor.
34
- - **Do not apply the changes immediately.** Write the proposals document first.
35
- - **One proposals file.** Do not create multiple proposal files.
19
+ - `.savepoint/Design.md` (project architecture).
20
+ - `AGENTS.md` (agent guide and codebase map).
21
+ - The source and test files modified during the Epic.
22
+
23
+ ## Workflow
24
+
25
+ 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."
26
+ 2. **Verify ACs:** Review the completed tasks for the Epic. Ensure the Acceptance Criteria were actually met by the committed code. Also confirm each task file has a `## Context Files` section — flag any missing ones under `## Main Findings` as a process gap.
27
+ 3. **Process Drift Notes (Reconciliation):** Read every task file in the Epic and look for `## Drift Notes`.
28
+ 4. **Draft Audit File:** Based on the code changes and the Drift Notes, write exactly ONE file: `.savepoint/releases/{release}/epics/{E##-slug}/E##-Audit.md`. It must use this format:
29
+ ````md
30
+ ---
31
+ type: audit-findings
32
+ audited: YYYY-MM-DD
33
+ ---
34
+ # Audit Findings: E## {Epic Name}
35
+
36
+ ## Main Findings
37
+ [human-readable audit summary only: AC verification, important drift, and notable risks. Do not include per-file replacement blocks here.]
38
+
39
+ ## Code Style Review
40
+ - [ ] One job per file
41
+ - [ ] One-sentence functions
42
+ - [ ] Test branches
43
+ - [ ] Types are documentation
44
+ - [ ] Build, don't speculate
45
+ - [ ] Errors at boundaries
46
+ - [ ] One source of truth
47
+ - [ ] Comments explain WHY
48
+ - [ ] Content in data files
49
+ - [ ] Small diffs
50
+
51
+ ## Proposed Changes
52
+ ### Target File
53
+ path/to/file.md
54
+
55
+ ### Replace
56
+ ```
57
+ exact old text
58
+ ```
59
+
60
+ ### With
61
+ ```
62
+ replacement text
63
+ ```
64
+ ````
65
+ The TUI Audit tab renders only `## Main Findings` and `## Code Style Review`. Keep `## Proposed Changes` as admin/apply metadata so the Epic Detail panel does not show stale file-change blocks.
66
+ 5. **Review Format:** Use `### Target File`, `### Replace`, and `### With` formatting only inside `## Proposed Changes`. Include proposals for:
67
+ * **Design.md:** Merge the epic's architectural changes into the project-level `Design.md`.
68
+ * **AGENTS.md:** Refresh the Codebase Map table with new or changed modules.
69
+ * **Epic E##-Detail.md:** Add "Implemented as:" notes showing where reality deviated from the original plan.
70
+ * **Implementation fixes:** Include any must-fix code or test changes found during audit.
71
+ 6. **Handoff:** Do not apply the proposals yourself. Do not mark the epic audited. Stop and prompt the user to review `.savepoint/releases/{release}/epics/{E##-slug}/E##-Audit.md` (via the TUI Audit tab). Tell them: "Review the audit tab. When ready, say 'apply audit' to apply proposals and close the epic."
72
+ 7. **Apply + Close** (only when the user approves by saying "apply audit" or equivalent):
73
+ 1. Read `E##-Audit.md` — extract every `### Target File` / `### Replace` / `### With` block from `## Proposed Changes`.
74
+ 2. For each pair, apply the replacement to the target file named in the preceding `### Target File` line.
75
+ 3. Update `E##-Audit.md` visible sections: rewrite `## Main Findings` and `## Code Style Review` so the TUI Audit tab reflects the applied outcome, resolved findings, remaining risks if any, and final code-style status. Keep `## Proposed Changes` intact as admin/apply trace unless the user asks to remove it.
76
+ 4. Update `E##-Detail.md` frontmatter: set `status: audited`.
77
+ 5. Update `.savepoint/Design.md` frontmatter: set `last_audited: {release}/{E##-slug}`.
78
+ 6. Read `.savepoint/router.md` current state. Advance:
79
+ - If more epics remain in the release: set `state: epic-design`, `epic: {next-epic-slug}`, `task: ""`, `next_action: "Draft epic design"`.
80
+ - If no more epics: set `state: epic-design`, `epic: ""`, `next_action: "Plan next epic"`.
81
+ 7. Print apply summary: "Applied X proposals. Updated audit findings. Epic {E##} closed as audited. Router → {new state}."
82
+
83
+ ## Constraints
84
+ - **Do not write product code.** You are an auditor.
85
+ - **Do not apply the changes immediately.** Write the proposals document first.
86
+ - **One proposals file.** Do not create multiple proposal files.
87
+ - **No CLI audit pipeline.** Savepoint audit is agent-led and skill-driven; do not invoke or design around a `savepoint audit` command.
@@ -1,4 +1,9 @@
1
- # Savepoint Skill: Build Task (`build-task`)
1
+ ---
2
+ name: savepoint-build-task
3
+ description: Executes Savepoint task-building work when .savepoint/router.md state is task-building, including implementing one active task, checking acceptance criteria, running quality gates, and stopping for user review.
4
+ ---
5
+
6
+ # Savepoint Skill: Build Task (`build-task`)
2
7
 
3
8
  ## Objective
4
9
  Act as a disciplined coding agent that strictly follows Savepoint's implementation loop.
@@ -36,4 +41,4 @@ This skill is activated when the `.savepoint/router.md` state is `task-building`
36
41
 
37
42
  ## Constraints
38
43
  - **Stay in scope:** Do not touch files outside of what is required for the Acceptance Criteria.
39
- - **Do not edit architecture documents.** If you must deviate from the plan, write the code and log a "Drift Note" in the task file.
44
+ - **Do not edit architecture documents.** If you must deviate from the plan, write the code and log a "Drift Note" in the task file.
@@ -1,4 +1,9 @@
1
- # Savepoint Skill: Create Plan (`create-plan`)
1
+ ---
2
+ name: savepoint-create-plan
3
+ description: Creates Savepoint release epics from the PRD and Design documents when the router is in pre-implementation planning.
4
+ ---
5
+
6
+ # Savepoint Skill: Create Plan (`create-plan`)
2
7
 
3
8
  ## Objective
4
9
  Turn the Product Requirements Document (PRD) and the architectural `Design.md` into Epics (logical slices of work) and high-level tasks that can be implemented independently.
@@ -25,4 +30,4 @@ This skill is activated when the `.savepoint/router.md` state is `pre-implementa
25
30
 
26
31
  ## Constraints
27
32
  - **Do not write code.**
28
- - **Do not write detailed implementation steps.** That is the job of the `create-task` skill. Keep the task outlines high-level at this stage.
33
+ - **Do not write detailed implementation steps.** That is the job of the `create-task` skill. Keep the task outlines high-level at this stage.
@@ -1,31 +1,44 @@
1
- # Savepoint Skill: Create Task (`create-task`)
2
-
3
- ## Objective
4
- Take high-level tasks identified during the planning phase and build detailed, actionable task plans with strict Acceptance Criteria.
5
-
6
- ## Context
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
-
9
- ## Trigger
10
- This skill is activated when the `.savepoint/router.md` state is `epic-task-breakdown` and the router points to a specific epic.
11
-
12
- ## Input
13
- - `.savepoint/releases/v1/epics/{E##-epic}/E##-Detail.md` (The active Epic's design).
14
- - The high-level task markdown file (e.g., `.savepoint/releases/v1/epics/{E##-epic}/tasks/T001-slug.md`).
15
-
16
- ## Workflow
17
-
18
- 1. **Read Context:** Understand the goal of the specific task within the context of its parent Epic.
19
- 2. **Define Acceptance Criteria (ACs):** This is the most critical step. Write explicit, observable outcomes. (e.g., "Running `npm test` passes 5 tests for the auth module" or "The `/login` route returns a 200 OK with a valid JWT"). Do not use subjective language like "looks good."
20
- 3. **Draft Implementation Plan:** Create a step-by-step checklist for the `build-task` agent to follow.
21
- * List which files need to be created or modified.
22
- * Specify which functions or components need to be written.
23
- * Include instructions to write tests *first* if applicable.
24
- 4. **Add Context Log Shell:** Ensure the bottom of the task file includes a `## Context Log` section with placeholders for `Files read:`, `Estimated input tokens:`, and `Notes:`.
25
- 5. **Define Dependencies:** If this task relies on another task being completed first, explicitly declare it in the YAML frontmatter (e.g., `depends_on: [T001-setup]`).
26
- 6. **Status Update:** Change the task frontmatter to `status: planned`.
27
- 7. **Handoff:** Update `.savepoint/router.md` to `state: in-progress` and ensure it points to the newly planned task. Prompt the user to approve the task plan before building begins.
28
-
29
- ## Constraints
30
- - **Do not write code.** Your job is to plan the work, not execute it.
31
- - **Keep it isolated:** The task plan should touch as few files as possible. If a task plan requires modifying 15 files, it is too large and should be broken down further.
1
+ ---
2
+ name: savepoint-create-task
3
+ description: Plans Savepoint task files during epic-task-breakdown by writing acceptance criteria, implementation checklists, dependencies, and context-log shells.
4
+ ---
5
+
6
+ # Savepoint Skill: Create Task (`create-task`)
7
+
8
+ ## Objective
9
+ Take high-level tasks identified during the planning phase and build detailed, actionable task plans with strict Acceptance Criteria.
10
+
11
+ ## Context
12
+ 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.
13
+
14
+ ## Trigger
15
+ This skill is activated when the `.savepoint/router.md` state is `epic-task-breakdown` and the router points to a specific epic.
16
+
17
+ ## Input
18
+ - `.savepoint/releases/v1/epics/{E##-epic}/E##-Detail.md` (The active Epic's design).
19
+ - The high-level task markdown file (e.g., `.savepoint/releases/v1/epics/{E##-epic}/tasks/T001-slug.md`).
20
+
21
+ ## Workflow
22
+
23
+ 1. **Read Context:** Understand the goal of the specific task within the context of its parent Epic.
24
+ 2. **Define Acceptance Criteria (ACs):** This is the most critical step. Write explicit, observable outcomes. (e.g., "Running `npm test` passes 5 tests for the auth module" or "The `/login` route returns a 200 OK with a valid JWT"). Do not use subjective language like "looks good."
25
+ 3. **Draft Implementation Plan:** Create a step-by-step checklist for the `build-task` agent to follow.
26
+ * List which files need to be created or modified.
27
+ * Specify which functions or components need to be written.
28
+ * Include instructions to write tests *first* if applicable.
29
+ 4. **Populate Context Files:** Add a `## Context Files` section listing the exact file paths the build agent must read before coding. Pull these from the epic's Components table — only the files this task actually touches or depends on. Example:
30
+ ```markdown
31
+ ## Context Files
32
+ - `internal/board/board.go`
33
+ - `internal/board/model.go`
34
+ - `internal/data/config.go`
35
+ ```
36
+ No globs. No directories. Exact paths only.
37
+ 5. **Add Context Log Shell:** Ensure the bottom of the task file includes a `## Context Log` section with placeholders for `Files read:`, `Estimated input tokens:`, and `Notes:`.
38
+ 5. **Define Dependencies:** If this task relies on another task being completed first, explicitly declare it in the YAML frontmatter (e.g., `depends_on: [T001-setup]`).
39
+ 6. **Status Update:** Change the task frontmatter to `status: planned`.
40
+ 7. **Handoff:** Update `.savepoint/router.md` to `state: in-progress` and ensure it points to the newly planned task. Prompt the user to approve the task plan before building begins.
41
+
42
+ ## Constraints
43
+ - **Do not write code.** Your job is to plan the work, not execute it.
44
+ - **Keep it isolated:** The task plan should touch as few files as possible. If a task plan requires modifying 15 files, it is too large and should be broken down further.
@@ -1,4 +1,9 @@
1
- # Savepoint Skill: Draft PRD (`draft-prd`)
1
+ ---
2
+ name: savepoint-draft-prd
3
+ description: Guides Savepoint PRD drafting and refinement before implementation, interviewing the user until the product scope is clear enough for design.
4
+ ---
5
+
6
+ # Savepoint Skill: Draft PRD (`draft-prd`)
2
7
 
3
8
  ## Objective
4
9
  Help the user write a structured, sufficiently detailed Product Requirements Document (PRD) before any design or planning occurs.
@@ -29,4 +34,4 @@ This skill is activated when the `.savepoint/router.md` state is `pre-implementa
29
34
  ## Constraints
30
35
  - **Do not write code.**
31
36
  - **Do not plan Epics.** That is the job of the `create-plan` skill.
32
- - **Do not make technical architectural decisions.** That is the job of the `system-design` skill. Your focus is strictly on *what* and *why*, not *how*.
37
+ - **Do not make technical architectural decisions.** That is the job of the `system-design` skill. Your focus is strictly on *what* and *why*, not *how*.
@@ -1,4 +1,9 @@
1
- # Savepoint Skill: System Design (`system-design`)
1
+ ---
2
+ name: savepoint-system-design
3
+ description: Produces Savepoint system or epic design documents from the PRD when the router is in epic-design or the user asks for architectural design.
4
+ ---
5
+
6
+ # Savepoint Skill: System Design (`system-design`)
2
7
 
3
8
  ## Objective
4
9
  Translate the Product Requirements Document (PRD) into the initial architectural blueprint (`Design.md`) before the planning phase breaks the work down into Epics.
@@ -30,4 +35,4 @@ This skill is activated when the `.savepoint/router.md` state is `epic-design` o
30
35
  ## Constraints
31
36
  - **Do not write code.**
32
37
  - **Do not break down tasks.** That is the job of the `create-plan` skill.
33
- - **Maintain Token Discipline:** Keep the `Design.md` concise. It is meant to be read by AI agents on every turn. Rambling design docs destroy context windows.
38
+ - **Maintain Token Discipline:** Keep the `Design.md` concise. It is meant to be read by AI agents on every turn. Rambling design docs destroy context windows.
@@ -0,0 +1,91 @@
1
+ package main
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "strings"
7
+ "testing"
8
+ )
9
+
10
+ func TestBundledSavepointSkillsHaveDiscoveryFrontmatter(t *testing.T) {
11
+ assertSavepointSkillsHaveFrontmatter(t, filepath.Join("agent-skills"))
12
+ assertSavepointSkillsHaveFrontmatter(t, filepath.Join("templates", "project", "agent-skills"))
13
+ }
14
+
15
+ func TestProjectAgentGuideIncludesLocalSkillFallback(t *testing.T) {
16
+ path := filepath.Join("templates", "project", "AGENTS.md")
17
+ content, err := os.ReadFile(path)
18
+ if err != nil {
19
+ t.Fatalf("ReadFile(%q) error = %v", path, err)
20
+ }
21
+
22
+ want := "If the agent says the skill is not found, read `agent-skills/{skill}/SKILL.md` directly"
23
+ if !strings.Contains(string(content), want) {
24
+ t.Fatalf("%s missing local skill fallback instruction", path)
25
+ }
26
+ }
27
+
28
+ func TestScaffoldedSavepointSkillsMatchBundledSkills(t *testing.T) {
29
+ root := filepath.Join("agent-skills")
30
+ entries, err := os.ReadDir(root)
31
+ if err != nil {
32
+ t.Fatalf("ReadDir(%q) error = %v", root, err)
33
+ }
34
+
35
+ for _, entry := range entries {
36
+ if !entry.IsDir() || !strings.HasPrefix(entry.Name(), "savepoint-") {
37
+ continue
38
+ }
39
+
40
+ sourcePath := filepath.Join(root, entry.Name(), "SKILL.md")
41
+ scaffoldPath := filepath.Join("templates", "project", "agent-skills", entry.Name(), "SKILL.md")
42
+ source, err := os.ReadFile(sourcePath)
43
+ if err != nil {
44
+ t.Fatalf("ReadFile(%q) error = %v", sourcePath, err)
45
+ }
46
+ scaffold, err := os.ReadFile(scaffoldPath)
47
+ if err != nil {
48
+ t.Fatalf("ReadFile(%q) error = %v", scaffoldPath, err)
49
+ }
50
+ if string(scaffold) != string(source) {
51
+ t.Fatalf("%s does not match %s", scaffoldPath, sourcePath)
52
+ }
53
+ }
54
+ }
55
+
56
+ func assertSavepointSkillsHaveFrontmatter(t *testing.T, root string) {
57
+ t.Helper()
58
+
59
+ entries, err := os.ReadDir(root)
60
+ if err != nil {
61
+ t.Fatalf("ReadDir(%q) error = %v", root, err)
62
+ }
63
+
64
+ var found int
65
+ for _, entry := range entries {
66
+ if !entry.IsDir() || !strings.HasPrefix(entry.Name(), "savepoint-") {
67
+ continue
68
+ }
69
+ found++
70
+ path := filepath.Join(root, entry.Name(), "SKILL.md")
71
+ content, err := os.ReadFile(path)
72
+ if err != nil {
73
+ t.Fatalf("ReadFile(%q) error = %v", path, err)
74
+ }
75
+
76
+ text := string(content)
77
+ if !strings.HasPrefix(text, "---\n") {
78
+ t.Fatalf("%s missing YAML frontmatter", path)
79
+ }
80
+ if !strings.Contains(text, "name: "+entry.Name()) {
81
+ t.Fatalf("%s frontmatter name does not match directory", path)
82
+ }
83
+ if !strings.Contains(text, "description:") {
84
+ t.Fatalf("%s missing frontmatter description", path)
85
+ }
86
+ }
87
+
88
+ if found == 0 {
89
+ t.Fatalf("%s contains %d savepoint skills, want at least 1", root, found)
90
+ }
91
+ }
package/cmd/board.go ADDED
@@ -0,0 +1,59 @@
1
+ package cmd
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "io"
7
+ )
8
+
9
+ const boardUsage = "Usage: board [--release <release>] [--epic <epic>]"
10
+
11
+ type BoardOptions struct {
12
+ Release string
13
+ Epic string
14
+ }
15
+
16
+ type BoardRunner func(BoardOptions) error
17
+
18
+ func RunBoard(ctx context.Context, args []string, stdout io.Writer, runner BoardRunner) error {
19
+ options, help, err := ParseBoardArgs(args)
20
+ if help {
21
+ _, writeErr := fmt.Fprintln(stdout, boardUsage)
22
+ return writeErr
23
+ }
24
+ if err != nil {
25
+ return err
26
+ }
27
+ return runner(options)
28
+ }
29
+
30
+ func ParseBoardArgs(args []string) (BoardOptions, bool, error) {
31
+ var options BoardOptions
32
+
33
+ for i := 0; i < len(args); i++ {
34
+ arg := args[i]
35
+ switch arg {
36
+ case "--help":
37
+ return BoardOptions{}, true, nil
38
+ case "--release":
39
+ i++
40
+ if i >= len(args) {
41
+ return BoardOptions{}, false, fmt.Errorf("--release requires a value")
42
+ }
43
+ options.Release = args[i]
44
+ case "--epic":
45
+ i++
46
+ if i >= len(args) {
47
+ return BoardOptions{}, false, fmt.Errorf("--epic requires a value")
48
+ }
49
+ options.Epic = args[i]
50
+ default:
51
+ if len(arg) > 0 && arg[0] == '-' {
52
+ return BoardOptions{}, false, fmt.Errorf("unknown board flag %q", arg)
53
+ }
54
+ return BoardOptions{}, false, fmt.Errorf("board takes no positional arguments, got %q", arg)
55
+ }
56
+ }
57
+
58
+ return options, false, nil
59
+ }
@@ -0,0 +1,137 @@
1
+ package cmd
2
+
3
+ import (
4
+ "bytes"
5
+ "context"
6
+ "errors"
7
+ "strings"
8
+ "testing"
9
+ )
10
+
11
+ func TestRunBoardHelp(t *testing.T) {
12
+ var stdout bytes.Buffer
13
+ called := false
14
+
15
+ err := RunBoard(context.Background(), []string{"--help"}, &stdout, func(BoardOptions) error {
16
+ called = true
17
+ return nil
18
+ })
19
+
20
+ if err != nil {
21
+ t.Fatalf("RunBoard() error = %v", err)
22
+ }
23
+ if called {
24
+ t.Fatal("RunBoard() called runner for help")
25
+ }
26
+ if !strings.Contains(stdout.String(), "board [--release <release>] [--epic <epic>]") {
27
+ t.Fatalf("help output = %q", stdout.String())
28
+ }
29
+ }
30
+
31
+ func TestRunBoardNoArgs(t *testing.T) {
32
+ got := runBoardOptions(t, nil)
33
+
34
+ if got.Release != "" {
35
+ t.Fatalf("Release = %q, want empty", got.Release)
36
+ }
37
+ if got.Epic != "" {
38
+ t.Fatalf("Epic = %q, want empty", got.Epic)
39
+ }
40
+ }
41
+
42
+ func TestRunBoardRelease(t *testing.T) {
43
+ got := runBoardOptions(t, []string{"--release", "v1"})
44
+
45
+ if got.Release != "v1" {
46
+ t.Fatalf("Release = %q, want v1", got.Release)
47
+ }
48
+ }
49
+
50
+ func TestRunBoardEpic(t *testing.T) {
51
+ got := runBoardOptions(t, []string{"--epic", "E03"})
52
+
53
+ if got.Epic != "E03" {
54
+ t.Fatalf("Epic = %q, want E03", got.Epic)
55
+ }
56
+ }
57
+
58
+ func TestRunBoardReleaseAndEpic(t *testing.T) {
59
+ got := runBoardOptions(t, []string{"--release", "v1", "--epic", "E03"})
60
+
61
+ if got.Release != "v1" {
62
+ t.Fatalf("Release = %q, want v1", got.Release)
63
+ }
64
+ if got.Epic != "E03" {
65
+ t.Fatalf("Epic = %q, want E03", got.Epic)
66
+ }
67
+ }
68
+
69
+ func TestRunBoardRejectsUnknownFlag(t *testing.T) {
70
+ var stdout bytes.Buffer
71
+
72
+ err := RunBoard(context.Background(), []string{"--bogus"}, &stdout, func(BoardOptions) error {
73
+ return nil
74
+ })
75
+
76
+ if err == nil {
77
+ t.Fatal("RunBoard() error = nil, want unknown flag error")
78
+ }
79
+ if !strings.Contains(err.Error(), "unknown board flag") {
80
+ t.Fatalf("error = %q, want unknown flag", err.Error())
81
+ }
82
+ }
83
+
84
+ func TestRunBoardRejectsPositionalArgs(t *testing.T) {
85
+ var stdout bytes.Buffer
86
+
87
+ err := RunBoard(context.Background(), []string{"extra"}, &stdout, func(BoardOptions) error {
88
+ return nil
89
+ })
90
+
91
+ if err == nil {
92
+ t.Fatal("RunBoard() error = nil, want positional arg error")
93
+ }
94
+ }
95
+
96
+ func TestRunBoardReleaseMissingValue(t *testing.T) {
97
+ var stdout bytes.Buffer
98
+
99
+ err := RunBoard(context.Background(), []string{"--release"}, &stdout, func(BoardOptions) error {
100
+ return nil
101
+ })
102
+
103
+ if err == nil {
104
+ t.Fatal("RunBoard() error = nil, want missing value error")
105
+ }
106
+ if !strings.Contains(err.Error(), "--release requires a value") {
107
+ t.Fatalf("error = %q", err.Error())
108
+ }
109
+ }
110
+
111
+ func TestRunBoardReturnsRunnerError(t *testing.T) {
112
+ want := errors.New("runner failed")
113
+ var stdout bytes.Buffer
114
+
115
+ err := RunBoard(context.Background(), nil, &stdout, func(BoardOptions) error {
116
+ return want
117
+ })
118
+
119
+ if !errors.Is(err, want) {
120
+ t.Fatalf("RunBoard() error = %v, want %v", err, want)
121
+ }
122
+ }
123
+
124
+ func runBoardOptions(t *testing.T, args []string) BoardOptions {
125
+ t.Helper()
126
+
127
+ var stdout bytes.Buffer
128
+ var got BoardOptions
129
+ err := RunBoard(context.Background(), args, &stdout, func(options BoardOptions) error {
130
+ got = options
131
+ return nil
132
+ })
133
+ if err != nil {
134
+ t.Fatalf("RunBoard() error = %v", err)
135
+ }
136
+ return got
137
+ }
package/cmd/doctor.go ADDED
@@ -0,0 +1,53 @@
1
+ package cmd
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "io"
7
+ )
8
+
9
+ const doctorUsage = "Usage: doctor [--epic <epic>]"
10
+
11
+ type DoctorOptions struct {
12
+ Epic string
13
+ }
14
+
15
+ // DoctorRunner receives parsed options and returns an exit code: 0=clean, 1=problems, 2=internal error.
16
+ type DoctorRunner func(DoctorOptions) (int, error)
17
+
18
+ func RunDoctor(ctx context.Context, args []string, stdout io.Writer, runner DoctorRunner) (int, error) {
19
+ options, help, err := ParseDoctorArgs(args)
20
+ if help {
21
+ _, writeErr := fmt.Fprintln(stdout, doctorUsage)
22
+ return 0, writeErr
23
+ }
24
+ if err != nil {
25
+ return 2, err
26
+ }
27
+ return runner(options)
28
+ }
29
+
30
+ func ParseDoctorArgs(args []string) (DoctorOptions, bool, error) {
31
+ var options DoctorOptions
32
+
33
+ for i := 0; i < len(args); i++ {
34
+ arg := args[i]
35
+ switch arg {
36
+ case "--help":
37
+ return DoctorOptions{}, true, nil
38
+ case "--epic":
39
+ i++
40
+ if i >= len(args) {
41
+ return DoctorOptions{}, false, fmt.Errorf("--epic requires a value")
42
+ }
43
+ options.Epic = args[i]
44
+ default:
45
+ if len(arg) > 0 && arg[0] == '-' {
46
+ return DoctorOptions{}, false, fmt.Errorf("unknown doctor flag %q", arg)
47
+ }
48
+ return DoctorOptions{}, false, fmt.Errorf("doctor takes no positional arguments, got %q", arg)
49
+ }
50
+ }
51
+
52
+ return options, false, nil
53
+ }