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,101 @@
|
|
|
1
|
+
//! Startup validation and automatic recovery.
|
|
2
|
+
//!
|
|
3
|
+
//! Port of Node.js `validateOnStartup()` — opens the database with integrity
|
|
4
|
+
//! checks, schema validation, and automatic snapshot recovery on failure.
|
|
5
|
+
|
|
6
|
+
use crate::error::{CoreError, Result};
|
|
7
|
+
use std::path::Path;
|
|
8
|
+
use super::Database;
|
|
9
|
+
|
|
10
|
+
impl Database {
|
|
11
|
+
/// Open the work database for a project root directory.
|
|
12
|
+
///
|
|
13
|
+
/// The database file is at `<project_root>/.jettypod/work.db`.
|
|
14
|
+
/// Includes integrity checks, schema validation, and automatic
|
|
15
|
+
/// snapshot recovery on failure.
|
|
16
|
+
pub fn open(project_root: &Path) -> Result<Self> {
|
|
17
|
+
let db_path = project_root.join(".jettypod").join("work.db");
|
|
18
|
+
Self::open_path(&db_path)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// Open a database at a specific path with full validation and recovery.
|
|
22
|
+
///
|
|
23
|
+
/// This is the primary entry point for production callers. It:
|
|
24
|
+
/// 1. Recovers from snapshots if the DB file is missing
|
|
25
|
+
/// 2. Attempts snapshot recovery if the DB fails to open
|
|
26
|
+
/// 3. Runs integrity checks with recovery on corruption
|
|
27
|
+
/// 4. Validates schema and tables
|
|
28
|
+
pub fn open_path(path: &Path) -> Result<Self> {
|
|
29
|
+
// 1. If DB file is missing, try snapshot recovery first
|
|
30
|
+
if !path.exists() {
|
|
31
|
+
match Self::recover_from_snapshots(path) {
|
|
32
|
+
Ok(count) => {
|
|
33
|
+
eprintln!("Recovered {count} rows from snapshots");
|
|
34
|
+
return Self::open_path_unchecked(path);
|
|
35
|
+
}
|
|
36
|
+
Err(_) => {
|
|
37
|
+
// No snapshots or recovery failed — open_path_unchecked will create fresh
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 2. Try to open
|
|
43
|
+
let db = match Self::open_path_unchecked(path) {
|
|
44
|
+
Ok(db) => db,
|
|
45
|
+
Err(open_err) => {
|
|
46
|
+
// Try snapshot recovery
|
|
47
|
+
match Self::recover_from_snapshots(path) {
|
|
48
|
+
Ok(count) => {
|
|
49
|
+
eprintln!("Recovered {count} rows from snapshots");
|
|
50
|
+
return Self::open_path_unchecked(path);
|
|
51
|
+
}
|
|
52
|
+
Err(recovery_err) => {
|
|
53
|
+
return Err(CoreError::Config(format!(
|
|
54
|
+
"Database failed to open and recovery failed.\n\
|
|
55
|
+
Original error: {open_err}\n\
|
|
56
|
+
Recovery error: {recovery_err}\n\n\
|
|
57
|
+
Manual recovery options:\n \
|
|
58
|
+
1. Restore from backup: jettypod work restore-backup latest\n \
|
|
59
|
+
2. Check ~/.jettypod-backups/ for global backups"
|
|
60
|
+
)));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// 3. Check integrity
|
|
67
|
+
let integrity = db.check_integrity()?;
|
|
68
|
+
if !integrity.ok {
|
|
69
|
+
drop(db);
|
|
70
|
+
match Self::recover_from_snapshots(path) {
|
|
71
|
+
Ok(count) => {
|
|
72
|
+
eprintln!("Recovered {count} rows from snapshots (corruption detected)");
|
|
73
|
+
let db = Self::open_path_unchecked(path)?;
|
|
74
|
+
let verify = db.check_integrity()?;
|
|
75
|
+
if verify.ok {
|
|
76
|
+
db.fixup_missing_columns()?;
|
|
77
|
+
db.validate_schema()?;
|
|
78
|
+
db.validate_tables()?;
|
|
79
|
+
return Ok(db);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
Err(_) => {}
|
|
83
|
+
}
|
|
84
|
+
let errors = integrity.errors.join("\n - ");
|
|
85
|
+
return Err(CoreError::Config(format!(
|
|
86
|
+
"Database integrity check failed and automatic recovery was unsuccessful.\n\
|
|
87
|
+
Errors found:\n - {errors}\n\n\
|
|
88
|
+
Manual recovery options:\n \
|
|
89
|
+
1. Restore from backup: jettypod work restore-backup latest\n \
|
|
90
|
+
2. Check ~/.jettypod-backups/ for global backups"
|
|
91
|
+
)));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 4. Fix up missing columns, then validate schema and tables
|
|
95
|
+
db.fixup_missing_columns()?;
|
|
96
|
+
db.validate_schema()?;
|
|
97
|
+
db.validate_tables()?;
|
|
98
|
+
|
|
99
|
+
Ok(db)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
//! Schema validation and database integrity utilities.
|
|
2
|
+
|
|
3
|
+
use crate::error::{CoreError, Result};
|
|
4
|
+
use super::Database;
|
|
5
|
+
|
|
6
|
+
// Column and table lists derived from V1__baseline.sql at build time.
|
|
7
|
+
include!(concat!(env!("OUT_DIR"), "/schema_info.rs"));
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Result types
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
/// Result of a WAL checkpoint operation.
|
|
14
|
+
#[derive(Debug)]
|
|
15
|
+
pub struct WalCheckpointResult {
|
|
16
|
+
/// Whether the checkpoint completed without being blocked.
|
|
17
|
+
pub success: bool,
|
|
18
|
+
/// Number of pages written to the database file.
|
|
19
|
+
pub pages_written: i32,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// Result of an integrity check.
|
|
23
|
+
#[derive(Debug)]
|
|
24
|
+
pub struct IntegrityResult {
|
|
25
|
+
/// `true` if the database passed all integrity checks.
|
|
26
|
+
pub ok: bool,
|
|
27
|
+
/// Error messages, empty if `ok` is true.
|
|
28
|
+
pub errors: Vec<String>,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Database validation methods
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
impl Database {
|
|
36
|
+
/// Force a WAL checkpoint, flushing all pending writes to the main
|
|
37
|
+
/// database file.
|
|
38
|
+
///
|
|
39
|
+
/// Uses TRUNCATE mode to also remove the WAL file. Should be called
|
|
40
|
+
/// before git checkout/merge operations to prevent WAL corruption.
|
|
41
|
+
pub fn wal_checkpoint(&self) -> Result<WalCheckpointResult> {
|
|
42
|
+
let row: (i32, i32, i32) = self
|
|
43
|
+
.conn
|
|
44
|
+
.query_row("PRAGMA wal_checkpoint(TRUNCATE)", [], |row| {
|
|
45
|
+
Ok((row.get(0)?, row.get(1)?, row.get(2)?))
|
|
46
|
+
})?;
|
|
47
|
+
|
|
48
|
+
let (busy, _log_pages, checkpointed) = row;
|
|
49
|
+
Ok(WalCheckpointResult {
|
|
50
|
+
success: busy == 0,
|
|
51
|
+
pages_written: checkpointed,
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// Run SQLite's built-in integrity check.
|
|
56
|
+
pub fn check_integrity(&self) -> Result<IntegrityResult> {
|
|
57
|
+
let mut stmt = self.conn.prepare("PRAGMA integrity_check")?;
|
|
58
|
+
let results: Vec<String> = stmt
|
|
59
|
+
.query_map([], |row| row.get(0))?
|
|
60
|
+
.collect::<std::result::Result<Vec<_>, _>>()?;
|
|
61
|
+
|
|
62
|
+
let ok = results.len() == 1 && results[0] == "ok";
|
|
63
|
+
Ok(IntegrityResult {
|
|
64
|
+
ok,
|
|
65
|
+
errors: if ok { Vec::new() } else { results },
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// Add any missing columns to work_items using definitions from V1__baseline.sql.
|
|
70
|
+
///
|
|
71
|
+
/// Existing databases created before new columns were added to V1 won't have them
|
|
72
|
+
/// since CREATE TABLE IF NOT EXISTS skips existing tables. This method fills the gap.
|
|
73
|
+
pub fn fixup_missing_columns(&self) -> Result<()> {
|
|
74
|
+
let mut stmt = self.conn.prepare("PRAGMA table_info(work_items)")?;
|
|
75
|
+
let existing: Vec<String> = stmt
|
|
76
|
+
.query_map([], |row| row.get::<_, String>(1))?
|
|
77
|
+
.collect::<std::result::Result<Vec<_>, _>>()?;
|
|
78
|
+
|
|
79
|
+
for (col_name, col_def) in WORK_ITEM_COLUMN_DEFS {
|
|
80
|
+
if !existing.iter().any(|e| e == *col_name) {
|
|
81
|
+
let sql = format!("ALTER TABLE work_items ADD COLUMN {col_name} {col_def}");
|
|
82
|
+
self.conn.execute(&sql, [])?;
|
|
83
|
+
eprintln!("Schema fixup: added missing column {col_name}");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
Ok(())
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/// Validate that all expected columns exist on the work_items table.
|
|
91
|
+
pub fn validate_schema(&self) -> Result<()> {
|
|
92
|
+
let mut stmt = self.conn.prepare("PRAGMA table_info(work_items)")?;
|
|
93
|
+
let existing: Vec<String> = stmt
|
|
94
|
+
.query_map([], |row| row.get::<_, String>(1))?
|
|
95
|
+
.collect::<std::result::Result<Vec<_>, _>>()?;
|
|
96
|
+
|
|
97
|
+
let missing: Vec<&str> = EXPECTED_WORK_ITEM_COLUMNS
|
|
98
|
+
.iter()
|
|
99
|
+
.filter(|col| !existing.iter().any(|e| e == **col))
|
|
100
|
+
.copied()
|
|
101
|
+
.collect();
|
|
102
|
+
|
|
103
|
+
if missing.is_empty() {
|
|
104
|
+
Ok(())
|
|
105
|
+
} else {
|
|
106
|
+
Err(CoreError::Config(format!(
|
|
107
|
+
"Schema validation failed: missing columns: {}",
|
|
108
|
+
missing.join(", ")
|
|
109
|
+
)))
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// Validate that all expected tables exist in the database.
|
|
114
|
+
pub fn validate_tables(&self) -> Result<()> {
|
|
115
|
+
let mut stmt = self.conn.prepare(
|
|
116
|
+
"SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'"
|
|
117
|
+
)?;
|
|
118
|
+
let existing: Vec<String> = stmt
|
|
119
|
+
.query_map([], |row| row.get::<_, String>(0))?
|
|
120
|
+
.collect::<std::result::Result<Vec<_>, _>>()?;
|
|
121
|
+
|
|
122
|
+
let missing: Vec<&str> = EXPECTED_TABLES
|
|
123
|
+
.iter()
|
|
124
|
+
.filter(|tbl| !existing.iter().any(|e| e == **tbl))
|
|
125
|
+
.copied()
|
|
126
|
+
.collect();
|
|
127
|
+
|
|
128
|
+
if missing.is_empty() {
|
|
129
|
+
Ok(())
|
|
130
|
+
} else {
|
|
131
|
+
Err(CoreError::Config(format!(
|
|
132
|
+
"Schema validation failed: missing tables: {}",
|
|
133
|
+
missing.join(", ")
|
|
134
|
+
)))
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/// Sync `_meta.schema_version` so the Node.js forward-compatibility guard
|
|
139
|
+
/// knows the current schema state. The value matches the count of Node.js
|
|
140
|
+
/// migration files (33) that our V1 baseline represents.
|
|
141
|
+
pub fn sync_meta_version(&self) -> Result<()> {
|
|
142
|
+
const NODE_MIGRATION_COUNT: &str = "33";
|
|
143
|
+
self.conn.execute(
|
|
144
|
+
"INSERT OR REPLACE INTO _meta (key, value) VALUES ('schema_version', ?1)",
|
|
145
|
+
[NODE_MIGRATION_COUNT],
|
|
146
|
+
)?;
|
|
147
|
+
Ok(())
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
use thiserror::Error;
|
|
2
|
+
|
|
3
|
+
/// Structured git operation errors.
|
|
4
|
+
#[derive(Debug, Error)]
|
|
5
|
+
pub enum GitError {
|
|
6
|
+
#[error("git command failed: git {args}\n{stderr}")]
|
|
7
|
+
CommandFailed { args: String, stderr: String },
|
|
8
|
+
|
|
9
|
+
#[error("git not available: {0}")]
|
|
10
|
+
NotAvailable(String),
|
|
11
|
+
|
|
12
|
+
#[error("invalid branch name: {0}")]
|
|
13
|
+
InvalidBranch(String),
|
|
14
|
+
|
|
15
|
+
#[error("not a git repository: {0}")]
|
|
16
|
+
NotARepo(String),
|
|
17
|
+
|
|
18
|
+
#[error("{0}")]
|
|
19
|
+
Other(String),
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// Structured work item errors.
|
|
23
|
+
#[derive(Debug, Error)]
|
|
24
|
+
pub enum WorkError {
|
|
25
|
+
#[error("work item #{0} not found")]
|
|
26
|
+
NotFound(i64),
|
|
27
|
+
|
|
28
|
+
#[error("title cannot be empty")]
|
|
29
|
+
EmptyTitle,
|
|
30
|
+
|
|
31
|
+
#[error("invalid state: {0}")]
|
|
32
|
+
InvalidState(String),
|
|
33
|
+
|
|
34
|
+
#[error("{0}")]
|
|
35
|
+
Other(String),
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// Core error type for all jettypod-core operations.
|
|
39
|
+
///
|
|
40
|
+
/// Each variant maps to a module boundary. Automatic `From` conversions
|
|
41
|
+
/// via `#[from]` keep call sites clean — use `?` freely.
|
|
42
|
+
#[derive(Debug, Error)]
|
|
43
|
+
pub enum CoreError {
|
|
44
|
+
#[error("database: {0}")]
|
|
45
|
+
Database(#[from] rusqlite::Error),
|
|
46
|
+
|
|
47
|
+
#[error("I/O: {0}")]
|
|
48
|
+
Io(#[from] std::io::Error),
|
|
49
|
+
|
|
50
|
+
#[error("JSON: {0}")]
|
|
51
|
+
Json(#[from] serde_json::Error),
|
|
52
|
+
|
|
53
|
+
#[error("JWT: {0}")]
|
|
54
|
+
Jwt(#[from] jsonwebtoken::errors::Error),
|
|
55
|
+
|
|
56
|
+
#[error("config: {0}")]
|
|
57
|
+
Config(String),
|
|
58
|
+
|
|
59
|
+
#[error("git: {0}")]
|
|
60
|
+
Git(#[from] GitError),
|
|
61
|
+
|
|
62
|
+
#[error("work: {0}")]
|
|
63
|
+
Work(#[from] WorkError),
|
|
64
|
+
|
|
65
|
+
#[error("skills: {0}")]
|
|
66
|
+
Skills(String),
|
|
67
|
+
|
|
68
|
+
#[error("parse: {0}")]
|
|
69
|
+
Parse(#[from] strum::ParseError),
|
|
70
|
+
|
|
71
|
+
#[error("{0}")]
|
|
72
|
+
Other(String),
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// Convenience alias used throughout jettypod-core.
|
|
76
|
+
pub type Result<T> = std::result::Result<T, CoreError>;
|