jettypod 4.4.118 → 4.4.121

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 (240) hide show
  1. package/.env +4 -3
  2. package/Cargo.lock +6450 -0
  3. package/Cargo.toml +35 -0
  4. package/README.md +5 -1
  5. package/TAURI-MIGRATION-PLAN.md +840 -0
  6. package/apps/dashboard/app/connect-claude/page.tsx +5 -6
  7. package/apps/dashboard/app/decision/[id]/page.tsx +63 -58
  8. package/apps/dashboard/app/demo/gates/page.tsx +43 -45
  9. package/apps/dashboard/app/design-system/page.tsx +868 -0
  10. package/apps/dashboard/app/globals.css +80 -4
  11. package/apps/dashboard/app/install-claude/page.tsx +4 -6
  12. package/apps/dashboard/app/login/page.tsx +72 -54
  13. package/apps/dashboard/app/page.tsx +101 -48
  14. package/apps/dashboard/app/settings/page.tsx +61 -13
  15. package/apps/dashboard/app/signup/page.tsx +242 -0
  16. package/apps/dashboard/app/subscribe/page.tsx +0 -2
  17. package/apps/dashboard/app/tests/page.tsx +37 -4
  18. package/apps/dashboard/app/welcome/page.tsx +13 -16
  19. package/apps/dashboard/app/work/[id]/page.tsx +117 -118
  20. package/apps/dashboard/app/work/[id]/proof/page.tsx +1489 -0
  21. package/apps/dashboard/components/AppShell.tsx +92 -85
  22. package/apps/dashboard/components/CardMenu.tsx +45 -12
  23. package/apps/dashboard/components/ClaudePanel.tsx +771 -850
  24. package/apps/dashboard/components/ClaudePanelInput.tsx +43 -15
  25. package/apps/dashboard/components/ConnectClaudeScreen.tsx +17 -34
  26. package/apps/dashboard/components/CopyableId.tsx +3 -4
  27. package/apps/dashboard/components/DetailReviewActions.tsx +100 -0
  28. package/apps/dashboard/components/DragContext.tsx +134 -63
  29. package/apps/dashboard/components/DraggableCard.tsx +3 -5
  30. package/apps/dashboard/components/DropZone.tsx +6 -7
  31. package/apps/dashboard/components/EditableDetailDescription.tsx +7 -13
  32. package/apps/dashboard/components/EditableDetailTitle.tsx +6 -13
  33. package/apps/dashboard/components/EditableTitle.tsx +26 -7
  34. package/apps/dashboard/components/ElapsedTimer.tsx +66 -0
  35. package/apps/dashboard/components/EpicGroup.tsx +359 -0
  36. package/apps/dashboard/components/GateCard.tsx +79 -17
  37. package/apps/dashboard/components/GateChoiceCard.tsx +15 -18
  38. package/apps/dashboard/components/InstallClaudeScreen.tsx +15 -32
  39. package/apps/dashboard/components/JettyLoader.tsx +37 -0
  40. package/apps/dashboard/components/KanbanBoard.tsx +368 -958
  41. package/apps/dashboard/components/KanbanCard.tsx +740 -0
  42. package/apps/dashboard/components/LazyCard.tsx +62 -0
  43. package/apps/dashboard/components/LazyMarkdown.tsx +11 -0
  44. package/apps/dashboard/components/MainNav.tsx +38 -73
  45. package/apps/dashboard/components/MessageBlock.tsx +468 -0
  46. package/apps/dashboard/components/ModeStartCard.tsx +15 -16
  47. package/apps/dashboard/components/OnboardingWelcome.tsx +213 -0
  48. package/apps/dashboard/components/PlaceholderCard.tsx +3 -4
  49. package/apps/dashboard/components/ProjectSwitcher.tsx +30 -30
  50. package/apps/dashboard/components/PrototypeTimeline.tsx +72 -51
  51. package/apps/dashboard/components/RealTimeKanbanWrapper.tsx +406 -388
  52. package/apps/dashboard/components/RealTimeTestsWrapper.tsx +373 -235
  53. package/apps/dashboard/components/ReviewFooter.tsx +139 -0
  54. package/apps/dashboard/components/SessionList.tsx +19 -19
  55. package/apps/dashboard/components/SubscribeContent.tsx +91 -47
  56. package/apps/dashboard/components/TestTree.tsx +16 -16
  57. package/apps/dashboard/components/TipCard.tsx +16 -17
  58. package/apps/dashboard/components/Toast.tsx +5 -6
  59. package/apps/dashboard/components/TypeIcon.tsx +55 -0
  60. package/apps/dashboard/components/ViewModeToolbar.tsx +104 -0
  61. package/apps/dashboard/components/WaveCompletionAnimation.tsx +52 -65
  62. package/apps/dashboard/components/WelcomeScreen.tsx +19 -35
  63. package/apps/dashboard/components/WorkItemHeader.tsx +4 -5
  64. package/apps/dashboard/components/WorkItemTree.tsx +11 -32
  65. package/apps/dashboard/components/settings/AccountSection.tsx +55 -35
  66. package/apps/dashboard/components/settings/AiContextSection.tsx +89 -0
  67. package/apps/dashboard/components/settings/ContextDocumentsSection.tsx +317 -0
  68. package/apps/dashboard/components/settings/EnvVarsSection.tsx +74 -152
  69. package/apps/dashboard/components/settings/GeneralSection.tsx +162 -56
  70. package/apps/dashboard/components/settings/ProjectStackSection.tsx +948 -0
  71. package/apps/dashboard/components/settings/SettingsLayout.tsx +4 -5
  72. package/apps/dashboard/components/ui/Button.tsx +104 -0
  73. package/apps/dashboard/components/ui/Input.tsx +78 -0
  74. package/apps/dashboard/components.json +1 -1
  75. package/apps/dashboard/contexts/ClaudeSessionContext.tsx +711 -418
  76. package/apps/dashboard/contexts/ConnectionStatusContext.tsx +25 -5
  77. package/apps/dashboard/contexts/UsageContext.tsx +87 -32
  78. package/apps/dashboard/dev.sh +35 -0
  79. package/apps/dashboard/eslint.config.mjs +9 -9
  80. package/apps/dashboard/hooks/useKanbanAnimation.ts +29 -0
  81. package/apps/dashboard/hooks/useKanbanUndo.ts +83 -0
  82. package/apps/dashboard/hooks/useWebSocket.ts +138 -83
  83. package/apps/dashboard/index.html +73 -0
  84. package/apps/dashboard/lib/constants.ts +43 -0
  85. package/apps/dashboard/lib/data-bridge.ts +722 -0
  86. package/apps/dashboard/lib/db.ts +69 -1265
  87. package/apps/dashboard/lib/environment-config.ts +173 -0
  88. package/apps/dashboard/lib/environment-verification.ts +119 -0
  89. package/apps/dashboard/lib/kanban-utils.ts +270 -0
  90. package/apps/dashboard/lib/proof-run.ts +495 -0
  91. package/apps/dashboard/lib/proof-scenario-runner.ts +346 -0
  92. package/apps/dashboard/lib/run-migrations.js +27 -2
  93. package/apps/dashboard/lib/service-recovery.ts +326 -0
  94. package/apps/dashboard/lib/session-state-machine.ts +1 -0
  95. package/apps/dashboard/lib/session-state-utils.ts +0 -164
  96. package/apps/dashboard/lib/session-stream-manager.ts +308 -134
  97. package/apps/dashboard/lib/shadows.ts +7 -0
  98. package/apps/dashboard/lib/stream-manager-registry.ts +46 -6
  99. package/apps/dashboard/lib/tauri-bridge.ts +102 -0
  100. package/apps/dashboard/lib/tauri.ts +106 -0
  101. package/apps/dashboard/lib/utils.ts +6 -0
  102. package/apps/dashboard/next-env.d.ts +1 -1
  103. package/apps/dashboard/package.json +21 -32
  104. package/apps/dashboard/public/bug-icon.png +0 -0
  105. package/apps/dashboard/public/buoy-icon.png +0 -0
  106. package/apps/dashboard/public/fonts/Satoshi-Variable.woff2 +0 -0
  107. package/apps/dashboard/public/fonts/Satoshi-VariableItalic.woff2 +0 -0
  108. package/apps/dashboard/public/in-flight-seagull.png +0 -0
  109. package/apps/dashboard/public/jetty-icon-loading-alt.svg +11 -0
  110. package/apps/dashboard/public/jetty-icon-loading.svg +11 -0
  111. package/apps/dashboard/public/jettypod_logo.png +0 -0
  112. package/apps/dashboard/public/pier-icon.png +0 -0
  113. package/apps/dashboard/public/star-icon.png +0 -0
  114. package/apps/dashboard/public/wrench-icon.png +0 -0
  115. package/apps/dashboard/scripts/tauri-build.js +228 -0
  116. package/apps/dashboard/scripts/upload-tauri-to-r2.js +125 -0
  117. package/apps/dashboard/scripts/ws-server.js +191 -0
  118. package/apps/dashboard/src/main.tsx +12 -0
  119. package/apps/dashboard/src/router.tsx +107 -0
  120. package/apps/dashboard/src/vite-env.d.ts +1 -0
  121. package/apps/dashboard/tsconfig.json +7 -12
  122. package/apps/dashboard/tsconfig.tsbuildinfo +1 -1
  123. package/apps/dashboard/vite.config.ts +33 -0
  124. package/apps/update-server/src/index.ts +228 -80
  125. package/claude-hooks/global-guardrails.js +14 -13
  126. package/crates/jettypod-cli/Cargo.toml +19 -0
  127. package/crates/jettypod-cli/src/commands.rs +1249 -0
  128. package/crates/jettypod-cli/src/main.rs +595 -0
  129. package/crates/jettypod-core/Cargo.toml +26 -0
  130. package/crates/jettypod-core/build.rs +98 -0
  131. package/crates/jettypod-core/migrations/V1__baseline.sql +197 -0
  132. package/crates/jettypod-core/migrations/V2__work_items_indexes.sql +6 -0
  133. package/crates/jettypod-core/migrations/V3__qa_steps.sql +2 -0
  134. package/crates/jettypod-core/src/auth.rs +294 -0
  135. package/crates/jettypod-core/src/config.rs +397 -0
  136. package/crates/jettypod-core/src/db/mod.rs +507 -0
  137. package/crates/jettypod-core/src/db/recovery.rs +114 -0
  138. package/crates/jettypod-core/src/db/startup.rs +101 -0
  139. package/crates/jettypod-core/src/db/validate.rs +149 -0
  140. package/crates/jettypod-core/src/error.rs +76 -0
  141. package/crates/jettypod-core/src/git.rs +458 -0
  142. package/crates/jettypod-core/src/lib.rs +20 -0
  143. package/crates/jettypod-core/src/sessions.rs +625 -0
  144. package/crates/jettypod-core/src/skills.rs +556 -0
  145. package/crates/jettypod-core/src/work.rs +1086 -0
  146. package/crates/jettypod-core/src/worktree.rs +628 -0
  147. package/crates/jettypod-core/src/ws.rs +767 -0
  148. package/cucumber-test.cjs +6 -0
  149. package/cucumber.js +9 -3
  150. package/docs/COMMAND_REFERENCE.md +34 -0
  151. package/hooks/post-checkout +32 -75
  152. package/hooks/post-merge +111 -10
  153. package/jest.setup.js +1 -0
  154. package/jettypod.js +145 -116
  155. package/lib/bdd-preflight.js +96 -0
  156. package/lib/chore-taxonomy.js +33 -10
  157. package/lib/database.js +36 -16
  158. package/lib/db-watcher.js +1 -1
  159. package/lib/git-hooks/pre-commit +1 -1
  160. package/lib/jettypod-backup.js +27 -4
  161. package/lib/merge-lock.js +111 -253
  162. package/lib/migrations/027-plan-at-creation-column.js +3 -1
  163. package/lib/migrations/029-remove-autoincrement.js +307 -0
  164. package/lib/migrations/029-rename-corrupted-to-cleaned.js +149 -0
  165. package/lib/migrations/030-rejection-round-columns.js +54 -0
  166. package/lib/migrations/031-session-isolation-index.js +17 -0
  167. package/lib/migrations/index.js +47 -4
  168. package/lib/schema.js +10 -5
  169. package/lib/seed-onboarding.js +1 -1
  170. package/lib/update-command/index.js +9 -175
  171. package/lib/work-commands/index.js +144 -19
  172. package/lib/work-tracking/index.js +148 -27
  173. package/lib/worktree-diagnostics.js +16 -16
  174. package/lib/worktree-facade.js +1 -1
  175. package/lib/worktree-manager.js +8 -8
  176. package/lib/worktree-reconciler.js +5 -5
  177. package/package.json +9 -2
  178. package/scripts/ndjson-to-cucumber-json.js +152 -0
  179. package/scripts/postinstall.js +25 -0
  180. package/skills-templates/bug-mode/SKILL.md +79 -20
  181. package/skills-templates/bug-planning/SKILL.md +25 -29
  182. package/skills-templates/chore-mode/SKILL.md +171 -69
  183. package/skills-templates/chore-mode/verification.js +51 -10
  184. package/skills-templates/chore-planning/SKILL.md +47 -18
  185. package/skills-templates/design-system-selection/SKILL.md +273 -0
  186. package/skills-templates/epic-planning/SKILL.md +82 -48
  187. package/skills-templates/external-transition/SKILL.md +47 -47
  188. package/skills-templates/feature-planning/SKILL.md +173 -74
  189. package/skills-templates/production-mode/SKILL.md +69 -49
  190. package/skills-templates/request-routing/SKILL.md +4 -4
  191. package/skills-templates/simple-improvement/SKILL.md +74 -29
  192. package/skills-templates/speed-mode/SKILL.md +217 -141
  193. package/skills-templates/stable-mode/SKILL.md +148 -89
  194. package/apps/dashboard/README.md +0 -36
  195. package/apps/dashboard/app/api/claude/[workItemId]/message/route.ts +0 -386
  196. package/apps/dashboard/app/api/claude/[workItemId]/pin/route.ts +0 -24
  197. package/apps/dashboard/app/api/claude/[workItemId]/route.ts +0 -167
  198. package/apps/dashboard/app/api/claude/sessions/[sessionId]/content/route.ts +0 -52
  199. package/apps/dashboard/app/api/claude/sessions/[sessionId]/message/route.ts +0 -378
  200. package/apps/dashboard/app/api/claude/sessions/[sessionId]/pin/route.ts +0 -24
  201. package/apps/dashboard/app/api/claude/sessions/cleanup/route.ts +0 -34
  202. package/apps/dashboard/app/api/claude/sessions/route.ts +0 -184
  203. package/apps/dashboard/app/api/decisions/[id]/route.ts +0 -25
  204. package/apps/dashboard/app/api/internal/set-project/route.ts +0 -17
  205. package/apps/dashboard/app/api/kanban/route.ts +0 -15
  206. package/apps/dashboard/app/api/settings/env-vars/route.ts +0 -125
  207. package/apps/dashboard/app/api/settings/general/route.ts +0 -21
  208. package/apps/dashboard/app/api/tests/route.ts +0 -9
  209. package/apps/dashboard/app/api/tests/run/route.ts +0 -82
  210. package/apps/dashboard/app/api/tests/run/stream/route.ts +0 -71
  211. package/apps/dashboard/app/api/tests/undefined/route.ts +0 -9
  212. package/apps/dashboard/app/api/usage/route.ts +0 -17
  213. package/apps/dashboard/app/api/work/[id]/description/route.ts +0 -21
  214. package/apps/dashboard/app/api/work/[id]/epic/route.ts +0 -21
  215. package/apps/dashboard/app/api/work/[id]/order/route.ts +0 -21
  216. package/apps/dashboard/app/api/work/[id]/status/route.ts +0 -21
  217. package/apps/dashboard/app/api/work/[id]/title/route.ts +0 -21
  218. package/apps/dashboard/app/layout.tsx +0 -43
  219. package/apps/dashboard/components/UpgradeBanner.tsx +0 -29
  220. package/apps/dashboard/electron/ipc-handlers.js +0 -1028
  221. package/apps/dashboard/electron/main.js +0 -2124
  222. package/apps/dashboard/electron/preload.js +0 -123
  223. package/apps/dashboard/electron/session-manager.js +0 -141
  224. package/apps/dashboard/electron-builder.config.js +0 -357
  225. package/apps/dashboard/hooks/useClaudeSessions.ts +0 -299
  226. package/apps/dashboard/lib/claude-process-manager.ts +0 -492
  227. package/apps/dashboard/lib/db-bridge.ts +0 -282
  228. package/apps/dashboard/lib/prototypes.ts +0 -202
  229. package/apps/dashboard/lib/test-results-db.ts +0 -307
  230. package/apps/dashboard/lib/tests.ts +0 -282
  231. package/apps/dashboard/next.config.js +0 -50
  232. package/apps/dashboard/postcss.config.mjs +0 -7
  233. package/apps/dashboard/public/file.svg +0 -1
  234. package/apps/dashboard/public/globe.svg +0 -1
  235. package/apps/dashboard/public/next.svg +0 -1
  236. package/apps/dashboard/public/vercel.svg +0 -1
  237. package/apps/dashboard/public/window.svg +0 -1
  238. package/apps/dashboard/scripts/download-node.js +0 -104
  239. package/apps/dashboard/scripts/upload-to-r2.js +0 -89
  240. package/docs/bdd-guidance.md +0 -390
