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.
- package/.env +4 -3
- package/Cargo.lock +6450 -0
- package/Cargo.toml +35 -0
- package/README.md +5 -1
- package/TAURI-MIGRATION-PLAN.md +840 -0
- package/apps/dashboard/app/connect-claude/page.tsx +5 -6
- package/apps/dashboard/app/decision/[id]/page.tsx +63 -58
- package/apps/dashboard/app/demo/gates/page.tsx +43 -45
- package/apps/dashboard/app/design-system/page.tsx +868 -0
- package/apps/dashboard/app/globals.css +80 -4
- package/apps/dashboard/app/install-claude/page.tsx +4 -6
- package/apps/dashboard/app/login/page.tsx +72 -54
- package/apps/dashboard/app/page.tsx +101 -48
- package/apps/dashboard/app/settings/page.tsx +61 -13
- package/apps/dashboard/app/signup/page.tsx +242 -0
- package/apps/dashboard/app/subscribe/page.tsx +0 -2
- package/apps/dashboard/app/tests/page.tsx +37 -4
- package/apps/dashboard/app/welcome/page.tsx +13 -16
- package/apps/dashboard/app/work/[id]/page.tsx +117 -118
- package/apps/dashboard/app/work/[id]/proof/page.tsx +1489 -0
- package/apps/dashboard/components/AppShell.tsx +92 -85
- package/apps/dashboard/components/CardMenu.tsx +45 -12
- package/apps/dashboard/components/ClaudePanel.tsx +771 -850
- package/apps/dashboard/components/ClaudePanelInput.tsx +43 -15
- package/apps/dashboard/components/ConnectClaudeScreen.tsx +17 -34
- package/apps/dashboard/components/CopyableId.tsx +3 -4
- package/apps/dashboard/components/DetailReviewActions.tsx +100 -0
- package/apps/dashboard/components/DragContext.tsx +134 -63
- package/apps/dashboard/components/DraggableCard.tsx +3 -5
- package/apps/dashboard/components/DropZone.tsx +6 -7
- package/apps/dashboard/components/EditableDetailDescription.tsx +7 -13
- package/apps/dashboard/components/EditableDetailTitle.tsx +6 -13
- package/apps/dashboard/components/EditableTitle.tsx +26 -7
- package/apps/dashboard/components/ElapsedTimer.tsx +66 -0
- package/apps/dashboard/components/EpicGroup.tsx +359 -0
- package/apps/dashboard/components/GateCard.tsx +79 -17
- package/apps/dashboard/components/GateChoiceCard.tsx +15 -18
- package/apps/dashboard/components/InstallClaudeScreen.tsx +15 -32
- package/apps/dashboard/components/JettyLoader.tsx +37 -0
- package/apps/dashboard/components/KanbanBoard.tsx +368 -958
- package/apps/dashboard/components/KanbanCard.tsx +740 -0
- package/apps/dashboard/components/LazyCard.tsx +62 -0
- package/apps/dashboard/components/LazyMarkdown.tsx +11 -0
- package/apps/dashboard/components/MainNav.tsx +38 -73
- package/apps/dashboard/components/MessageBlock.tsx +468 -0
- package/apps/dashboard/components/ModeStartCard.tsx +15 -16
- package/apps/dashboard/components/OnboardingWelcome.tsx +213 -0
- package/apps/dashboard/components/PlaceholderCard.tsx +3 -4
- package/apps/dashboard/components/ProjectSwitcher.tsx +30 -30
- package/apps/dashboard/components/PrototypeTimeline.tsx +72 -51
- package/apps/dashboard/components/RealTimeKanbanWrapper.tsx +406 -388
- package/apps/dashboard/components/RealTimeTestsWrapper.tsx +373 -235
- package/apps/dashboard/components/ReviewFooter.tsx +139 -0
- package/apps/dashboard/components/SessionList.tsx +19 -19
- package/apps/dashboard/components/SubscribeContent.tsx +91 -47
- package/apps/dashboard/components/TestTree.tsx +16 -16
- package/apps/dashboard/components/TipCard.tsx +16 -17
- package/apps/dashboard/components/Toast.tsx +5 -6
- package/apps/dashboard/components/TypeIcon.tsx +55 -0
- package/apps/dashboard/components/ViewModeToolbar.tsx +104 -0
- package/apps/dashboard/components/WaveCompletionAnimation.tsx +52 -65
- package/apps/dashboard/components/WelcomeScreen.tsx +19 -35
- package/apps/dashboard/components/WorkItemHeader.tsx +4 -5
- package/apps/dashboard/components/WorkItemTree.tsx +11 -32
- package/apps/dashboard/components/settings/AccountSection.tsx +55 -35
- package/apps/dashboard/components/settings/AiContextSection.tsx +89 -0
- package/apps/dashboard/components/settings/ContextDocumentsSection.tsx +317 -0
- package/apps/dashboard/components/settings/EnvVarsSection.tsx +74 -152
- package/apps/dashboard/components/settings/GeneralSection.tsx +162 -56
- package/apps/dashboard/components/settings/ProjectStackSection.tsx +948 -0
- package/apps/dashboard/components/settings/SettingsLayout.tsx +4 -5
- package/apps/dashboard/components/ui/Button.tsx +104 -0
- package/apps/dashboard/components/ui/Input.tsx +78 -0
- package/apps/dashboard/components.json +1 -1
- package/apps/dashboard/contexts/ClaudeSessionContext.tsx +711 -418
- package/apps/dashboard/contexts/ConnectionStatusContext.tsx +25 -5
- package/apps/dashboard/contexts/UsageContext.tsx +87 -32
- package/apps/dashboard/dev.sh +35 -0
- package/apps/dashboard/eslint.config.mjs +9 -9
- package/apps/dashboard/hooks/useKanbanAnimation.ts +29 -0
- package/apps/dashboard/hooks/useKanbanUndo.ts +83 -0
- package/apps/dashboard/hooks/useWebSocket.ts +138 -83
- package/apps/dashboard/index.html +73 -0
- package/apps/dashboard/lib/constants.ts +43 -0
- package/apps/dashboard/lib/data-bridge.ts +722 -0
- package/apps/dashboard/lib/db.ts +69 -1265
- package/apps/dashboard/lib/environment-config.ts +173 -0
- package/apps/dashboard/lib/environment-verification.ts +119 -0
- package/apps/dashboard/lib/kanban-utils.ts +270 -0
- package/apps/dashboard/lib/proof-run.ts +495 -0
- package/apps/dashboard/lib/proof-scenario-runner.ts +346 -0
- package/apps/dashboard/lib/run-migrations.js +27 -2
- package/apps/dashboard/lib/service-recovery.ts +326 -0
- package/apps/dashboard/lib/session-state-machine.ts +1 -0
- package/apps/dashboard/lib/session-state-utils.ts +0 -164
- package/apps/dashboard/lib/session-stream-manager.ts +308 -134
- package/apps/dashboard/lib/shadows.ts +7 -0
- package/apps/dashboard/lib/stream-manager-registry.ts +46 -6
- package/apps/dashboard/lib/tauri-bridge.ts +102 -0
- package/apps/dashboard/lib/tauri.ts +106 -0
- package/apps/dashboard/lib/utils.ts +6 -0
- package/apps/dashboard/next-env.d.ts +1 -1
- package/apps/dashboard/package.json +21 -32
- package/apps/dashboard/public/bug-icon.png +0 -0
- package/apps/dashboard/public/buoy-icon.png +0 -0
- package/apps/dashboard/public/fonts/Satoshi-Variable.woff2 +0 -0
- package/apps/dashboard/public/fonts/Satoshi-VariableItalic.woff2 +0 -0
- package/apps/dashboard/public/in-flight-seagull.png +0 -0
- package/apps/dashboard/public/jetty-icon-loading-alt.svg +11 -0
- package/apps/dashboard/public/jetty-icon-loading.svg +11 -0
- package/apps/dashboard/public/jettypod_logo.png +0 -0
- package/apps/dashboard/public/pier-icon.png +0 -0
- package/apps/dashboard/public/star-icon.png +0 -0
- package/apps/dashboard/public/wrench-icon.png +0 -0
- package/apps/dashboard/scripts/tauri-build.js +228 -0
- package/apps/dashboard/scripts/upload-tauri-to-r2.js +125 -0
- package/apps/dashboard/scripts/ws-server.js +191 -0
- package/apps/dashboard/src/main.tsx +12 -0
- package/apps/dashboard/src/router.tsx +107 -0
- package/apps/dashboard/src/vite-env.d.ts +1 -0
- package/apps/dashboard/tsconfig.json +7 -12
- package/apps/dashboard/tsconfig.tsbuildinfo +1 -1
- package/apps/dashboard/vite.config.ts +33 -0
- package/apps/update-server/src/index.ts +228 -80
- package/claude-hooks/global-guardrails.js +14 -13
- package/crates/jettypod-cli/Cargo.toml +19 -0
- package/crates/jettypod-cli/src/commands.rs +1249 -0
- package/crates/jettypod-cli/src/main.rs +595 -0
- package/crates/jettypod-core/Cargo.toml +26 -0
- package/crates/jettypod-core/build.rs +98 -0
- package/crates/jettypod-core/migrations/V1__baseline.sql +197 -0
- package/crates/jettypod-core/migrations/V2__work_items_indexes.sql +6 -0
- package/crates/jettypod-core/migrations/V3__qa_steps.sql +2 -0
- package/crates/jettypod-core/src/auth.rs +294 -0
- package/crates/jettypod-core/src/config.rs +397 -0
- package/crates/jettypod-core/src/db/mod.rs +507 -0
- package/crates/jettypod-core/src/db/recovery.rs +114 -0
- package/crates/jettypod-core/src/db/startup.rs +101 -0
- package/crates/jettypod-core/src/db/validate.rs +149 -0
- package/crates/jettypod-core/src/error.rs +76 -0
- package/crates/jettypod-core/src/git.rs +458 -0
- package/crates/jettypod-core/src/lib.rs +20 -0
- package/crates/jettypod-core/src/sessions.rs +625 -0
- package/crates/jettypod-core/src/skills.rs +556 -0
- package/crates/jettypod-core/src/work.rs +1086 -0
- package/crates/jettypod-core/src/worktree.rs +628 -0
- package/crates/jettypod-core/src/ws.rs +767 -0
- package/cucumber-test.cjs +6 -0
- package/cucumber.js +9 -3
- package/docs/COMMAND_REFERENCE.md +34 -0
- package/hooks/post-checkout +32 -75
- package/hooks/post-merge +111 -10
- package/jest.setup.js +1 -0
- package/jettypod.js +145 -116
- package/lib/bdd-preflight.js +96 -0
- package/lib/chore-taxonomy.js +33 -10
- package/lib/database.js +36 -16
- package/lib/db-watcher.js +1 -1
- package/lib/git-hooks/pre-commit +1 -1
- package/lib/jettypod-backup.js +27 -4
- package/lib/merge-lock.js +111 -253
- package/lib/migrations/027-plan-at-creation-column.js +3 -1
- package/lib/migrations/029-remove-autoincrement.js +307 -0
- package/lib/migrations/029-rename-corrupted-to-cleaned.js +149 -0
- package/lib/migrations/030-rejection-round-columns.js +54 -0
- package/lib/migrations/031-session-isolation-index.js +17 -0
- package/lib/migrations/index.js +47 -4
- package/lib/schema.js +10 -5
- package/lib/seed-onboarding.js +1 -1
- package/lib/update-command/index.js +9 -175
- package/lib/work-commands/index.js +144 -19
- package/lib/work-tracking/index.js +148 -27
- package/lib/worktree-diagnostics.js +16 -16
- package/lib/worktree-facade.js +1 -1
- package/lib/worktree-manager.js +8 -8
- package/lib/worktree-reconciler.js +5 -5
- package/package.json +9 -2
- package/scripts/ndjson-to-cucumber-json.js +152 -0
- package/scripts/postinstall.js +25 -0
- package/skills-templates/bug-mode/SKILL.md +79 -20
- package/skills-templates/bug-planning/SKILL.md +25 -29
- package/skills-templates/chore-mode/SKILL.md +171 -69
- package/skills-templates/chore-mode/verification.js +51 -10
- package/skills-templates/chore-planning/SKILL.md +47 -18
- package/skills-templates/design-system-selection/SKILL.md +273 -0
- package/skills-templates/epic-planning/SKILL.md +82 -48
- package/skills-templates/external-transition/SKILL.md +47 -47
- package/skills-templates/feature-planning/SKILL.md +173 -74
- package/skills-templates/production-mode/SKILL.md +69 -49
- package/skills-templates/request-routing/SKILL.md +4 -4
- package/skills-templates/simple-improvement/SKILL.md +74 -29
- package/skills-templates/speed-mode/SKILL.md +217 -141
- package/skills-templates/stable-mode/SKILL.md +148 -89
- package/apps/dashboard/README.md +0 -36
- package/apps/dashboard/app/api/claude/[workItemId]/message/route.ts +0 -386
- package/apps/dashboard/app/api/claude/[workItemId]/pin/route.ts +0 -24
- package/apps/dashboard/app/api/claude/[workItemId]/route.ts +0 -167
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/content/route.ts +0 -52
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/message/route.ts +0 -378
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/pin/route.ts +0 -24
- package/apps/dashboard/app/api/claude/sessions/cleanup/route.ts +0 -34
- package/apps/dashboard/app/api/claude/sessions/route.ts +0 -184
- package/apps/dashboard/app/api/decisions/[id]/route.ts +0 -25
- package/apps/dashboard/app/api/internal/set-project/route.ts +0 -17
- package/apps/dashboard/app/api/kanban/route.ts +0 -15
- package/apps/dashboard/app/api/settings/env-vars/route.ts +0 -125
- package/apps/dashboard/app/api/settings/general/route.ts +0 -21
- package/apps/dashboard/app/api/tests/route.ts +0 -9
- package/apps/dashboard/app/api/tests/run/route.ts +0 -82
- package/apps/dashboard/app/api/tests/run/stream/route.ts +0 -71
- package/apps/dashboard/app/api/tests/undefined/route.ts +0 -9
- package/apps/dashboard/app/api/usage/route.ts +0 -17
- package/apps/dashboard/app/api/work/[id]/description/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/epic/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/order/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/status/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/title/route.ts +0 -21
- package/apps/dashboard/app/layout.tsx +0 -43
- package/apps/dashboard/components/UpgradeBanner.tsx +0 -29
- package/apps/dashboard/electron/ipc-handlers.js +0 -1028
- package/apps/dashboard/electron/main.js +0 -2124
- package/apps/dashboard/electron/preload.js +0 -123
- package/apps/dashboard/electron/session-manager.js +0 -141
- package/apps/dashboard/electron-builder.config.js +0 -357
- package/apps/dashboard/hooks/useClaudeSessions.ts +0 -299
- package/apps/dashboard/lib/claude-process-manager.ts +0 -492
- package/apps/dashboard/lib/db-bridge.ts +0 -282
- package/apps/dashboard/lib/prototypes.ts +0 -202
- package/apps/dashboard/lib/test-results-db.ts +0 -307
- package/apps/dashboard/lib/tests.ts +0 -282
- package/apps/dashboard/next.config.js +0 -50
- package/apps/dashboard/postcss.config.mjs +0 -7
- package/apps/dashboard/public/file.svg +0 -1
- package/apps/dashboard/public/globe.svg +0 -1
- package/apps/dashboard/public/next.svg +0 -1
- package/apps/dashboard/public/vercel.svg +0 -1
- package/apps/dashboard/public/window.svg +0 -1
- package/apps/dashboard/scripts/download-node.js +0 -104
- package/apps/dashboard/scripts/upload-to-r2.js +0 -89
- 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"
|