jettypod 4.4.116 → 4.4.120
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 +7 -0
- package/apps/dashboard/app/api/claude/[workItemId]/message/route.ts +124 -48
- package/apps/dashboard/app/api/claude/[workItemId]/route.ts +171 -58
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/message/route.ts +161 -10
- package/apps/dashboard/app/api/tests/run/stream/route.ts +13 -1
- package/apps/dashboard/app/api/usage/route.ts +17 -0
- package/apps/dashboard/app/api/work/[id]/route.ts +35 -0
- package/apps/dashboard/app/api/work/[id]/status/route.ts +43 -1
- package/apps/dashboard/app/connect-claude/page.tsx +24 -0
- package/apps/dashboard/app/decision/[id]/page.tsx +14 -14
- package/apps/dashboard/app/demo/gates/page.tsx +42 -42
- package/apps/dashboard/app/design-system/page.tsx +868 -0
- package/apps/dashboard/app/globals.css +6 -2
- package/apps/dashboard/app/install-claude/page.tsx +9 -7
- package/apps/dashboard/app/layout.tsx +17 -5
- package/apps/dashboard/app/login/page.tsx +250 -0
- package/apps/dashboard/app/page.tsx +11 -9
- package/apps/dashboard/app/settings/page.tsx +4 -2
- package/apps/dashboard/app/signup/page.tsx +245 -0
- package/apps/dashboard/app/subscribe/page.tsx +11 -0
- package/apps/dashboard/app/welcome/page.tsx +24 -1
- package/apps/dashboard/app/work/[id]/page.tsx +34 -50
- package/apps/dashboard/components/AppShell.tsx +95 -55
- package/apps/dashboard/components/CardMenu.tsx +56 -13
- package/apps/dashboard/components/ClaudePanel.tsx +301 -582
- package/apps/dashboard/components/ClaudePanelInput.tsx +23 -14
- package/apps/dashboard/components/ConnectClaudeScreen.tsx +210 -0
- package/apps/dashboard/components/CopyableId.tsx +3 -3
- package/apps/dashboard/components/DetailReviewActions.tsx +109 -0
- package/apps/dashboard/components/DragContext.tsx +75 -65
- package/apps/dashboard/components/DraggableCard.tsx +6 -46
- package/apps/dashboard/components/DropZone.tsx +2 -2
- package/apps/dashboard/components/EditableDetailDescription.tsx +1 -1
- package/apps/dashboard/components/EditableTitle.tsx +26 -6
- package/apps/dashboard/components/ElapsedTimer.tsx +54 -0
- package/apps/dashboard/components/EpicGroup.tsx +329 -0
- package/apps/dashboard/components/GateCard.tsx +100 -16
- package/apps/dashboard/components/GateChoiceCard.tsx +15 -17
- package/apps/dashboard/components/InstallClaudeScreen.tsx +140 -51
- package/apps/dashboard/components/JettyLoader.tsx +38 -0
- package/apps/dashboard/components/KanbanBoard.tsx +147 -766
- package/apps/dashboard/components/KanbanCard.tsx +506 -0
- package/apps/dashboard/components/LazyMarkdown.tsx +12 -0
- package/apps/dashboard/components/MainNav.tsx +20 -54
- package/apps/dashboard/components/MessageBlock.tsx +391 -0
- package/apps/dashboard/components/ModeStartCard.tsx +15 -15
- package/apps/dashboard/components/OnboardingWelcome.tsx +214 -0
- package/apps/dashboard/components/PlaceholderCard.tsx +11 -21
- package/apps/dashboard/components/ProjectSwitcher.tsx +36 -8
- package/apps/dashboard/components/PrototypeTimeline.tsx +25 -25
- package/apps/dashboard/components/RealTimeKanbanWrapper.tsx +265 -301
- package/apps/dashboard/components/RealTimeTestsWrapper.tsx +97 -74
- package/apps/dashboard/components/ReviewFooter.tsx +141 -0
- package/apps/dashboard/components/SessionList.tsx +19 -18
- package/apps/dashboard/components/SubscribeContent.tsx +206 -0
- package/apps/dashboard/components/TestTree.tsx +15 -14
- package/apps/dashboard/components/TipCard.tsx +177 -0
- package/apps/dashboard/components/Toast.tsx +5 -5
- package/apps/dashboard/components/TypeIcon.tsx +56 -0
- package/apps/dashboard/components/UpgradeBanner.tsx +30 -0
- package/apps/dashboard/components/WaveCompletionAnimation.tsx +61 -62
- package/apps/dashboard/components/WelcomeScreen.tsx +25 -27
- package/apps/dashboard/components/WorkItemHeader.tsx +4 -4
- package/apps/dashboard/components/WorkItemTree.tsx +9 -28
- package/apps/dashboard/components/settings/AccountSection.tsx +169 -0
- package/apps/dashboard/components/settings/EnvVarsSection.tsx +54 -79
- package/apps/dashboard/components/settings/GeneralSection.tsx +26 -31
- package/apps/dashboard/components/settings/SettingsLayout.tsx +4 -4
- package/apps/dashboard/components/ui/Button.tsx +104 -0
- package/apps/dashboard/components/ui/Input.tsx +78 -0
- package/apps/dashboard/contexts/ClaudeSessionContext.tsx +408 -105
- package/apps/dashboard/contexts/ConnectionStatusContext.tsx +25 -4
- package/apps/dashboard/contexts/UsageContext.tsx +155 -0
- package/apps/dashboard/contexts/usageHelpers.js +9 -0
- package/apps/dashboard/electron/ipc-handlers.js +281 -88
- package/apps/dashboard/electron/main.js +691 -131
- package/apps/dashboard/electron/preload.js +25 -4
- package/apps/dashboard/electron/session-manager.js +163 -0
- package/apps/dashboard/electron-builder.config.js +3 -5
- package/apps/dashboard/hooks/useKanbanAnimation.ts +29 -0
- package/apps/dashboard/hooks/useKanbanUndo.ts +83 -0
- package/apps/dashboard/lib/backlog-parser.ts +50 -0
- package/apps/dashboard/lib/claude-process-manager.ts +50 -11
- package/apps/dashboard/lib/constants.ts +43 -0
- package/apps/dashboard/lib/db-bridge.ts +33 -0
- package/apps/dashboard/lib/db.ts +136 -20
- package/apps/dashboard/lib/kanban-utils.ts +70 -0
- package/apps/dashboard/lib/run-migrations.js +27 -2
- package/apps/dashboard/lib/session-state-machine.ts +3 -0
- package/apps/dashboard/lib/session-stream-manager.ts +144 -38
- package/apps/dashboard/lib/shadows.ts +7 -0
- package/apps/dashboard/lib/tests.ts +3 -1
- package/apps/dashboard/lib/utils.ts +6 -0
- package/apps/dashboard/next.config.js +35 -14
- package/apps/dashboard/package.json +6 -3
- package/apps/dashboard/public/bug-icon.svg +9 -0
- package/apps/dashboard/public/buoy-icon.svg +9 -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.svg +9 -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.svg +14 -0
- package/apps/dashboard/public/star-icon.svg +9 -0
- package/apps/dashboard/public/wrench-icon.svg +9 -0
- package/apps/dashboard/scripts/upload-to-r2.js +89 -0
- package/apps/dashboard/scripts/ws-server.js +191 -0
- package/apps/dashboard/tsconfig.tsbuildinfo +1 -0
- package/apps/update-server/package.json +16 -0
- package/apps/update-server/schema.sql +31 -0
- package/apps/update-server/src/index.ts +1085 -0
- package/apps/update-server/tsconfig.json +16 -0
- package/apps/update-server/wrangler.toml +35 -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 +54 -116
- 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/migrations/027-plan-at-creation-column.js +33 -0
- package/lib/migrations/028-ready-for-review-column.js +27 -0
- package/lib/migrations/029-remove-autoincrement.js +307 -0
- package/lib/migrations/029-rename-corrupted-to-cleaned.js +149 -0
- package/lib/migrations/index.js +47 -4
- package/lib/schema.js +13 -6
- package/lib/seed-onboarding.js +101 -69
- package/lib/update-command/index.js +9 -175
- package/lib/work-commands/index.js +129 -16
- package/lib/work-tracking/index.js +86 -46
- 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 +39 -28
- package/skills-templates/bug-planning/SKILL.md +25 -29
- package/skills-templates/chore-mode/SKILL.md +131 -68
- package/skills-templates/chore-mode/verification.js +51 -10
- package/skills-templates/chore-planning/SKILL.md +47 -18
- package/skills-templates/epic-planning/SKILL.md +68 -48
- package/skills-templates/external-transition/SKILL.md +47 -47
- package/skills-templates/feature-planning/SKILL.md +83 -73
- package/skills-templates/production-mode/SKILL.md +49 -49
- package/skills-templates/request-routing/SKILL.md +27 -14
- package/skills-templates/simple-improvement/SKILL.md +68 -44
- package/skills-templates/speed-mode/SKILL.md +209 -128
- package/skills-templates/stable-mode/SKILL.md +105 -94
- package/templates/bdd-guidance.md +139 -0
- package/templates/bdd-scaffolding/wait.js +18 -0
- package/templates/bdd-scaffolding/world.js +19 -0
- package/.jettypod-backup/work.db +0 -0
- package/apps/dashboard/app/access-code/page.tsx +0 -110
- package/lib/discovery-checkpoint.js +0 -123
- package/skills-templates/project-discovery/SKILL.md +0 -372
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"lib": ["ESNext"],
|
|
7
|
+
"types": ["@cloudflare/workers-types"],
|
|
8
|
+
"strict": true,
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*.ts"]
|
|
16
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name = "jettypod-update-server"
|
|
2
|
+
main = "src/index.ts"
|
|
3
|
+
compatibility_date = "2025-02-01"
|
|
4
|
+
|
|
5
|
+
# R2 bucket for release artifacts (DMG, ZIP, latest-mac.yml)
|
|
6
|
+
[[r2_buckets]]
|
|
7
|
+
binding = "RELEASE_ARTIFACTS"
|
|
8
|
+
bucket_name = "jettypod-releases"
|
|
9
|
+
|
|
10
|
+
# D1 database for user accounts + usage tracking
|
|
11
|
+
[[d1_databases]]
|
|
12
|
+
binding = "AUTH_DB"
|
|
13
|
+
database_name = "jettypod-auth"
|
|
14
|
+
database_id = "80205398-8f56-4e04-a612-4a348458098f"
|
|
15
|
+
|
|
16
|
+
# KV namespace for OTP codes (with TTL)
|
|
17
|
+
[[kv_namespaces]]
|
|
18
|
+
binding = "AUTH_KV"
|
|
19
|
+
id = "91b43f72715e4c26a15b1dc0371af225"
|
|
20
|
+
|
|
21
|
+
# Stripe secrets - set via wrangler:
|
|
22
|
+
# wrangler secret put STRIPE_SECRET_KEY
|
|
23
|
+
# wrangler secret put STRIPE_WEBHOOK_SECRET
|
|
24
|
+
# wrangler secret put STRIPE_MONTHLY_PRICE_ID
|
|
25
|
+
# wrangler secret put STRIPE_LIFETIME_PRICE_ID
|
|
26
|
+
# Auth secrets - set via wrangler:
|
|
27
|
+
# wrangler secret put GOOGLE_CLIENT_ID
|
|
28
|
+
# wrangler secret put GOOGLE_CLIENT_SECRET
|
|
29
|
+
# wrangler secret put JWT_SECRET
|
|
30
|
+
# wrangler secret put RESEND_API_KEY
|
|
31
|
+
# Do NOT put actual keys here. Use wrangler secrets for sensitive values.
|
|
32
|
+
|
|
33
|
+
# Environment variables (non-secret)
|
|
34
|
+
[vars]
|
|
35
|
+
ENVIRONMENT = "production"
|
package/cucumber.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
1
3
|
module.exports = {
|
|
2
4
|
default: {
|
|
3
|
-
require: ['features/**/steps.js', 'features/**/simple-steps.js', 'features/**/*.steps.js'],
|
|
4
|
-
format: [
|
|
5
|
-
|
|
5
|
+
require: ['features/support/*.js', 'features/**/steps.js', 'features/**/simple-steps.js', 'features/**/*.steps.js'],
|
|
6
|
+
format: [
|
|
7
|
+
'progress',
|
|
8
|
+
`json:${path.resolve(__dirname, 'cucumber-results.json')}`,
|
|
9
|
+
`message:${path.resolve(__dirname, 'cucumber-results.ndjson')}`,
|
|
10
|
+
`rerun:${path.resolve(__dirname, '@rerun.txt')}`,
|
|
11
|
+
]
|
|
6
12
|
}
|
|
7
13
|
};
|
|
@@ -98,8 +98,36 @@ jettypod project discover complete \
|
|
|
98
98
|
|
|
99
99
|
## Work Item Management
|
|
100
100
|
|
|
101
|
+
### `jettypod work create --from=<filepath>`
|
|
102
|
+
|
|
103
|
+
Create any work item type from a JSON file. **This is the preferred method** — it avoids shell quoting issues and is truncation-safe (a truncated file path errors immediately instead of hanging).
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# Write JSON file first, then create
|
|
107
|
+
jettypod work create --from=/tmp/jettypod-create.json
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**JSON file format:**
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"type": "feature",
|
|
114
|
+
"title": "Login Form",
|
|
115
|
+
"description": "User login UI component",
|
|
116
|
+
"parent": 5,
|
|
117
|
+
"mode": "speed",
|
|
118
|
+
"needsDiscovery": false
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Required fields:** `type` (epic/feature/chore/bug), `title`
|
|
123
|
+
**Optional fields:** `description`, `parent` (ID), `mode` (speed/stable/production), `needsDiscovery` (boolean)
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
101
127
|
### `jettypod work create epic <title> [description] [flags]`
|
|
102
128
|
|
|
129
|
+
> **Prefer `--from` (above)** for Claude Code usage — positional args risk shell hangs from truncated quotes.
|
|
130
|
+
|
|
103
131
|
Create a new epic.
|
|
104
132
|
|
|
105
133
|
```bash
|
|
@@ -126,6 +154,8 @@ jettypod work create epic "Real-time Updates" "Live data sync" --needs-discovery
|
|
|
126
154
|
|
|
127
155
|
### `jettypod work create feature <title> [description] [flags]`
|
|
128
156
|
|
|
157
|
+
> **Prefer `--from` (above)** for Claude Code usage — positional args risk shell hangs from truncated quotes.
|
|
158
|
+
|
|
129
159
|
Create a new feature.
|
|
130
160
|
|
|
131
161
|
```bash
|
|
@@ -155,6 +185,8 @@ jettypod work create feature "Dashboard" "User dashboard" --mode=stable
|
|
|
155
185
|
|
|
156
186
|
### `jettypod work create chore <title> [description] [flags]`
|
|
157
187
|
|
|
188
|
+
> **Prefer `--from` (above)** for Claude Code usage — positional args risk shell hangs from truncated quotes.
|
|
189
|
+
|
|
158
190
|
Create a chore (non-feature work).
|
|
159
191
|
|
|
160
192
|
```bash
|
|
@@ -170,6 +202,8 @@ jettypod work create chore "Refactor auth module" --parent=5
|
|
|
170
202
|
|
|
171
203
|
### `jettypod work create bug <title> [description] [flags]`
|
|
172
204
|
|
|
205
|
+
> **Prefer `--from` (above)** for Claude Code usage — positional args risk shell hangs from truncated quotes.
|
|
206
|
+
|
|
173
207
|
Create a bug.
|
|
174
208
|
|
|
175
209
|
```bash
|
package/hooks/post-checkout
CHANGED
|
@@ -7,96 +7,53 @@
|
|
|
7
7
|
* 2. Imports database snapshots after checkout
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
const { importAll } = require('jettypod/lib/db-import');
|
|
11
|
-
const { walCheckpoint } = require('jettypod/lib/database');
|
|
12
10
|
const { execSync } = require('child_process');
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
const cwd = process.cwd();
|
|
12
|
+
const cwd = process.cwd();
|
|
16
13
|
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
// Prevent checking out default branch in JettyPod worktrees
|
|
15
|
+
if (cwd.includes('.jettypod-work')) {
|
|
16
|
+
try {
|
|
17
|
+
const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {
|
|
18
|
+
encoding: 'utf8',
|
|
19
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
20
|
+
}).trim();
|
|
21
|
+
|
|
22
|
+
// Detect default branch
|
|
23
|
+
let defaultBranch;
|
|
19
24
|
try {
|
|
20
|
-
|
|
21
|
-
const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {
|
|
25
|
+
defaultBranch = execSync('git symbolic-ref refs/remotes/origin/HEAD', {
|
|
22
26
|
encoding: 'utf8',
|
|
23
27
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
24
|
-
}).trim();
|
|
25
|
-
|
|
26
|
-
// Detect default branch
|
|
27
|
-
let defaultBranch;
|
|
28
|
+
}).trim().replace('refs/remotes/origin/', '');
|
|
29
|
+
} catch {
|
|
28
30
|
try {
|
|
29
|
-
|
|
30
|
-
defaultBranch =
|
|
31
|
-
encoding: 'utf8',
|
|
32
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
33
|
-
}).trim().replace('refs/remotes/origin/', '');
|
|
31
|
+
execSync('git rev-parse --verify main', { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
32
|
+
defaultBranch = 'main';
|
|
34
33
|
} catch {
|
|
35
|
-
// Fallback: check which common branch names exist
|
|
36
34
|
try {
|
|
37
|
-
execSync('git rev-parse --verify
|
|
38
|
-
defaultBranch = '
|
|
35
|
+
execSync('git rev-parse --verify master', { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
36
|
+
defaultBranch = 'master';
|
|
39
37
|
} catch {
|
|
40
|
-
|
|
41
|
-
execSync('git rev-parse --verify master', { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
42
|
-
defaultBranch = 'master';
|
|
43
|
-
} catch {
|
|
44
|
-
// Can't determine default branch - skip check
|
|
45
|
-
defaultBranch = null;
|
|
46
|
-
}
|
|
38
|
+
defaultBranch = null;
|
|
47
39
|
}
|
|
48
40
|
}
|
|
41
|
+
}
|
|
49
42
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
56
|
-
console.error('');
|
|
57
|
-
console.error(`You are in a JettyPod worktree for isolated work.`);
|
|
58
|
-
console.error(`Checking out ${defaultBranch} in a worktree bypasses the merge workflow.`);
|
|
59
|
-
console.error('');
|
|
60
|
-
console.error('To merge your changes:');
|
|
61
|
-
console.error(' 1. Commit your changes: git add . && git commit -m "..."');
|
|
62
|
-
console.error(' 2. Use JettyPod merge: jettypod work merge');
|
|
63
|
-
console.error('');
|
|
64
|
-
console.error('This ensures proper worktree cleanup and database updates.');
|
|
65
|
-
console.error('');
|
|
66
|
-
console.error('Reverting checkout...');
|
|
67
|
-
console.error('');
|
|
68
|
-
|
|
69
|
-
// Revert to previous branch
|
|
70
|
-
try {
|
|
71
|
-
execSync('git checkout -', { stdio: 'inherit' });
|
|
72
|
-
} catch (revertErr) {
|
|
73
|
-
console.error('Failed to revert checkout. You may need to manually switch branches.');
|
|
74
|
-
}
|
|
43
|
+
if (defaultBranch && currentBranch === defaultBranch) {
|
|
44
|
+
console.error('\n❌ Cannot checkout default branch in worktree');
|
|
45
|
+
console.error(`Checking out ${defaultBranch} bypasses the merge workflow.\n`);
|
|
46
|
+
console.error('To merge: jettypod work merge\n');
|
|
47
|
+
console.error('Reverting checkout...\n');
|
|
75
48
|
|
|
76
|
-
|
|
49
|
+
try {
|
|
50
|
+
execSync('git checkout -', { stdio: 'inherit' });
|
|
51
|
+
} catch (revertErr) {
|
|
52
|
+
console.error('Failed to revert. Manually switch branches.');
|
|
77
53
|
}
|
|
78
|
-
|
|
79
|
-
// If we can't determine the branch, allow the checkout
|
|
80
|
-
// (better to be permissive than block legitimate operations)
|
|
54
|
+
process.exit(1);
|
|
81
55
|
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// SECOND: Checkpoint WAL before importing to prevent corruption
|
|
85
|
-
// This flushes any pending writes to the main database file
|
|
86
|
-
try {
|
|
87
|
-
await walCheckpoint();
|
|
88
|
-
} catch (err) {
|
|
89
|
-
// Checkpoint failure shouldn't block - just log and continue
|
|
90
|
-
console.error('Post-checkout hook warning: WAL checkpoint failed:', err.message);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// THIRD: Import database snapshots
|
|
94
|
-
try {
|
|
95
|
-
await importAll();
|
|
96
|
-
process.exit(0);
|
|
97
56
|
} catch (err) {
|
|
98
|
-
//
|
|
99
|
-
console.error('Post-checkout hook warning:', err.message);
|
|
100
|
-
process.exit(0);
|
|
57
|
+
// Can't determine branch - allow checkout
|
|
101
58
|
}
|
|
102
|
-
}
|
|
59
|
+
}
|
package/hooks/post-merge
CHANGED
|
@@ -1,17 +1,118 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// JettyPod Git Hook: post-merge
|
|
4
|
+
// Backs up database to .jettypod-backup/ when merging to main
|
|
5
|
+
// Uses --no-verify to bypass pre-commit tests for data-only commits
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { execSync } = require('child_process');
|
|
10
|
+
|
|
11
|
+
const jettypodDir = path.join(process.cwd(), '.jettypod');
|
|
12
|
+
const backupDir = path.join(process.cwd(), '.jettypod-backup');
|
|
13
|
+
|
|
14
|
+
// Backup database and commit
|
|
15
|
+
function backupDatabase() {
|
|
16
|
+
if (!fs.existsSync(jettypodDir)) {
|
|
17
|
+
return; // No JettyPod directory, skip backup
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Only backup on main/master branch
|
|
21
|
+
let currentBranch;
|
|
6
22
|
try {
|
|
7
|
-
|
|
8
|
-
|
|
23
|
+
currentBranch = execSync('git branch --show-current', { encoding: 'utf-8' }).trim();
|
|
24
|
+
} catch {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (currentBranch !== 'main' && currentBranch !== 'master') {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
console.log('\n💾 Backing up database...\n');
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
// Ensure backup directory exists
|
|
36
|
+
if (!fs.existsSync(backupDir)) {
|
|
37
|
+
fs.mkdirSync(backupDir, { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Backup work.db using sqlite3 .backup command
|
|
41
|
+
// CRITICAL: fs.copyFileSync does NOT work with WAL mode - it only copies
|
|
42
|
+
// the base .db file, missing all data in the -wal file. sqlite3 .backup
|
|
43
|
+
// reads the logical database (including WAL) and writes a clean standalone file.
|
|
44
|
+
const sourcePath = path.join(jettypodDir, 'work.db');
|
|
45
|
+
const backupPath = path.join(backupDir, 'work.db');
|
|
9
46
|
|
|
10
|
-
|
|
11
|
-
|
|
47
|
+
if (!fs.existsSync(sourcePath)) {
|
|
48
|
+
console.log('⚠️ No work.db found, skipping backup\n');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
execSync(`sqlite3 "${sourcePath}" ".backup '${backupPath}'"`, {
|
|
53
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// SAFETY: Validate backup didn't lose data
|
|
57
|
+
// If an existing backup has significantly more items, refuse to overwrite
|
|
58
|
+
const newCount = parseInt(execSync(
|
|
59
|
+
`sqlite3 "${backupPath}" "SELECT count(*) FROM work_items"`,
|
|
60
|
+
{ encoding: 'utf-8', stdio: 'pipe' }
|
|
61
|
+
).trim()) || 0;
|
|
62
|
+
|
|
63
|
+
if (newCount < 2) {
|
|
64
|
+
console.log('⚠️ Backup has fewer than 2 items - likely corrupted, skipping commit\n');
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Check against git's previous version of the backup
|
|
69
|
+
try {
|
|
70
|
+
const oldCount = parseInt(execSync(
|
|
71
|
+
`git show HEAD:.jettypod-backup/work.db 2>/dev/null | sqlite3 "" "SELECT count(*) FROM work_items"`,
|
|
72
|
+
{ encoding: 'utf-8', stdio: 'pipe' }
|
|
73
|
+
).trim()) || 0;
|
|
74
|
+
|
|
75
|
+
if (oldCount > 0 && newCount < oldCount * 0.5) {
|
|
76
|
+
console.log(`⚠️ Backup shrank from ${oldCount} to ${newCount} items (>50% loss) - refusing to commit\n`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
// No previous backup in git or can't read it - proceed
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Stage the backup file (force-add because .jettypod-backup is gitignored
|
|
84
|
+
// to prevent worktree symlink corruption, but backups must be committed)
|
|
85
|
+
execSync(`git add -f "${backupPath}"`, {
|
|
86
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Check if there are any staged changes
|
|
90
|
+
try {
|
|
91
|
+
execSync('git diff --cached --quiet', {
|
|
92
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
93
|
+
});
|
|
94
|
+
// No staged changes, nothing to commit
|
|
95
|
+
console.log('✅ Database backup unchanged\n');
|
|
96
|
+
return;
|
|
97
|
+
} catch {
|
|
98
|
+
// Has staged changes, continue to commit
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
execSync('git commit --no-verify -m "chore: Update database backup"', {
|
|
102
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Push the backup commit
|
|
106
|
+
execSync('git push', {
|
|
107
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
console.log('✅ Database backed up and pushed\n');
|
|
12
111
|
} catch (err) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
process.exit(0);
|
|
112
|
+
console.error('⚠️ Database backup failed:', err.message);
|
|
113
|
+
// Don't block - backup is important but not critical
|
|
16
114
|
}
|
|
17
|
-
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Main
|
|
118
|
+
backupDatabase();
|
package/jest.setup.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
process.env.NODE_ENV = 'test';
|
package/jettypod.js
CHANGED
|
@@ -496,9 +496,9 @@ Skills auto-activate and MUST complete their full workflow:
|
|
|
496
496
|
✅ ALWAYS let skills complete autonomously before taking manual actions
|
|
497
497
|
|
|
498
498
|
## Basic Commands (for non-workflow operations)
|
|
499
|
-
|
|
500
|
-
jettypod work create
|
|
501
|
-
|
|
499
|
+
# To create work items: Write JSON to /tmp/jettypod-create.json, then:
|
|
500
|
+
jettypod work create --from=/tmp/jettypod-create.json
|
|
501
|
+
# JSON format: {"type":"epic|feature|chore|bug","title":"...","description":"...","parent":<id>}
|
|
502
502
|
jettypod work start <id> # Creates worktree branch
|
|
503
503
|
jettypod work merge # Merges worktree back to main
|
|
504
504
|
jettypod work cleanup <id> # Cleanup worktree after merge
|
|
@@ -877,6 +877,11 @@ async function initializeProject() {
|
|
|
877
877
|
}
|
|
878
878
|
});
|
|
879
879
|
if (result) {
|
|
880
|
+
// Mark onboarding chores as conversational (no worktree, no 5s delay)
|
|
881
|
+
const db = getDb();
|
|
882
|
+
for (const choreId of result.choreIds) {
|
|
883
|
+
db.run('UPDATE work_items SET conversational = 1 WHERE id = ?', [choreId]);
|
|
884
|
+
}
|
|
880
885
|
console.log(`📋 Created onboarding epic with ${result.choreIds.length} chores`);
|
|
881
886
|
console.log(` Start with: jettypod work start ${result.choreIds[0]}`);
|
|
882
887
|
}
|
|
@@ -1259,110 +1264,10 @@ if (!commandsWithoutDb.includes(command) && fs.existsSync('.jettypod')) {
|
|
|
1259
1264
|
|
|
1260
1265
|
switch (command) {
|
|
1261
1266
|
case 'update': {
|
|
1262
|
-
//
|
|
1267
|
+
// CLI is bundled with the app — no independent update mechanism
|
|
1263
1268
|
const updateCommand = require('./lib/update-command');
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
// Always refresh skills and hooks in current project after update attempt
|
|
1267
|
-
if (fs.existsSync('.jettypod')) {
|
|
1268
|
-
// Update Claude Code hooks first
|
|
1269
|
-
ensureClaudeHooks();
|
|
1270
|
-
|
|
1271
|
-
console.log('');
|
|
1272
|
-
console.log('🔄 Refreshing skills in current project...');
|
|
1273
|
-
|
|
1274
|
-
// Use the existing skills update logic from initializeProject
|
|
1275
|
-
// Allow tests to override skills source directory
|
|
1276
|
-
const skillsSourceDir = process.env.JETTYPOD_SKILLS_SOURCE_DIR ||
|
|
1277
|
-
path.join(__dirname, 'skills-templates');
|
|
1278
|
-
const skillsDestDir = path.join('.claude', 'skills');
|
|
1279
|
-
let backupDir = null;
|
|
1280
|
-
|
|
1281
|
-
// Backup existing skills before updating
|
|
1282
|
-
if (fs.existsSync(skillsDestDir)) {
|
|
1283
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
1284
|
-
backupDir = path.join('.claude', `skills.backup-${timestamp}`);
|
|
1285
|
-
|
|
1286
|
-
let counter = 1;
|
|
1287
|
-
while (fs.existsSync(backupDir)) {
|
|
1288
|
-
backupDir = path.join('.claude', `skills.backup-${timestamp}-${counter}`);
|
|
1289
|
-
counter++;
|
|
1290
|
-
}
|
|
1291
|
-
|
|
1292
|
-
try {
|
|
1293
|
-
fs.renameSync(skillsDestDir, backupDir);
|
|
1294
|
-
|
|
1295
|
-
if (!fs.existsSync(backupDir)) {
|
|
1296
|
-
throw new Error('Backup directory was not created');
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
const backupContents = fs.readdirSync(backupDir);
|
|
1300
|
-
if (backupContents.length === 0) {
|
|
1301
|
-
throw new Error('Backup directory is empty');
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
console.log(`💾 Backed up existing skills to ${backupDir}`);
|
|
1305
|
-
} catch (err) {
|
|
1306
|
-
console.error(`❌ Could not backup skills: ${err.message}`);
|
|
1307
|
-
throw new Error(`Could not backup existing skills: ${err.message}`);
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
|
|
1311
|
-
// Copy skills from jettypod to project
|
|
1312
|
-
if (!fs.existsSync(skillsSourceDir)) {
|
|
1313
|
-
console.warn('⚠️ Skills source directory not found');
|
|
1314
|
-
} else {
|
|
1315
|
-
try {
|
|
1316
|
-
const copyRecursive = (src, dest) => {
|
|
1317
|
-
if (!fs.existsSync(dest)) {
|
|
1318
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
1319
|
-
}
|
|
1320
|
-
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
1321
|
-
for (const entry of entries) {
|
|
1322
|
-
const srcPath = path.join(src, entry.name);
|
|
1323
|
-
const destPath = path.join(dest, entry.name);
|
|
1324
|
-
if (entry.isDirectory()) {
|
|
1325
|
-
copyRecursive(srcPath, destPath);
|
|
1326
|
-
} else {
|
|
1327
|
-
fs.copyFileSync(srcPath, destPath);
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
};
|
|
1331
|
-
|
|
1332
|
-
copyRecursive(skillsSourceDir, skillsDestDir);
|
|
1333
|
-
console.log('✅ Skills refreshed');
|
|
1334
|
-
} catch (err) {
|
|
1335
|
-
console.error(`❌ Could not refresh skills: ${err.message}`);
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
|
-
|
|
1339
|
-
// Ensure session.md is gitignored (fixes existing projects)
|
|
1340
|
-
ensureJettypodGitignores();
|
|
1341
|
-
|
|
1342
|
-
// Auto-commit any changes made by update (skills, hooks, gitignore, etc.)
|
|
1343
|
-
// This prevents untracked changes from breaking worktree merges
|
|
1344
|
-
try {
|
|
1345
|
-
const { execSync } = require('child_process');
|
|
1346
|
-
// Check if there are any changes to commit
|
|
1347
|
-
const status = execSync('git status --porcelain', { encoding: 'utf-8' });
|
|
1348
|
-
if (status.trim()) {
|
|
1349
|
-
execSync('git add -A');
|
|
1350
|
-
// Use --no-verify to bypass pre-commit hooks - this is an automated system update
|
|
1351
|
-
execSync('git commit --no-verify -m "chore: jettypod update - refresh skills and hooks"');
|
|
1352
|
-
console.log('✅ Update changes committed');
|
|
1353
|
-
}
|
|
1354
|
-
} catch (err) {
|
|
1355
|
-
// Log the error so users know what happened
|
|
1356
|
-
console.log('');
|
|
1357
|
-
console.log('⚠️ Could not auto-commit skill updates');
|
|
1358
|
-
console.log(' Reason:', err.message.split('\n')[0]);
|
|
1359
|
-
console.log('');
|
|
1360
|
-
console.log(' You may have untracked changes in .claude/skills/');
|
|
1361
|
-
console.log(' Run: git add .claude && git commit -m "chore: update skills"');
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
|
|
1365
|
-
process.exit(success ? 0 : 1);
|
|
1269
|
+
await updateCommand.runUpdate();
|
|
1270
|
+
process.exit(0);
|
|
1366
1271
|
break;
|
|
1367
1272
|
}
|
|
1368
1273
|
|
|
@@ -1449,13 +1354,14 @@ switch (command) {
|
|
|
1449
1354
|
} else if (subcommand === 'cleanup') {
|
|
1450
1355
|
const workCommands = require('./lib/work-commands/index.js');
|
|
1451
1356
|
const dryRun = args.includes('--dry-run');
|
|
1357
|
+
const force = args.includes('--force');
|
|
1452
1358
|
// Find numeric arg for specific work item cleanup
|
|
1453
1359
|
const workItemId = args.find(a => /^\d+$/.test(a)) ? parseInt(args.find(a => /^\d+$/.test(a))) : null;
|
|
1454
1360
|
|
|
1455
1361
|
try {
|
|
1456
1362
|
if (workItemId) {
|
|
1457
1363
|
// Clean up specific work item's worktree
|
|
1458
|
-
await workCommands.cleanupWorkItem(workItemId);
|
|
1364
|
+
await workCommands.cleanupWorkItem(workItemId, { force });
|
|
1459
1365
|
} else {
|
|
1460
1366
|
// Batch cleanup of all orphaned worktrees
|
|
1461
1367
|
const results = await workCommands.cleanupWorktrees({ dryRun });
|
|
@@ -2200,10 +2106,6 @@ switch (command) {
|
|
|
2200
2106
|
}
|
|
2201
2107
|
});
|
|
2202
2108
|
|
|
2203
|
-
// Clear checkpoint
|
|
2204
|
-
const checkpoint = require('./lib/discovery-checkpoint');
|
|
2205
|
-
checkpoint.clearCheckpoint();
|
|
2206
|
-
|
|
2207
2109
|
await generateClaude({ autoCommit: true });
|
|
2208
2110
|
|
|
2209
2111
|
console.log('✅ Project discovery complete!');
|
|
@@ -2352,7 +2254,33 @@ switch (command) {
|
|
|
2352
2254
|
break;
|
|
2353
2255
|
}
|
|
2354
2256
|
|
|
2355
|
-
case undefined:
|
|
2257
|
+
case undefined: {
|
|
2258
|
+
// Auto-update: if running from a stale global install, reinstall from source
|
|
2259
|
+
// Skip in worktrees - worktree copies always differ from source and updating
|
|
2260
|
+
// the global install won't change the worktree file, causing infinite recursion
|
|
2261
|
+
const devLinkPath = path.join(require('os').homedir(), '.jettypod-dev.json');
|
|
2262
|
+
if (fs.existsSync(devLinkPath) && !__dirname.includes('.jettypod-work')) {
|
|
2263
|
+
try {
|
|
2264
|
+
const devInfo = JSON.parse(fs.readFileSync(devLinkPath, 'utf-8'));
|
|
2265
|
+
const sourceDir = devInfo.sourceDir;
|
|
2266
|
+
if (sourceDir && fs.existsSync(path.join(sourceDir, 'jettypod.js'))) {
|
|
2267
|
+
const sourceContent = fs.readFileSync(path.join(sourceDir, 'jettypod.js'));
|
|
2268
|
+
const installedContent = fs.readFileSync(path.join(__dirname, 'jettypod.js'));
|
|
2269
|
+
if (!sourceContent.equals(installedContent)) {
|
|
2270
|
+
console.log('🔄 Source has changed, updating jettypod...');
|
|
2271
|
+
const { execSync: execSyncUpdate } = require('child_process');
|
|
2272
|
+
execSyncUpdate('npm install -g .', { cwd: sourceDir, stdio: 'inherit' });
|
|
2273
|
+
console.log('✅ Updated. Relaunching...');
|
|
2274
|
+
const { execFileSync } = require('child_process');
|
|
2275
|
+
execFileSync(process.argv[0], process.argv.slice(1), { stdio: 'inherit', cwd: process.cwd() });
|
|
2276
|
+
process.exit(0);
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
} catch (e) {
|
|
2280
|
+
// Auto-update check failed — continue with current version
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2356
2284
|
// Smart detection when no command provided
|
|
2357
2285
|
if (!fs.existsSync('.jettypod/config.json')) {
|
|
2358
2286
|
// New project - auto-initialize
|
|
@@ -2456,12 +2384,13 @@ switch (command) {
|
|
|
2456
2384
|
|
|
2457
2385
|
// Start dashboard in dev mode for live reload
|
|
2458
2386
|
console.log('🚀 Starting dashboard (dev mode)...');
|
|
2459
|
-
const dashboardProcess = spawn('npm', ['run', 'dev'
|
|
2387
|
+
const dashboardProcess = spawn('npm', ['run', 'dev'], {
|
|
2460
2388
|
cwd: dashboardPath,
|
|
2461
2389
|
detached: true,
|
|
2462
2390
|
stdio: 'ignore',
|
|
2463
2391
|
env: {
|
|
2464
2392
|
...process.env,
|
|
2393
|
+
PORT: String(availablePort),
|
|
2465
2394
|
JETTYPOD_PROJECT_PATH: process.cwd(),
|
|
2466
2395
|
JETTYPOD_WS_PORT: String(WS_PORT)
|
|
2467
2396
|
}
|
|
@@ -2519,6 +2448,7 @@ Quick commands:
|
|
|
2519
2448
|
}
|
|
2520
2449
|
}
|
|
2521
2450
|
break;
|
|
2451
|
+
}
|
|
2522
2452
|
|
|
2523
2453
|
case 'trash': {
|
|
2524
2454
|
// Trash management commands
|
|
@@ -2651,9 +2581,17 @@ Quick commands:
|
|
|
2651
2581
|
const impact = graph.getImpact(targetFile);
|
|
2652
2582
|
|
|
2653
2583
|
if (impact.error) {
|
|
2654
|
-
console.
|
|
2655
|
-
|
|
2656
|
-
|
|
2584
|
+
console.log(`\n❌ ${impact.error}`);
|
|
2585
|
+
if (impact.suggestions && impact.suggestions.length > 0) {
|
|
2586
|
+
console.log('');
|
|
2587
|
+
console.log('Did you mean one of these?');
|
|
2588
|
+
for (const s of impact.suggestions) {
|
|
2589
|
+
console.log(` ${s}`);
|
|
2590
|
+
}
|
|
2591
|
+
} else {
|
|
2592
|
+
console.log('');
|
|
2593
|
+
console.log('Make sure the file path is relative to the project root.');
|
|
2594
|
+
}
|
|
2657
2595
|
process.exit(1);
|
|
2658
2596
|
}
|
|
2659
2597
|
|