@@ -0,0 +1,595 @@
1
+ use std::path::PathBuf;
2
+
3
+ use anyhow::{bail, Result};
4
+ use clap::{Parser, Subcommand};
5
+
6
+ mod commands;
7
+
8
+ use commands::CommandContext;
9
+
10
+ /// Find the project root by walking up from cwd looking for `.jettypod/`.
11
+ fn find_project_root() -> Result<PathBuf> {
12
+ let mut dir = std::env::current_dir()?;
13
+ loop {
14
+ if dir.join(".jettypod").exists() {
15
+ return Ok(dir);
16
+ }
17
+ if !dir.pop() {
18
+ bail!("Not in a JettyPod project (no .jettypod/ found). Run `jettypod init` first.");
19
+ }
20
+ }
21
+ }
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // CLI structure
25
+ // ---------------------------------------------------------------------------
26
+
27
+ #[derive(Parser)]
28
+ #[command(name = "jettypod", version, about = "JettyPod workflow CLI")]
29
+ struct Cli {
30
+ #[command(subcommand)]
31
+ command: Option<Commands>,
32
+ }
33
+
34
+ #[derive(Subcommand)]
35
+ enum Commands {
36
+ /// Initialize a new JettyPod project in the current directory.
37
+ Init {
38
+ /// Project name (defaults to directory name).
39
+ name: Option<String>,
40
+ },
41
+
42
+ /// Set the project description.
43
+ Describe {
44
+ /// Description text.
45
+ #[arg(num_args = 1.., required = true)]
46
+ description: Vec<String>,
47
+ },
48
+
49
+ /// Regenerate CLAUDE.md from config.
50
+ Generate,
51
+
52
+ /// Manage work items (create, start, stop, merge, etc).
53
+ Work {
54
+ #[command(subcommand)]
55
+ action: WorkAction,
56
+ },
57
+
58
+ /// Show the project backlog.
59
+ Backlog {
60
+ /// Expand specific IDs (comma-separated).
61
+ #[arg(long)]
62
+ expand: Option<String>,
63
+
64
+ /// Expand all items.
65
+ #[arg(long)]
66
+ expand_all: bool,
67
+ },
68
+
69
+ /// View and manage architectural decisions.
70
+ Decisions {
71
+ /// Show all decisions.
72
+ #[arg(long)]
73
+ all: bool,
74
+
75
+ /// Show project-level decisions only.
76
+ #[arg(long)]
77
+ project: bool,
78
+
79
+ /// Show all epic decisions.
80
+ #[arg(long)]
81
+ epics: bool,
82
+
83
+ /// Show decisions for a specific epic.
84
+ #[arg(long)]
85
+ epic: Option<i64>,
86
+
87
+ /// Show decisions file.
88
+ #[arg(long)]
89
+ view: bool,
90
+ },
91
+
92
+ /// Manage project settings.
93
+ Project {
94
+ #[command(subcommand)]
95
+ action: ProjectAction,
96
+ },
97
+
98
+ /// Emit a UI gate marker (for dashboard narrated mode).
99
+ Ui {
100
+ #[command(subcommand)]
101
+ action: UiAction,
102
+ },
103
+
104
+ /// Check what files are affected by changes.
105
+ Impact {
106
+ /// File path to check.
107
+ file: String,
108
+ },
109
+
110
+ /// Manage workflow state.
111
+ Workflow {
112
+ #[command(subcommand)]
113
+ action: WorkflowAction,
114
+ },
115
+ }
116
+
117
+ // ---------------------------------------------------------------------------
118
+ // Work subcommands
119
+ // ---------------------------------------------------------------------------
120
+
121
+ #[derive(Subcommand)]
122
+ enum WorkAction {
123
+ /// Create a new work item.
124
+ Create {
125
+ /// Create from JSON file.
126
+ #[arg(long)]
127
+ from: Option<String>,
128
+
129
+ /// Work item type (epic, feature, chore, bug).
130
+ #[arg(value_name = "TYPE")]
131
+ item_type: Option<String>,
132
+
133
+ /// Title.
134
+ title: Option<String>,
135
+
136
+ /// Description.
137
+ description: Option<String>,
138
+
139
+ /// Parent work item ID.
140
+ #[arg(long)]
141
+ parent: Option<i64>,
142
+
143
+ /// Initial mode (speed, stable, production).
144
+ #[arg(long)]
145
+ mode: Option<String>,
146
+
147
+ /// Mark as needing discovery.
148
+ #[arg(long)]
149
+ needs_discovery: bool,
150
+ },
151
+
152
+ /// Start working on an item (creates worktree).
153
+ Start {
154
+ /// Work item ID.
155
+ id: i64,
156
+ },
157
+
158
+ /// Stop working on the current item.
159
+ Stop {
160
+ /// Status to set (in_progress, blocked, done, skip).
161
+ status: Option<String>,
162
+ },
163
+
164
+ /// Update a work item's status.
165
+ Status {
166
+ /// Work item ID.
167
+ id: i64,
168
+ /// New status.
169
+ status: String,
170
+ },
171
+
172
+ /// Show the current work item.
173
+ Current,
174
+
175
+ /// Show details of a work item.
176
+ Show {
177
+ /// Work item ID.
178
+ id: i64,
179
+ },
180
+
181
+ /// Update a work item's description.
182
+ Describe {
183
+ /// Work item ID.
184
+ id: i64,
185
+ /// Description text.
186
+ #[arg(num_args = 1.., required = true)]
187
+ description: Vec<String>,
188
+ },
189
+
190
+ /// Get children of a work item.
191
+ Children {
192
+ /// Parent work item ID.
193
+ id: i64,
194
+ },
195
+
196
+ /// Merge a worktree back to main.
197
+ Merge {
198
+ /// Work item ID (auto-detects if in worktree).
199
+ id: Option<i64>,
200
+ /// Force release merge lock.
201
+ #[arg(long)]
202
+ release_lock: bool,
203
+ },
204
+
205
+ /// Clean up a worktree after merge.
206
+ Cleanup {
207
+ /// Work item ID (batch cleanup if omitted).
208
+ id: Option<i64>,
209
+ /// Force cleanup even if merge not complete.
210
+ #[arg(long)]
211
+ force: bool,
212
+ /// Preview what would be cleaned.
213
+ #[arg(long)]
214
+ dry_run: bool,
215
+ },
216
+
217
+ /// Set a work item's mode.
218
+ SetMode {
219
+ /// Work item ID.
220
+ id: i64,
221
+ /// Mode (discovery, speed, stable, production).
222
+ mode: String,
223
+ },
224
+
225
+ /// Elevate a work item to a higher mode.
226
+ Elevate {
227
+ /// Work item ID.
228
+ id: i64,
229
+ /// Target mode (stable, production).
230
+ mode: String,
231
+ },
232
+
233
+ /// Manage test worktrees.
234
+ Tests {
235
+ #[command(subcommand)]
236
+ action: Option<TestAction>,
237
+
238
+ /// Feature ID (for shorthand `work tests <id>`).
239
+ #[arg(value_name = "FEATURE_ID")]
240
+ id: Option<i64>,
241
+ },
242
+
243
+ /// Manage prototype worktrees.
244
+ Prototype {
245
+ #[command(subcommand)]
246
+ action: PrototypeAction,
247
+ },
248
+
249
+ /// Record an epic architectural decision.
250
+ EpicImplement {
251
+ /// Epic ID.
252
+ id: i64,
253
+ /// Decision aspect/category.
254
+ #[arg(long)]
255
+ aspect: String,
256
+ /// Chosen approach.
257
+ #[arg(long)]
258
+ decision: String,
259
+ /// Rationale for the choice.
260
+ #[arg(long)]
261
+ rationale: String,
262
+ /// Prototype file paths (comma-separated).
263
+ #[arg(long)]
264
+ prototypes: Option<String>,
265
+ },
266
+
267
+ /// Set QA checklist steps for a work item (JSON).
268
+ SetQaSteps {
269
+ /// Work item ID.
270
+ id: i64,
271
+ /// Read QA steps JSON from file.
272
+ #[arg(long)]
273
+ from: Option<String>,
274
+ /// QA steps JSON string (alternative to --from).
275
+ steps: Option<String>,
276
+ },
277
+
278
+ /// Transition a feature from discovery to implementation.
279
+ Implement {
280
+ /// Feature ID.
281
+ id: i64,
282
+ /// Prototype file paths (comma-separated).
283
+ #[arg(long)]
284
+ prototypes: Option<String>,
285
+ /// Winning prototype path.
286
+ #[arg(long)]
287
+ winner: Option<String>,
288
+ },
289
+ }
290
+
291
+ #[derive(Subcommand)]
292
+ enum TestAction {
293
+ /// Start a test worktree.
294
+ Start {
295
+ /// Feature ID.
296
+ id: i64,
297
+ },
298
+ /// Merge test worktree.
299
+ Merge {
300
+ /// Feature ID.
301
+ id: i64,
302
+ },
303
+ }
304
+
305
+ #[derive(Subcommand)]
306
+ enum PrototypeAction {
307
+ /// Start a prototype worktree.
308
+ Start {
309
+ /// Feature ID.
310
+ id: i64,
311
+ /// Approach name.
312
+ approach: String,
313
+ },
314
+ /// Merge prototype worktree.
315
+ Merge {
316
+ /// Feature ID.
317
+ id: i64,
318
+ },
319
+ }
320
+
321
+ // ---------------------------------------------------------------------------
322
+ // Project subcommands
323
+ // ---------------------------------------------------------------------------
324
+
325
+ #[derive(Subcommand)]
326
+ enum ProjectAction {
327
+ /// Show current project state.
328
+ State,
329
+ /// Show project info.
330
+ Info,
331
+ /// Transition project to external state.
332
+ External,
333
+ /// Manage project discovery.
334
+ Discover {
335
+ #[command(subcommand)]
336
+ action: DiscoverAction,
337
+ },
338
+ /// Manage project prototypes.
339
+ Prototype {
340
+ #[command(subcommand)]
341
+ action: ProjectPrototypeAction,
342
+ },
343
+ }
344
+
345
+ #[derive(Subcommand)]
346
+ enum DiscoverAction {
347
+ /// Start discovery phase.
348
+ Start,
349
+ /// Complete discovery phase.
350
+ Complete {
351
+ /// Winning approach path.
352
+ #[arg(long)]
353
+ winner: String,
354
+ /// Rationale for choice.
355
+ #[arg(long)]
356
+ rationale: String,
357
+ /// Prototype file paths (comma-separated).
358
+ #[arg(long)]
359
+ prototypes: Option<String>,
360
+ },
361
+ }
362
+
363
+ #[derive(Subcommand)]
364
+ enum ProjectPrototypeAction {
365
+ /// Start a project prototype.
366
+ Start {
367
+ /// Approach name.
368
+ approach: String,
369
+ },
370
+ /// Merge project prototype.
371
+ Merge,
372
+ }
373
+
374
+ // ---------------------------------------------------------------------------
375
+ // UI subcommands
376
+ // ---------------------------------------------------------------------------
377
+
378
+ #[derive(Subcommand)]
379
+ enum UiAction {
380
+ /// Emit a gate marker.
381
+ Gate {
382
+ /// Gate type.
383
+ #[arg(value_name = "TYPE")]
384
+ gate_type: String,
385
+ /// Optional JSON data.
386
+ #[arg(long)]
387
+ data: Option<String>,
388
+ },
389
+ }
390
+
391
+ // ---------------------------------------------------------------------------
392
+ // Workflow subcommands
393
+ // ---------------------------------------------------------------------------
394
+
395
+ #[derive(Subcommand)]
396
+ enum WorkflowAction {
397
+ /// Start a workflow execution.
398
+ Start {
399
+ /// Skill name.
400
+ skill: String,
401
+ /// Work item ID.
402
+ id: i64,
403
+ },
404
+ /// Complete a workflow execution.
405
+ Complete {
406
+ /// Skill name.
407
+ skill: String,
408
+ /// Work item ID.
409
+ id: i64,
410
+ },
411
+ /// Record a workflow checkpoint.
412
+ Checkpoint {
413
+ /// Work item ID.
414
+ id: i64,
415
+ /// Step number.
416
+ #[arg(long)]
417
+ step: i64,
418
+ },
419
+ }
420
+
421
+ // ---------------------------------------------------------------------------
422
+ // Main
423
+ // ---------------------------------------------------------------------------
424
+
425
+ #[tokio::main]
426
+ async fn main() -> Result<()> {
427
+ let cli = Cli::parse();
428
+
429
+ match cli.command {
430
+ None => {
431
+ // No subcommand: check if project exists, init or launch dashboard
432
+ match find_project_root() {
433
+ Ok(root) => {
434
+ let ctx = CommandContext::new(&root)?;
435
+ commands::launch_dashboard(&ctx).await
436
+ }
437
+ Err(_) => {
438
+ eprintln!("Not in a JettyPod project. Run `jettypod init` to get started.");
439
+ Ok(())
440
+ }
441
+ }
442
+ }
443
+ Some(cmd) => dispatch(cmd).await,
444
+ }
445
+ }
446
+
447
+ async fn dispatch(cmd: Commands) -> Result<()> {
448
+ match cmd {
449
+ Commands::Init { name } => commands::init(name),
450
+
451
+ Commands::Describe { description } => {
452
+ let root = find_project_root()?;
453
+ commands::describe(&root, &description.join(" "))
454
+ }
455
+
456
+ Commands::Generate => {
457
+ let root = find_project_root()?;
458
+ commands::generate(&root)
459
+ }
460
+
461
+ Commands::Work { action } => {
462
+ let root = find_project_root()?;
463
+ let ctx = CommandContext::new(&root)?;
464
+ dispatch_work(&ctx, action)
465
+ }
466
+
467
+ Commands::Backlog {
468
+ expand,
469
+ expand_all,
470
+ } => {
471
+ let root = find_project_root()?;
472
+ let ctx = CommandContext::new(&root)?;
473
+ commands::backlog(&ctx, expand.as_deref(), expand_all)
474
+ }
475
+
476
+ Commands::Decisions {
477
+ all,
478
+ project,
479
+ epics,
480
+ epic,
481
+ view,
482
+ } => {
483
+ let root = find_project_root()?;
484
+ let ctx = CommandContext::new(&root)?;
485
+ commands::decisions(&ctx, all, project, epics, epic, view)
486
+ }
487
+
488
+ Commands::Project { action } => {
489
+ let root = find_project_root()?;
490
+ let ctx = CommandContext::new(&root)?;
491
+ dispatch_project(&ctx, action)
492
+ }
493
+
494
+ Commands::Ui { action } => dispatch_ui(action),
495
+
496
+ Commands::Impact { file } => {
497
+ let root = find_project_root()?;
498
+ let ctx = CommandContext::new(&root)?;
499
+ commands::impact(&ctx, &file)
500
+ }
501
+
502
+ Commands::Workflow { action } => {
503
+ let root = find_project_root()?;
504
+ let ctx = CommandContext::new(&root)?;
505
+ dispatch_workflow(&ctx, action)
506
+ }
507
+ }
508
+ }
509
+
510
+ fn dispatch_work(ctx: &CommandContext, action: WorkAction) -> Result<()> {
511
+ match action {
512
+ WorkAction::Create {
513
+ from,
514
+ item_type,
515
+ title,
516
+ description,
517
+ parent,
518
+ mode,
519
+ needs_discovery,
520
+ } => commands::work_create(ctx, from, item_type, title, description, parent, mode, needs_discovery),
521
+
522
+ WorkAction::Start { id } => commands::work_start(ctx, id),
523
+ WorkAction::Stop { status } => commands::work_stop(ctx, status),
524
+ WorkAction::Status { id, status } => commands::work_status(ctx, id, &status),
525
+ WorkAction::Current => commands::work_current(ctx),
526
+ WorkAction::Show { id } => commands::work_show(ctx, id),
527
+ WorkAction::Describe { id, description } => {
528
+ commands::work_describe(ctx, id, &description.join(" "))
529
+ }
530
+ WorkAction::Children { id } => commands::work_children(ctx, id),
531
+ WorkAction::Merge { id, release_lock } => commands::work_merge(ctx, id, release_lock),
532
+ WorkAction::Cleanup {
533
+ id,
534
+ force,
535
+ dry_run,
536
+ } => commands::work_cleanup(ctx, id, force, dry_run),
537
+ WorkAction::SetMode { id, mode } => commands::work_set_mode(ctx, id, &mode),
538
+ WorkAction::Elevate { id, mode } => commands::work_elevate(ctx, id, &mode),
539
+ WorkAction::Tests { action, id } => commands::work_tests(ctx, action, id),
540
+ WorkAction::Prototype { action } => commands::work_prototype(ctx, action),
541
+ WorkAction::EpicImplement {
542
+ id,
543
+ aspect,
544
+ decision,
545
+ rationale,
546
+ prototypes,
547
+ } => commands::work_epic_implement(ctx, id, &aspect, &decision, &rationale, prototypes),
548
+ WorkAction::SetQaSteps { id, from, steps } => commands::work_set_qa_steps(ctx, id, from, steps),
549
+ WorkAction::Implement {
550
+ id,
551
+ prototypes,
552
+ winner,
553
+ } => commands::work_implement(ctx, id, prototypes, winner),
554
+ }
555
+ }
556
+
557
+ fn dispatch_project(ctx: &CommandContext, action: ProjectAction) -> Result<()> {
558
+ match action {
559
+ ProjectAction::State => commands::project_state(ctx),
560
+ ProjectAction::Info => commands::project_info(ctx),
561
+ ProjectAction::External => commands::project_external(ctx),
562
+ ProjectAction::Discover { action } => match action {
563
+ DiscoverAction::Start => commands::project_discover_start(ctx),
564
+ DiscoverAction::Complete {
565
+ winner,
566
+ rationale,
567
+ prototypes,
568
+ } => commands::project_discover_complete(ctx, &winner, &rationale, prototypes),
569
+ },
570
+ ProjectAction::Prototype { action } => match action {
571
+ ProjectPrototypeAction::Start { approach } => {
572
+ commands::project_prototype_start(ctx, &approach)
573
+ }
574
+ ProjectPrototypeAction::Merge => commands::project_prototype_merge(ctx),
575
+ },
576
+ }
577
+ }
578
+
579
+ fn dispatch_ui(action: UiAction) -> Result<()> {
580
+ match action {
581
+ UiAction::Gate { gate_type, data } => {
582
+ let json = data.unwrap_or_else(|| "{}".to_string());
583
+ println!("[GATE:{gate_type}]{json}[/GATE]");
584
+ Ok(())
585
+ }
586
+ }
587
+ }
588
+
589
+ fn dispatch_workflow(ctx: &CommandContext, action: WorkflowAction) -> Result<()> {
590
+ match action {
591
+ WorkflowAction::Start { skill, id } => commands::workflow_start(ctx, &skill, id),
592
+ WorkflowAction::Complete { skill, id } => commands::workflow_complete(ctx, &skill, id),
593
+ WorkflowAction::Checkpoint { id, step } => commands::workflow_checkpoint(ctx, id, step),
594
+ }
595
+ }
@@ -0,0 +1,26 @@
1
+ [package]
2
+ name = "jettypod-core"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ description = "JettyPod core business logic — database, work tracking, git, config, auth"
6
+
7
+ [dependencies]
8
+ rusqlite = { version = "0.31", features = ["bundled", "hooks"] }
9
+ refinery = { version = "0.8", features = ["rusqlite"] }
10
+ serde = { workspace = true }
11
+ serde_json = { workspace = true }
12
+ tokio = { workspace = true, features = ["rt-multi-thread", "macros", "sync", "net", "time", "process", "io-util"] }
13
+ anyhow = { workspace = true }
14
+ thiserror = { workspace = true }
15
+ chrono = { workspace = true }
16
+ log = { workspace = true }
17
+ jsonwebtoken = "9"
18
+ url = { workspace = true }
19
+ dirs = "6"
20
+ strum = { version = "0.26", features = ["derive"] }
21
+ tokio-tungstenite = "0.24"
22
+ futures-util = "0.3"
23
+ notify = "6"
24
+
25
+ [dev-dependencies]
26
+ tempfile = "3"