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
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
const { execSync } = require('child_process');
|
|
2
|
-
const https = require('https');
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
1
|
const packageJson = require('../../package.json');
|
|
6
2
|
|
|
7
3
|
/**
|
|
@@ -12,184 +8,22 @@ function getCurrentVersion() {
|
|
|
12
8
|
return packageJson.version;
|
|
13
9
|
}
|
|
14
10
|
|
|
15
|
-
/**
|
|
16
|
-
* Check npm registry for latest version
|
|
17
|
-
* @returns {Promise<string>} Latest version from npm
|
|
18
|
-
*/
|
|
19
|
-
function getLatestVersion() {
|
|
20
|
-
return new Promise((resolve, reject) => {
|
|
21
|
-
const packageName = packageJson.name;
|
|
22
|
-
const url = `https://registry.npmjs.org/${packageName}/latest`;
|
|
23
|
-
|
|
24
|
-
const request = https.get(url, (res) => {
|
|
25
|
-
let data = '';
|
|
26
|
-
|
|
27
|
-
if (res.statusCode !== 200) {
|
|
28
|
-
reject(new Error(`HTTP ${res.statusCode}`));
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
res.on('data', (chunk) => {
|
|
33
|
-
data += chunk;
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
res.on('end', () => {
|
|
37
|
-
try {
|
|
38
|
-
const json = JSON.parse(data);
|
|
39
|
-
if (!json.version) {
|
|
40
|
-
reject(new Error('No version found in npm response'));
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
resolve(json.version);
|
|
44
|
-
} catch (err) {
|
|
45
|
-
reject(new Error(`Invalid JSON response: ${err.message}`));
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
// Set timeout for network request (30 seconds)
|
|
51
|
-
request.setTimeout(30000, () => {
|
|
52
|
-
request.destroy();
|
|
53
|
-
reject(new Error('Request timeout - network too slow'));
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
request.on('error', (err) => {
|
|
57
|
-
// Provide specific error messages for common network errors
|
|
58
|
-
if (err.code === 'ENOTFOUND') {
|
|
59
|
-
reject(new Error('network error - DNS lookup failed (check internet connection)'));
|
|
60
|
-
} else if (err.code === 'ETIMEDOUT' || err.code === 'ESOCKETTIMEDOUT') {
|
|
61
|
-
reject(new Error('network error - connection timeout'));
|
|
62
|
-
} else if (err.code === 'ECONNREFUSED') {
|
|
63
|
-
reject(new Error('network error - connection refused'));
|
|
64
|
-
} else if (err.code === 'ECONNRESET') {
|
|
65
|
-
reject(new Error('network error - connection reset'));
|
|
66
|
-
} else {
|
|
67
|
-
reject(err);
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Update jettypod to latest version using npm
|
|
75
|
-
* @param {string} version - Version to update to (default: latest)
|
|
76
|
-
* @returns {boolean} True if update succeeded
|
|
77
|
-
*/
|
|
78
|
-
function updateJettyPod(version = 'latest') {
|
|
79
|
-
const packageName = packageJson.name;
|
|
80
|
-
try {
|
|
81
|
-
console.log(`📦 Installing jettypod@${version}...`);
|
|
82
|
-
|
|
83
|
-
// Use npm to update globally
|
|
84
|
-
execSync(`npm install -g ${packageName}@${version}`, {
|
|
85
|
-
stdio: 'inherit'
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
// Clear dashboard .next folder to force rebuild with new assets
|
|
89
|
-
try {
|
|
90
|
-
const globalRoot = execSync('npm root -g', { encoding: 'utf-8' }).trim();
|
|
91
|
-
const dashboardNextPath = path.join(globalRoot, 'jettypod', 'apps', 'dashboard', '.next');
|
|
92
|
-
if (fs.existsSync(dashboardNextPath)) {
|
|
93
|
-
fs.rmSync(dashboardNextPath, { recursive: true, force: true });
|
|
94
|
-
console.log('🧹 Cleared dashboard cache');
|
|
95
|
-
}
|
|
96
|
-
} catch {
|
|
97
|
-
// Non-fatal - dashboard will still work, just might use old cache
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return true;
|
|
101
|
-
} catch (err) {
|
|
102
|
-
console.log('');
|
|
103
|
-
console.error(`❌ Update failed`);
|
|
104
|
-
console.log('');
|
|
105
|
-
|
|
106
|
-
// Provide specific error messages for common failures
|
|
107
|
-
const errorOutput = err.stderr ? err.stderr.toString() : '';
|
|
108
|
-
|
|
109
|
-
if (err.message.includes('EACCES') || err.message.includes('EPERM') ||
|
|
110
|
-
errorOutput.includes('EACCES') || errorOutput.includes('EPERM')) {
|
|
111
|
-
console.error('Permission denied - try running with sudo:');
|
|
112
|
-
console.log(` sudo npm install -g ${packageName}@${version}`);
|
|
113
|
-
console.log('');
|
|
114
|
-
console.error('Or configure npm to use a different directory:');
|
|
115
|
-
console.log(' mkdir ~/.npm-global');
|
|
116
|
-
console.log(' npm config set prefix ~/.npm-global');
|
|
117
|
-
console.log(' export PATH=~/.npm-global/bin:$PATH');
|
|
118
|
-
} else if (errorOutput.includes('ENOSPC')) {
|
|
119
|
-
console.error('Not enough disk space to install update');
|
|
120
|
-
console.log('Free up disk space and try again');
|
|
121
|
-
} else if (errorOutput.includes('404') || errorOutput.includes('E404')) {
|
|
122
|
-
console.error(`Version ${version} not found in npm registry`);
|
|
123
|
-
} else if (errorOutput.includes('network') || errorOutput.includes('ETIMEDOUT') ||
|
|
124
|
-
errorOutput.includes('ENOTFOUND')) {
|
|
125
|
-
console.error('Network error during npm install');
|
|
126
|
-
console.log('Check your internet connection and try again');
|
|
127
|
-
} else {
|
|
128
|
-
console.error(`Error details: ${err.message}`);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
console.log('');
|
|
132
|
-
console.error('Manual update:');
|
|
133
|
-
console.log(` npm install -g ${packageName}@${version}`);
|
|
134
|
-
|
|
135
|
-
return false;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
11
|
/**
|
|
140
12
|
* Run the update command
|
|
141
|
-
*
|
|
142
|
-
* @param {Function} options.getCurrentVersion - Function to get current version
|
|
143
|
-
* @param {Function} options.getLatestVersion - Function to get latest version
|
|
144
|
-
* @param {Function} options.updateJettyPod - Function to update jettypod
|
|
13
|
+
* The JettyPod app is the single distribution point — CLI updates ship with app updates.
|
|
145
14
|
*/
|
|
146
|
-
async function runUpdate(
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
const _updateJettyPod = options.updateJettyPod || updateJettyPod;
|
|
150
|
-
|
|
151
|
-
console.log('🔍 Checking for updates...');
|
|
152
|
-
|
|
153
|
-
const currentVersion = _getCurrentVersion();
|
|
154
|
-
console.log(`Current version: ${currentVersion}`);
|
|
155
|
-
|
|
156
|
-
let latestVersion;
|
|
157
|
-
try {
|
|
158
|
-
latestVersion = await _getLatestVersion();
|
|
159
|
-
} catch (err) {
|
|
160
|
-
console.log(`Cannot check for updates: ${err.message}`);
|
|
161
|
-
console.log('');
|
|
162
|
-
console.log('You can still manually update with:');
|
|
163
|
-
console.log(` npm install -g ${packageJson.name}@latest`);
|
|
164
|
-
return false;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
console.log(`Latest version: ${latestVersion}`);
|
|
15
|
+
async function runUpdate() {
|
|
16
|
+
const currentVersion = getCurrentVersion();
|
|
17
|
+
console.log(`JettyPod v${currentVersion}`);
|
|
168
18
|
console.log('');
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
console.log(`Already on latest version: ${latestVersion}`);
|
|
172
|
-
return true;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
console.log(`New version available: ${latestVersion} (current: ${currentVersion})`);
|
|
19
|
+
console.log('The CLI is bundled with the JettyPod app.');
|
|
20
|
+
console.log('Updates are delivered automatically through the app.');
|
|
176
21
|
console.log('');
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (success) {
|
|
181
|
-
console.log('');
|
|
182
|
-
console.log(`✅ JettyPod updated to ${latestVersion}`);
|
|
183
|
-
console.log('');
|
|
184
|
-
return true;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
return false;
|
|
22
|
+
console.log('To check for app updates: open JettyPod and it will auto-update.');
|
|
23
|
+
return true;
|
|
188
24
|
}
|
|
189
25
|
|
|
190
26
|
module.exports = {
|
|
191
27
|
runUpdate,
|
|
192
|
-
getCurrentVersion
|
|
193
|
-
getLatestVersion,
|
|
194
|
-
updateJettyPod
|
|
28
|
+
getCurrentVersion
|
|
195
29
|
};
|
|
@@ -1148,9 +1148,12 @@ async function cleanupWorktrees(options = {}) {
|
|
|
1148
1148
|
* Clean up a specific worktree after merge
|
|
1149
1149
|
* Should be run from main repo after cd'ing out of the worktree
|
|
1150
1150
|
* @param {number} workItemId - The work item ID to clean up
|
|
1151
|
+
* @param {Object} [options] - Cleanup options
|
|
1152
|
+
* @param {boolean} [options.force] - Force cleanup even if worktree is still active (skips merge requirement)
|
|
1151
1153
|
* @returns {Promise<Object>} Result with success status
|
|
1152
1154
|
*/
|
|
1153
|
-
async function cleanupWorkItem(workItemId) {
|
|
1155
|
+
async function cleanupWorkItem(workItemId, options = {}) {
|
|
1156
|
+
const { force = false } = options;
|
|
1154
1157
|
const db = getDb();
|
|
1155
1158
|
const gitRoot = getGitRoot();
|
|
1156
1159
|
|
|
@@ -1183,13 +1186,89 @@ async function cleanupWorkItem(workItemId) {
|
|
|
1183
1186
|
return { success: true, message: 'No worktree to clean up' };
|
|
1184
1187
|
}
|
|
1185
1188
|
|
|
1186
|
-
if (worktree.status === 'active') {
|
|
1189
|
+
if (worktree.status === 'active' && !force) {
|
|
1187
1190
|
return Promise.reject(new Error(
|
|
1188
1191
|
`Worktree for #${workItemId} is still active.\n` +
|
|
1189
|
-
`Run 'jettypod work merge ${workItemId}' first.`
|
|
1192
|
+
`Run 'jettypod work merge ${workItemId}' first, or use --force to skip merge.`
|
|
1190
1193
|
));
|
|
1191
1194
|
}
|
|
1192
1195
|
|
|
1196
|
+
// Force cleanup of active worktree — warn about unmerged work
|
|
1197
|
+
if (worktree.status === 'active' && force) {
|
|
1198
|
+
const warnings = [];
|
|
1199
|
+
|
|
1200
|
+
// Check for uncommitted changes in the worktree
|
|
1201
|
+
if (worktree.worktree_path && fs.existsSync(worktree.worktree_path)) {
|
|
1202
|
+
try {
|
|
1203
|
+
const dirtyFiles = execSync('git status --porcelain', {
|
|
1204
|
+
cwd: worktree.worktree_path,
|
|
1205
|
+
stdio: 'pipe'
|
|
1206
|
+
}).toString().trim();
|
|
1207
|
+
if (dirtyFiles) {
|
|
1208
|
+
const fileCount = dirtyFiles.split('\n').length;
|
|
1209
|
+
warnings.push(`${fileCount} uncommitted change${fileCount !== 1 ? 's' : ''}`);
|
|
1210
|
+
}
|
|
1211
|
+
} catch {
|
|
1212
|
+
// Worktree may be in a bad state — not a blocker for force cleanup
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
// Check for unmerged commits on the branch
|
|
1217
|
+
if (worktree.branch_name) {
|
|
1218
|
+
try {
|
|
1219
|
+
const unmergedLog = execSync(`git log main..${worktree.branch_name} --oneline`, {
|
|
1220
|
+
cwd: gitRoot,
|
|
1221
|
+
stdio: 'pipe'
|
|
1222
|
+
}).toString().trim();
|
|
1223
|
+
if (unmergedLog) {
|
|
1224
|
+
const commitCount = unmergedLog.split('\n').length;
|
|
1225
|
+
warnings.push(`${commitCount} unmerged commit${commitCount !== 1 ? 's' : ''}`);
|
|
1226
|
+
}
|
|
1227
|
+
} catch {
|
|
1228
|
+
// Branch may not exist or may not have diverged — not a blocker
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
// Display warnings and prompt for confirmation
|
|
1233
|
+
console.log(`\n⚠️ Force cleanup of active worktree for #${workItemId}: ${worktree.title}\n`);
|
|
1234
|
+
if (warnings.length > 0) {
|
|
1235
|
+
console.log(` This worktree has ${warnings.join(' and ')}.\n This data will be lost and cannot be recovered easily.\n`);
|
|
1236
|
+
} else {
|
|
1237
|
+
console.log(` No unmerged commits or uncommitted changes detected.\n`);
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// Interactive confirmation
|
|
1241
|
+
const confirmed = await new Promise((resolve) => {
|
|
1242
|
+
const readline = require('readline');
|
|
1243
|
+
const rl = readline.createInterface({
|
|
1244
|
+
input: process.stdin,
|
|
1245
|
+
output: process.stdout
|
|
1246
|
+
});
|
|
1247
|
+
rl.question('Proceed with force cleanup? (y/N): ', (answer) => {
|
|
1248
|
+
rl.close();
|
|
1249
|
+
resolve(answer.trim().toLowerCase() === 'y');
|
|
1250
|
+
});
|
|
1251
|
+
});
|
|
1252
|
+
|
|
1253
|
+
if (!confirmed) {
|
|
1254
|
+
console.log('Cancelled.');
|
|
1255
|
+
return { success: false, message: 'Force cleanup cancelled by user' };
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
// Update work item status to cancelled
|
|
1259
|
+
await new Promise((resolve, reject) => {
|
|
1260
|
+
db.run(
|
|
1261
|
+
`UPDATE work_items SET status = 'cancelled' WHERE id = ?`,
|
|
1262
|
+
[workItemId],
|
|
1263
|
+
(err) => {
|
|
1264
|
+
if (err) return reject(err);
|
|
1265
|
+
resolve();
|
|
1266
|
+
}
|
|
1267
|
+
);
|
|
1268
|
+
});
|
|
1269
|
+
console.log(`📋 Work item #${workItemId} marked as cancelled`);
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1193
1272
|
console.log(`Cleaning up worktree for #${workItemId}: ${worktree.title}`);
|
|
1194
1273
|
|
|
1195
1274
|
// Remove git worktree if it exists
|
|
@@ -1252,13 +1331,12 @@ async function cleanupWorkItem(workItemId) {
|
|
|
1252
1331
|
* Pushes feature branch, checks out main, merges, and pushes main
|
|
1253
1332
|
* Post-merge hook will mark work item as done and cleanup worktree
|
|
1254
1333
|
* @param {Object} options - Merge options
|
|
1255
|
-
* @param {boolean} options.withTransition - Hold lock for transition phase (BDD generation)
|
|
1256
1334
|
* @param {boolean} options.releaseLock - Release held lock only (no merge)
|
|
1257
|
-
* @returns {Promise<void
|
|
1335
|
+
* @returns {Promise<void>}
|
|
1258
1336
|
* @throws {Error} If no current work, git operations fail, or not in git repo
|
|
1259
1337
|
*/
|
|
1260
1338
|
async function mergeWork(options = {}) {
|
|
1261
|
-
const {
|
|
1339
|
+
const { releaseLock = false, featureBranch = null, workItemId = null } = options;
|
|
1262
1340
|
|
|
1263
1341
|
// Handle lock release-only mode first (can run from anywhere)
|
|
1264
1342
|
if (releaseLock) {
|
|
@@ -1561,6 +1639,8 @@ async function mergeWork(options = {}) {
|
|
|
1561
1639
|
// and should never block branch switching
|
|
1562
1640
|
const generatedFilesForReset = [
|
|
1563
1641
|
'cucumber-results.json',
|
|
1642
|
+
'cucumber-results.ndjson',
|
|
1643
|
+
'@rerun.txt',
|
|
1564
1644
|
'package-lock.json',
|
|
1565
1645
|
'yarn.lock',
|
|
1566
1646
|
'pnpm-lock.yaml'
|
|
@@ -1700,6 +1780,8 @@ async function mergeWork(options = {}) {
|
|
|
1700
1780
|
// Check if all conflicts are in generated files that can be auto-resolved
|
|
1701
1781
|
const generatedFiles = [
|
|
1702
1782
|
'cucumber-results.json',
|
|
1783
|
+
'cucumber-results.ndjson',
|
|
1784
|
+
'@rerun.txt',
|
|
1703
1785
|
'package-lock.json',
|
|
1704
1786
|
'yarn.lock',
|
|
1705
1787
|
'pnpm-lock.yaml'
|
|
@@ -1931,7 +2013,7 @@ async function mergeWork(options = {}) {
|
|
|
1931
2013
|
});
|
|
1932
2014
|
console.log(`✅ ${currentWork.type.charAt(0).toUpperCase() + currentWork.type.slice(1)} #${currentWork.id} ready for review`);
|
|
1933
2015
|
} else {
|
|
1934
|
-
// Chore/bug under a feature: mark as done
|
|
2016
|
+
// Chore/bug under a feature: mark as done, then check if feature is complete
|
|
1935
2017
|
console.log(`Marking ${currentWork.type} as done...`);
|
|
1936
2018
|
const completedAt = new Date().toISOString();
|
|
1937
2019
|
await new Promise((resolve, reject) => {
|
|
@@ -1945,6 +2027,60 @@ async function mergeWork(options = {}) {
|
|
|
1945
2027
|
);
|
|
1946
2028
|
});
|
|
1947
2029
|
console.log(`✅ ${currentWork.type.charAt(0).toUpperCase() + currentWork.type.slice(1)} #${currentWork.id} marked as done`);
|
|
2030
|
+
|
|
2031
|
+
// Check if all sibling chores under the parent feature are now done
|
|
2032
|
+
// If so, and mode progression is complete, set ready_for_review on the parent
|
|
2033
|
+
const parent = await new Promise((resolve, reject) => {
|
|
2034
|
+
db.get('SELECT id, type, mode FROM work_items WHERE id = ?', [currentWork.parent_id], (err, row) => {
|
|
2035
|
+
if (err) return reject(err);
|
|
2036
|
+
resolve(row);
|
|
2037
|
+
});
|
|
2038
|
+
});
|
|
2039
|
+
|
|
2040
|
+
if (parent && parent.type === 'feature') {
|
|
2041
|
+
const siblingChores = await new Promise((resolve, reject) => {
|
|
2042
|
+
db.all(
|
|
2043
|
+
'SELECT id, status FROM work_items WHERE parent_id = ? AND type = ?',
|
|
2044
|
+
[parent.id, 'chore'],
|
|
2045
|
+
(err, rows) => {
|
|
2046
|
+
if (err) return reject(err);
|
|
2047
|
+
resolve(rows || []);
|
|
2048
|
+
}
|
|
2049
|
+
);
|
|
2050
|
+
});
|
|
2051
|
+
|
|
2052
|
+
const allChoresDone = siblingChores.length > 0 && siblingChores.every(c => c.status === 'done');
|
|
2053
|
+
if (allChoresDone) {
|
|
2054
|
+
const config = await new Promise((resolve, reject) => {
|
|
2055
|
+
db.get('SELECT project_state FROM project_config WHERE id = 1', [], (err, row) => {
|
|
2056
|
+
if (err) return reject(err);
|
|
2057
|
+
resolve(row);
|
|
2058
|
+
});
|
|
2059
|
+
});
|
|
2060
|
+
const projectState = (config && config.project_state) || 'internal';
|
|
2061
|
+
const featureMode = parent.mode || 'speed';
|
|
2062
|
+
|
|
2063
|
+
let featureComplete = false;
|
|
2064
|
+
if (projectState === 'internal') {
|
|
2065
|
+
featureComplete = (featureMode === 'stable');
|
|
2066
|
+
} else if (projectState === 'external') {
|
|
2067
|
+
featureComplete = (featureMode === 'production');
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
if (featureComplete) {
|
|
2071
|
+
await new Promise((resolve, reject) => {
|
|
2072
|
+
db.run('UPDATE work_items SET ready_for_review = 1 WHERE id = ?', [parent.id], (err) => {
|
|
2073
|
+
if (err) return reject(err);
|
|
2074
|
+
resolve();
|
|
2075
|
+
});
|
|
2076
|
+
});
|
|
2077
|
+
console.log(`✅ Feature #${parent.id} ready for review (all ${featureMode} mode chores done)`);
|
|
2078
|
+
} else {
|
|
2079
|
+
const nextMode = featureMode === 'speed' ? 'stable' : 'production';
|
|
2080
|
+
console.log(`✓ All ${featureMode} mode chores done. Feature #${parent.id} ready for ${nextMode} mode.`);
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
1948
2084
|
}
|
|
1949
2085
|
}
|
|
1950
2086
|
|
|
@@ -1975,20 +2111,9 @@ async function mergeWork(options = {}) {
|
|
|
1975
2111
|
console.log(' Worktrees accumulate until cleaned up.');
|
|
1976
2112
|
}
|
|
1977
2113
|
|
|
1978
|
-
if (withTransition) {
|
|
1979
|
-
// Hold lock for transition phase (BDD generation)
|
|
1980
|
-
// Work is done and worktree is cleaned up, but lock is held so no other merges
|
|
1981
|
-
// happen while Claude generates stable scenarios on main
|
|
1982
|
-
console.log('⚠️ Merge lock held for transition phase');
|
|
1983
|
-
console.log(' Skills will release lock after generating stable scenarios');
|
|
1984
|
-
console.log(' Release lock with: jettypod work merge --release-lock');
|
|
1985
|
-
return Promise.resolve({ lockHeld: true });
|
|
1986
|
-
}
|
|
1987
|
-
|
|
1988
2114
|
return Promise.resolve();
|
|
1989
2115
|
} finally {
|
|
1990
|
-
|
|
1991
|
-
if (lock && !withTransition) {
|
|
2116
|
+
if (lock) {
|
|
1992
2117
|
try {
|
|
1993
2118
|
await lock.release();
|
|
1994
2119
|
console.log('✅ Merge lock released');
|
|
@@ -298,8 +298,12 @@ function create(type, title, description = '', parentId = null, mode = null, nee
|
|
|
298
298
|
// Set phase for features (discovery when mode=NULL, implementation when mode is set, NULL for everything else)
|
|
299
299
|
const phase = type === 'feature' ? (mode ? 'implementation' : 'discovery') : null;
|
|
300
300
|
|
|
301
|
-
|
|
302
|
-
|
|
301
|
+
// If parent has been rejected, tag this child with the current rejection round
|
|
302
|
+
let rejectionRound = null;
|
|
303
|
+
|
|
304
|
+
function doInsert() {
|
|
305
|
+
const sql = `INSERT INTO work_items (type, title, description, parent_id, epic_id, mode, needs_discovery, phase, status, plan_at_creation, rejection_round) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
|
|
306
|
+
db.run(sql, [type, title, description, parentId, epicId, mode, needsDiscovery ? 1 : 0, phase, 'backlog', planAtCreation, rejectionRound], function(err) {
|
|
303
307
|
if (err) {
|
|
304
308
|
return reject(err);
|
|
305
309
|
}
|
|
@@ -327,6 +331,21 @@ function create(type, title, description = '', parentId = null, mode = null, nee
|
|
|
327
331
|
resolve(newId);
|
|
328
332
|
}
|
|
329
333
|
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (parentId) {
|
|
337
|
+
db.get('SELECT rejection_count FROM work_items WHERE id = ?', [parentId], (err, parentRow) => {
|
|
338
|
+
if (err) {
|
|
339
|
+
return reject(err);
|
|
340
|
+
}
|
|
341
|
+
if (parentRow && parentRow.rejection_count > 0) {
|
|
342
|
+
rejectionRound = parentRow.rejection_count;
|
|
343
|
+
}
|
|
344
|
+
doInsert();
|
|
345
|
+
});
|
|
346
|
+
} else {
|
|
347
|
+
doInsert();
|
|
348
|
+
}
|
|
330
349
|
}
|
|
331
350
|
});
|
|
332
351
|
}
|
|
@@ -398,13 +417,32 @@ function filterProductionItems(items, itemsById) {
|
|
|
398
417
|
* Get all work items as hierarchical tree structure
|
|
399
418
|
* @param {boolean} includeCompleted - Include done/cancelled items (default: false)
|
|
400
419
|
* @param {boolean} showAll - Show all items including production (bypasses internal/external filtering)
|
|
420
|
+
* @param {number|null} sessionId - If provided, hide items owned by OTHER active sessions
|
|
401
421
|
* @returns {Promise<Array>} Root work items with nested children
|
|
402
422
|
* @throws {Error} If database query fails
|
|
403
423
|
*/
|
|
404
|
-
function getTree(includeCompleted = false, showAll = false) {
|
|
424
|
+
function getTree(includeCompleted = false, showAll = false, sessionId = null) {
|
|
405
425
|
return new Promise((resolve, reject) => {
|
|
406
|
-
const
|
|
407
|
-
|
|
426
|
+
const conditions = [];
|
|
427
|
+
const params = [];
|
|
428
|
+
|
|
429
|
+
if (!includeCompleted) {
|
|
430
|
+
conditions.push("(status NOT IN ('done', 'cancelled') OR status IS NULL)");
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Session isolation: hide items actively owned by OTHER sessions
|
|
434
|
+
if (sessionId) {
|
|
435
|
+
conditions.push(`work_items.id NOT IN (
|
|
436
|
+
SELECT cs.work_item_id FROM claude_sessions cs
|
|
437
|
+
WHERE cs.work_item_id IS NOT NULL
|
|
438
|
+
AND cs.status = 'active'
|
|
439
|
+
AND cs.id != ?
|
|
440
|
+
)`);
|
|
441
|
+
params.push(sessionId);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const whereClause = conditions.length > 0 ? 'WHERE ' + conditions.join(' AND ') : '';
|
|
445
|
+
db.all(`SELECT * FROM work_items ${whereClause} ORDER BY parent_id, id`, params, (err, rows) => {
|
|
408
446
|
if (err) {
|
|
409
447
|
return reject(new Error(`Failed to fetch work items: ${err.message}`));
|
|
410
448
|
}
|
|
@@ -901,6 +939,14 @@ function setScenario(id, scenarioFile) {
|
|
|
901
939
|
});
|
|
902
940
|
}
|
|
903
941
|
|
|
942
|
+
// Set QA steps
|
|
943
|
+
function setQaSteps(id, qaStepsJson) {
|
|
944
|
+
db.run(`UPDATE work_items SET qa_steps = ? WHERE id = ?`, [qaStepsJson, id], () => {
|
|
945
|
+
const steps = JSON.parse(qaStepsJson);
|
|
946
|
+
console.log(`Set #${id} qa_steps (${steps.length} steps)`);
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
|
|
904
950
|
// Set mode
|
|
905
951
|
function setMode(id, mode) {
|
|
906
952
|
return new Promise((resolve, reject) => {
|
|
@@ -1184,25 +1230,46 @@ async function main() {
|
|
|
1184
1230
|
|
|
1185
1231
|
switch(command) {
|
|
1186
1232
|
case 'create': {
|
|
1187
|
-
|
|
1188
|
-
const
|
|
1189
|
-
|
|
1233
|
+
// Support --from=<file> for truncation-safe creation via JSON file
|
|
1234
|
+
const fromArg = args.find(a => a.startsWith('--from='));
|
|
1235
|
+
let type, title, desc, parentId, mode, needsDiscovery;
|
|
1190
1236
|
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1237
|
+
if (fromArg) {
|
|
1238
|
+
const fs = require('fs');
|
|
1239
|
+
const filePath = fromArg.split('=').slice(1).join('=');
|
|
1240
|
+
try {
|
|
1241
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
1242
|
+
const data = JSON.parse(raw);
|
|
1243
|
+
type = data.type;
|
|
1244
|
+
title = data.title;
|
|
1245
|
+
desc = data.description || '';
|
|
1246
|
+
parentId = data.parent ? parseInt(data.parent) : null;
|
|
1247
|
+
mode = data.mode || null;
|
|
1248
|
+
needsDiscovery = !!data.needsDiscovery;
|
|
1249
|
+
} catch (e) {
|
|
1250
|
+
console.error(`Error reading --from file: ${e.message}`);
|
|
1251
|
+
process.exit(1);
|
|
1204
1252
|
}
|
|
1205
|
-
}
|
|
1253
|
+
} else {
|
|
1254
|
+
type = args[0];
|
|
1255
|
+
title = args[1];
|
|
1256
|
+
desc = args[2] || '';
|
|
1257
|
+
parentId = null;
|
|
1258
|
+
mode = null;
|
|
1259
|
+
needsDiscovery = false;
|
|
1260
|
+
|
|
1261
|
+
args.forEach(arg => {
|
|
1262
|
+
if (arg.startsWith('--parent=')) {
|
|
1263
|
+
parentId = parseInt(arg.split('=')[1]);
|
|
1264
|
+
}
|
|
1265
|
+
if (arg.startsWith('--mode=')) {
|
|
1266
|
+
mode = arg.split('=')[1];
|
|
1267
|
+
}
|
|
1268
|
+
if (arg === '--needs-discovery') {
|
|
1269
|
+
needsDiscovery = true;
|
|
1270
|
+
}
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1206
1273
|
|
|
1207
1274
|
try {
|
|
1208
1275
|
const newId = await create(type, title, desc, parentId, mode, needsDiscovery);
|
|
@@ -1277,7 +1344,7 @@ async function main() {
|
|
|
1277
1344
|
LEFT JOIN work_items p ON w.parent_id = p.id
|
|
1278
1345
|
LEFT JOIN work_items e ON w.epic_id = e.id
|
|
1279
1346
|
WHERE w.status = 'in_progress'
|
|
1280
|
-
ORDER BY w.id ASC
|
|
1347
|
+
ORDER BY w.ready_for_review DESC, w.id ASC
|
|
1281
1348
|
`, [], (err, rows) => {
|
|
1282
1349
|
if (err) return reject(err);
|
|
1283
1350
|
resolve(rows || []);
|
|
@@ -1511,7 +1578,23 @@ async function main() {
|
|
|
1511
1578
|
expandedIds = new Set(ids);
|
|
1512
1579
|
}
|
|
1513
1580
|
|
|
1514
|
-
//
|
|
1581
|
+
// Session isolation for backlog display
|
|
1582
|
+
const sessionId = process.env.JETTYPOD_SESSION_ID ? parseInt(process.env.JETTYPOD_SESSION_ID, 10) : null;
|
|
1583
|
+
|
|
1584
|
+
// Query for ALL in_progress items (with session filtering)
|
|
1585
|
+
const activeParams = [];
|
|
1586
|
+
let activeSessionFilter = '';
|
|
1587
|
+
if (sessionId) {
|
|
1588
|
+
activeSessionFilter = `
|
|
1589
|
+
AND w.id NOT IN (
|
|
1590
|
+
SELECT cs.work_item_id FROM claude_sessions cs
|
|
1591
|
+
WHERE cs.work_item_id IS NOT NULL
|
|
1592
|
+
AND cs.status = 'active'
|
|
1593
|
+
AND cs.id != ?
|
|
1594
|
+
)`;
|
|
1595
|
+
activeParams.push(sessionId);
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1515
1598
|
const activeItems = await new Promise((resolve, reject) => {
|
|
1516
1599
|
db.all(`
|
|
1517
1600
|
SELECT w.id, w.title, w.type, w.status,
|
|
@@ -1521,8 +1604,9 @@ async function main() {
|
|
|
1521
1604
|
LEFT JOIN work_items p ON w.parent_id = p.id
|
|
1522
1605
|
LEFT JOIN work_items e ON w.epic_id = e.id
|
|
1523
1606
|
WHERE w.status = 'in_progress'
|
|
1524
|
-
|
|
1525
|
-
|
|
1607
|
+
${activeSessionFilter}
|
|
1608
|
+
ORDER BY w.ready_for_review DESC, w.id ASC
|
|
1609
|
+
`, activeParams, (err, rows) => {
|
|
1526
1610
|
if (err) return reject(err);
|
|
1527
1611
|
resolve(rows || []);
|
|
1528
1612
|
});
|
|
@@ -1652,7 +1736,7 @@ async function main() {
|
|
|
1652
1736
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
1653
1737
|
console.log('📋 BACKLOG');
|
|
1654
1738
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
1655
|
-
const items = await getTree(false, showAll);
|
|
1739
|
+
const items = await getTree(false, showAll, sessionId);
|
|
1656
1740
|
|
|
1657
1741
|
printTree(items, '', true, expandedIds);
|
|
1658
1742
|
|
|
@@ -1728,6 +1812,40 @@ async function main() {
|
|
|
1728
1812
|
break;
|
|
1729
1813
|
}
|
|
1730
1814
|
|
|
1815
|
+
case 'set-qa-steps': {
|
|
1816
|
+
const id = parseInt(args[0]);
|
|
1817
|
+
if (isNaN(id)) {
|
|
1818
|
+
console.error('Error: Work item ID is required');
|
|
1819
|
+
console.log('Usage: jettypod work set-qa-steps <id> --from=<path>');
|
|
1820
|
+
process.exit(1);
|
|
1821
|
+
}
|
|
1822
|
+
const fromArg = args.find(a => a.startsWith('--from='));
|
|
1823
|
+
if (!fromArg) {
|
|
1824
|
+
console.error('Error: --from=<path> is required');
|
|
1825
|
+
console.log('Usage: jettypod work set-qa-steps <id> --from=<path>');
|
|
1826
|
+
process.exit(1);
|
|
1827
|
+
}
|
|
1828
|
+
const filePath = fromArg.replace('--from=', '');
|
|
1829
|
+
const fs = require('fs');
|
|
1830
|
+
if (!fs.existsSync(filePath)) {
|
|
1831
|
+
console.error(`Error: File not found: ${filePath}`);
|
|
1832
|
+
process.exit(1);
|
|
1833
|
+
}
|
|
1834
|
+
const qaJson = fs.readFileSync(filePath, 'utf8');
|
|
1835
|
+
try {
|
|
1836
|
+
const parsed = JSON.parse(qaJson);
|
|
1837
|
+
if (!Array.isArray(parsed)) {
|
|
1838
|
+
console.error('Error: QA steps must be a JSON array');
|
|
1839
|
+
process.exit(1);
|
|
1840
|
+
}
|
|
1841
|
+
} catch (e) {
|
|
1842
|
+
console.error(`Error: Invalid JSON in ${filePath}: ${e.message}`);
|
|
1843
|
+
process.exit(1);
|
|
1844
|
+
}
|
|
1845
|
+
setQaSteps(id, qaJson);
|
|
1846
|
+
break;
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1731
1849
|
case 'current': {
|
|
1732
1850
|
if (!args[0]) {
|
|
1733
1851
|
// No ID provided - show current work
|
|
@@ -2573,6 +2691,9 @@ Commands:
|
|
|
2573
2691
|
jettypod work set-scenario <id> <file>
|
|
2574
2692
|
Set scenario_file for a feature (e.g., features/my-feature.feature)
|
|
2575
2693
|
|
|
2694
|
+
jettypod work set-qa-steps <id> --from=<path>
|
|
2695
|
+
Set QA steps from a JSON file (array of {section, text, detail})
|
|
2696
|
+
|
|
2576
2697
|
jettypod work current <id>
|
|
2577
2698
|
Set as current work item
|
|
2578
2699
|
|