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
package/lib/worktree-manager.js
CHANGED
|
@@ -21,7 +21,7 @@ const config = require('./config');
|
|
|
21
21
|
/**
|
|
22
22
|
* Valid worktree statuses
|
|
23
23
|
*/
|
|
24
|
-
const VALID_STATUSES = ['active', 'merging', 'cleanup_pending', '
|
|
24
|
+
const VALID_STATUSES = ['active', 'merging', 'cleanup_pending', 'cleaned'];
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Create a worktree for a work item
|
|
@@ -352,7 +352,7 @@ async function getAllActiveWorktrees(options = {}) {
|
|
|
352
352
|
* Mark worktree status
|
|
353
353
|
*
|
|
354
354
|
* @param {number} worktreeId - Worktree ID
|
|
355
|
-
* @param {string} status - New status (active|merging|cleanup_pending|
|
|
355
|
+
* @param {string} status - New status (active|merging|cleanup_pending|cleaned)
|
|
356
356
|
* @param {Object} options - Optional configuration
|
|
357
357
|
* @param {Object} options.db - Database connection (defaults to global database)
|
|
358
358
|
* @returns {Promise<void>}
|
|
@@ -671,13 +671,13 @@ async function cleanupWorktree(worktreeId, options = {}) {
|
|
|
671
671
|
}
|
|
672
672
|
}
|
|
673
673
|
|
|
674
|
-
// Step 5: Mark as
|
|
675
|
-
// Using '
|
|
674
|
+
// Step 5: Mark as cleaned (we never delete DB entries)
|
|
675
|
+
// Using 'cleaned' as the final state for cleaned up worktrees
|
|
676
676
|
// This preserves the audit trail
|
|
677
677
|
await new Promise((resolve, reject) => {
|
|
678
678
|
db.run(
|
|
679
679
|
'UPDATE worktrees SET status = ?, updated_at = datetime(\'now\') WHERE id = ?',
|
|
680
|
-
['
|
|
680
|
+
['cleaned', worktreeId],
|
|
681
681
|
(err) => {
|
|
682
682
|
if (err) reject(err);
|
|
683
683
|
else resolve();
|
|
@@ -686,12 +686,12 @@ async function cleanupWorktree(worktreeId, options = {}) {
|
|
|
686
686
|
});
|
|
687
687
|
|
|
688
688
|
} catch (err) {
|
|
689
|
-
// On any failure, mark as
|
|
689
|
+
// On any failure, mark as cleaned (terminal state)
|
|
690
690
|
try {
|
|
691
691
|
await new Promise((resolve, reject) => {
|
|
692
692
|
db.run(
|
|
693
693
|
'UPDATE worktrees SET status = ?, updated_at = datetime(\'now\') WHERE id = ?',
|
|
694
|
-
['
|
|
694
|
+
['cleaned', worktreeId],
|
|
695
695
|
(err) => {
|
|
696
696
|
if (err) reject(err);
|
|
697
697
|
else resolve();
|
|
@@ -699,7 +699,7 @@ async function cleanupWorktree(worktreeId, options = {}) {
|
|
|
699
699
|
);
|
|
700
700
|
});
|
|
701
701
|
} catch (dbErr) {
|
|
702
|
-
console.error(`Failed to mark worktree as
|
|
702
|
+
console.error(`Failed to mark worktree as cleaned: ${dbErr.message}`);
|
|
703
703
|
}
|
|
704
704
|
|
|
705
705
|
throw new Error(`Worktree cleanup failed: ${err.message}`);
|
|
@@ -95,7 +95,7 @@ async function reconcileState(db, repoPath, options = {}) {
|
|
|
95
95
|
if (cleanup && staleEntries.length > 0) {
|
|
96
96
|
for (const entry of staleEntries) {
|
|
97
97
|
try {
|
|
98
|
-
await
|
|
98
|
+
await markWorktreeCleaned(db, entry.id);
|
|
99
99
|
results.cleanupAttempts.staleDbEntries++;
|
|
100
100
|
} catch (err) {
|
|
101
101
|
results.errors.push(`Failed to mark stale entry as corrupted: ${err.message}`);
|
|
@@ -127,7 +127,7 @@ async function reconcileState(db, repoPath, options = {}) {
|
|
|
127
127
|
results.cleanupAttempts.gitFilesystemMismatches++;
|
|
128
128
|
results.gitCommands.push(`git worktree add --force`);
|
|
129
129
|
} catch (err) {
|
|
130
|
-
await
|
|
130
|
+
await markWorktreeCleaned(db, mismatch.id);
|
|
131
131
|
results.errors.push(`Failed to re-register worktree ${mismatch.path}: ${err.message}`);
|
|
132
132
|
}
|
|
133
133
|
}
|
|
@@ -342,13 +342,13 @@ async function detectStaleDbEntries(db, repoPath) {
|
|
|
342
342
|
}
|
|
343
343
|
|
|
344
344
|
/**
|
|
345
|
-
* Mark worktree as
|
|
345
|
+
* Mark worktree as cleaned in database (terminal state)
|
|
346
346
|
*/
|
|
347
|
-
async function
|
|
347
|
+
async function markWorktreeCleaned(db, worktreeId) {
|
|
348
348
|
return new Promise((resolve, reject) => {
|
|
349
349
|
db.run(
|
|
350
350
|
'UPDATE worktrees SET status = ?, updated_at = datetime(\'now\') WHERE id = ?',
|
|
351
|
-
['
|
|
351
|
+
['cleaned', worktreeId],
|
|
352
352
|
(err) => {
|
|
353
353
|
if (err) reject(err);
|
|
354
354
|
else resolve();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jettypod",
|
|
3
|
-
"version": "4.4.
|
|
3
|
+
"version": "4.4.120",
|
|
4
4
|
"description": "AI-powered development workflow manager with TDD, BDD, and automatic test generation",
|
|
5
5
|
"main": "jettypod.js",
|
|
6
6
|
"bin": {
|
|
@@ -34,8 +34,10 @@
|
|
|
34
34
|
"test:unit": "NODE_ENV=test jest",
|
|
35
35
|
"test:unit:watch": "NODE_ENV=test jest --watch",
|
|
36
36
|
"test:cleanup": "node scripts/test-cleanup.js",
|
|
37
|
+
"version": "node -e \"const v=require('./package.json').version; const f='apps/dashboard/package.json'; const p=JSON.parse(require('fs').readFileSync(f)); p.version=v; require('fs').writeFileSync(f, JSON.stringify(p,null,2)+'\\n')\"",
|
|
37
38
|
"prepublishOnly": "echo '🔄 Syncing skills to templates...' && cp -r .claude/skills/* skills-templates/ && (git diff --quiet skills-templates/ || (git add skills-templates/ && git commit -m 'chore: Sync skills-templates')) && echo '✅ Skills synced' && echo '🔨 Building dashboard...' && cd apps/dashboard && npm install && npm run build && echo '✅ Dashboard built'",
|
|
38
39
|
"postpublish": "git push origin main --tags && echo '✅ Pushed version commit and tags'",
|
|
40
|
+
"postinstall": "node scripts/postinstall.js",
|
|
39
41
|
"dashboard": "npm run dev --prefix apps/dashboard",
|
|
40
42
|
"dashboard:build": "npm run build --prefix apps/dashboard",
|
|
41
43
|
"dashboard:start": "npm run start --prefix apps/dashboard"
|
|
@@ -48,13 +50,18 @@
|
|
|
48
50
|
"ws": "^8.18.3"
|
|
49
51
|
},
|
|
50
52
|
"jest": {
|
|
53
|
+
"setupFiles": [
|
|
54
|
+
"./jest.setup.js"
|
|
55
|
+
],
|
|
51
56
|
"testEnvironment": "node",
|
|
52
57
|
"testMatch": [
|
|
53
58
|
"**/__tests__/**/*.js",
|
|
54
59
|
"**/*.test.js"
|
|
55
60
|
],
|
|
56
61
|
"testPathIgnorePatterns": [
|
|
57
|
-
"/node_modules/"
|
|
62
|
+
"/node_modules/",
|
|
63
|
+
"/apps/dashboard/dist/",
|
|
64
|
+
"/.jettypod-work/"
|
|
58
65
|
],
|
|
59
66
|
"collectCoverage": false,
|
|
60
67
|
"coverageDirectory": "coverage"
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Convert cucumber ndjson (message format) to standard cucumber JSON format.
|
|
3
|
+
// Usage: node scripts/ndjson-to-cucumber-json.js [input.ndjson] [output.json]
|
|
4
|
+
// Defaults: cucumber-results.ndjson -> cucumber-results.json
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
function convert(ndjsonPath, jsonPath) {
|
|
10
|
+
const raw = fs.readFileSync(ndjsonPath, 'utf-8');
|
|
11
|
+
const lines = raw.split('\n').filter(l => l.trim());
|
|
12
|
+
|
|
13
|
+
// Lookup tables
|
|
14
|
+
const gherkinDocuments = new Map(); // uri -> gherkinDocument
|
|
15
|
+
const pickles = new Map(); // pickle.id -> pickle
|
|
16
|
+
const testCases = new Map(); // testCase.id -> testCase
|
|
17
|
+
const testCaseStartedMap = new Map(); // testCaseStarted.id -> testCaseStarted
|
|
18
|
+
const stepDefinitions = new Map(); // stepDefinition.id -> stepDefinition
|
|
19
|
+
|
|
20
|
+
// Results
|
|
21
|
+
const testStepResults = new Map(); // testCaseStartedId -> [{stepId, result}]
|
|
22
|
+
|
|
23
|
+
for (const line of lines) {
|
|
24
|
+
let msg;
|
|
25
|
+
try {
|
|
26
|
+
msg = JSON.parse(line);
|
|
27
|
+
} catch {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (msg.gherkinDocument) {
|
|
32
|
+
gherkinDocuments.set(msg.gherkinDocument.uri, msg.gherkinDocument);
|
|
33
|
+
}
|
|
34
|
+
if (msg.pickle) {
|
|
35
|
+
pickles.set(msg.pickle.id, msg.pickle);
|
|
36
|
+
}
|
|
37
|
+
if (msg.stepDefinition) {
|
|
38
|
+
stepDefinitions.set(msg.stepDefinition.id, msg.stepDefinition);
|
|
39
|
+
}
|
|
40
|
+
if (msg.testCase) {
|
|
41
|
+
testCases.set(msg.testCase.id, msg.testCase);
|
|
42
|
+
}
|
|
43
|
+
if (msg.testCaseStarted) {
|
|
44
|
+
testCaseStartedMap.set(msg.testCaseStarted.id, msg.testCaseStarted);
|
|
45
|
+
testStepResults.set(msg.testCaseStarted.id, []);
|
|
46
|
+
}
|
|
47
|
+
if (msg.testStepFinished) {
|
|
48
|
+
const tsf = msg.testStepFinished;
|
|
49
|
+
const arr = testStepResults.get(tsf.testCaseStartedId);
|
|
50
|
+
if (arr) {
|
|
51
|
+
arr.push({
|
|
52
|
+
testStepId: tsf.testStepId,
|
|
53
|
+
result: tsf.testStepResult,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Build cucumber JSON structure grouped by feature URI
|
|
60
|
+
const featureMap = new Map(); // uri -> { feature data }
|
|
61
|
+
|
|
62
|
+
for (const [tcsId, tcs] of testCaseStartedMap) {
|
|
63
|
+
const testCase = testCases.get(tcs.testCaseId);
|
|
64
|
+
if (!testCase) continue;
|
|
65
|
+
const pickle = pickles.get(testCase.pickleId);
|
|
66
|
+
if (!pickle) continue;
|
|
67
|
+
|
|
68
|
+
const uri = pickle.uri;
|
|
69
|
+
if (!featureMap.has(uri)) {
|
|
70
|
+
const doc = gherkinDocuments.get(uri);
|
|
71
|
+
featureMap.set(uri, {
|
|
72
|
+
uri,
|
|
73
|
+
name: doc?.feature?.name || uri,
|
|
74
|
+
description: doc?.feature?.description || '',
|
|
75
|
+
keyword: doc?.feature?.keyword || 'Feature',
|
|
76
|
+
elements: [],
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const stepResults = testStepResults.get(tcsId) || [];
|
|
81
|
+
const steps = [];
|
|
82
|
+
|
|
83
|
+
for (const testStep of testCase.testSteps) {
|
|
84
|
+
// Skip hook steps (no pickleStepId)
|
|
85
|
+
if (!testStep.pickleStepId) continue;
|
|
86
|
+
|
|
87
|
+
const pickleStep = pickle.steps.find(s => s.id === testStep.pickleStepId);
|
|
88
|
+
if (!pickleStep) continue;
|
|
89
|
+
|
|
90
|
+
// Find the result for this step
|
|
91
|
+
const stepResult = stepResults.find(r => r.testStepId === testStep.id);
|
|
92
|
+
const result = stepResult?.result;
|
|
93
|
+
|
|
94
|
+
const dur = result?.duration || { seconds: 0, nanos: 0 };
|
|
95
|
+
const durationNanos = (dur.seconds || 0) * 1e9 + (dur.nanos || 0);
|
|
96
|
+
|
|
97
|
+
// Get keyword from gherkin document
|
|
98
|
+
let keyword = '';
|
|
99
|
+
const doc = gherkinDocuments.get(uri);
|
|
100
|
+
if (doc?.feature) {
|
|
101
|
+
for (const child of doc.feature.children || []) {
|
|
102
|
+
const scenario = child.scenario;
|
|
103
|
+
if (!scenario) continue;
|
|
104
|
+
for (const gherkinStep of scenario.steps) {
|
|
105
|
+
if (pickleStep.astNodeIds?.includes(gherkinStep.id)) {
|
|
106
|
+
keyword = gherkinStep.keyword;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (keyword) break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
steps.push({
|
|
115
|
+
keyword: keyword || 'Step ',
|
|
116
|
+
name: pickleStep.text || '',
|
|
117
|
+
result: {
|
|
118
|
+
status: (result?.status || 'UNKNOWN').toLowerCase(),
|
|
119
|
+
duration: durationNanos,
|
|
120
|
+
error_message: result?.message || undefined,
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
featureMap.get(uri).elements.push({
|
|
126
|
+
type: 'scenario',
|
|
127
|
+
name: pickle.name,
|
|
128
|
+
steps,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const output = Array.from(featureMap.values());
|
|
133
|
+
fs.writeFileSync(jsonPath, JSON.stringify(output, null, 2));
|
|
134
|
+
return output.reduce((sum, f) => sum + f.elements.length, 0);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// CLI entry point
|
|
138
|
+
if (require.main === module) {
|
|
139
|
+
const projectRoot = path.resolve(__dirname, '..');
|
|
140
|
+
const ndjsonPath = process.argv[2] || path.join(projectRoot, 'cucumber-results.ndjson');
|
|
141
|
+
const jsonPath = process.argv[3] || path.join(projectRoot, 'cucumber-results.json');
|
|
142
|
+
|
|
143
|
+
if (!fs.existsSync(ndjsonPath)) {
|
|
144
|
+
console.error(`No ndjson file found at ${ndjsonPath}`);
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const count = convert(ndjsonPath, jsonPath);
|
|
149
|
+
console.log(`Converted ${count} scenarios to ${jsonPath}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
module.exports = { convert };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Records the source directory on global install so jettypod can auto-update.
|
|
4
|
+
// Writes ~/.jettypod-dev.json with the source path for staleness detection.
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
|
|
10
|
+
// Only run for global installs
|
|
11
|
+
if (process.env.npm_config_global !== 'true') process.exit(0);
|
|
12
|
+
|
|
13
|
+
const sourceDir = process.env.INIT_CWD;
|
|
14
|
+
if (!sourceDir) process.exit(0);
|
|
15
|
+
|
|
16
|
+
// Verify source dir looks like a jettypod repo (has jettypod.js)
|
|
17
|
+
if (!fs.existsSync(path.join(sourceDir, 'jettypod.js'))) process.exit(0);
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const devInfo = { sourceDir };
|
|
21
|
+
const devFile = path.join(os.homedir(), '.jettypod-dev.json');
|
|
22
|
+
fs.writeFileSync(devFile, JSON.stringify(devInfo, null, 2) + '\n');
|
|
23
|
+
} catch {
|
|
24
|
+
// Non-fatal — skip silently
|
|
25
|
+
}
|
|
@@ -44,7 +44,7 @@ When this skill is activated, you are implementing a bug fix directly on the bug
|
|
|
44
44
|
|
|
45
45
|
Your job: Implement the fix, write a regression test, and verify everything passes.
|
|
46
46
|
|
|
47
|
-
##
|
|
47
|
+
## Critical Context
|
|
48
48
|
|
|
49
49
|
**You are working in an isolated git worktree:**
|
|
50
50
|
- `work start [bug-id]` already created a dedicated worktree
|
|
@@ -55,7 +55,7 @@ Your job: Implement the fix, write a regression test, and verify everything pass
|
|
|
55
55
|
|
|
56
56
|
---
|
|
57
57
|
|
|
58
|
-
##
|
|
58
|
+
## SHELL CWD RECOVERY
|
|
59
59
|
|
|
60
60
|
**If ALL bash commands start failing with "Error: Exit code 1" and no output:**
|
|
61
61
|
|
|
@@ -69,7 +69,7 @@ Your shell's working directory was likely inside a worktree that was deleted. Th
|
|
|
69
69
|
|
|
70
70
|
---
|
|
71
71
|
|
|
72
|
-
##
|
|
72
|
+
## PRE-FLIGHT VALIDATION (REQUIRED)
|
|
73
73
|
|
|
74
74
|
**Before proceeding, validate the worktree exists.**
|
|
75
75
|
|
|
@@ -81,9 +81,9 @@ sqlite3 .jettypod/work.db "SELECT wi.id, wi.title, wi.status, wt.worktree_path,
|
|
|
81
81
|
|
|
82
82
|
| worktree_path | What it means | Action |
|
|
83
83
|
|---------------|---------------|--------|
|
|
84
|
-
| **Has a path** |
|
|
85
|
-
| **NULL or empty** |
|
|
86
|
-
| **No rows returned** |
|
|
84
|
+
| **Has a path** | Worktree exists, ready to proceed | Continue to Step 0 |
|
|
85
|
+
| **NULL or empty** | `work start` was not called | **STOP - run `jettypod work start [bug-id]` first** |
|
|
86
|
+
| **No rows returned** | No bug is in progress | **STOP - verify the bug exists and run `work start`** |
|
|
87
87
|
|
|
88
88
|
**After pre-flight passes, emit gate signal:**
|
|
89
89
|
|
|
@@ -114,7 +114,7 @@ The bug-planning skill embedded implementation guidance:
|
|
|
114
114
|
|
|
115
115
|
```
|
|
116
116
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
117
|
-
|
|
117
|
+
BUG MODE: Fixing #[bug-id]
|
|
118
118
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
119
119
|
|
|
120
120
|
Bug: #[id] [title]
|
|
@@ -176,14 +176,14 @@ sqlite3 .jettypod/work.db "SELECT project_state FROM project_config WHERE id = 1
|
|
|
176
176
|
|
|
177
177
|
```
|
|
178
178
|
━━━ Iteration 1 ━━━
|
|
179
|
-
|
|
180
|
-
|
|
179
|
+
Reading: [file:line from breadcrumbs]
|
|
180
|
+
Change: [what you're fixing]
|
|
181
181
|
```
|
|
182
182
|
|
|
183
183
|
**After implementation:**
|
|
184
184
|
|
|
185
185
|
```
|
|
186
|
-
|
|
186
|
+
Fix Implemented
|
|
187
187
|
|
|
188
188
|
Changes made:
|
|
189
189
|
- [Change 1]
|
|
@@ -197,6 +197,16 @@ Now writing regression test...
|
|
|
197
197
|
|
|
198
198
|
---
|
|
199
199
|
|
|
200
|
+
### BDD Step Definition Constraints (ENFORCED)
|
|
201
|
+
|
|
202
|
+
Before writing any step definitions, read `templates/bdd-guidance.md`. BANNED in step defs: `setTimeout`/`sleep`, module-level `let`/`var`, raw SQL, assertions in Given/When, loops/branching, self-fulfilling mocks (setting state then asserting it). Steps must be 1-5 lines calling one helper via Cucumber World (`this`). Read `features/support/helpers/` and reuse before creating.
|
|
203
|
+
|
|
204
|
+
**Scaffolding:** If `features/support/helpers/` does not exist in the project, scaffold it before writing step definitions. Copy `templates/bdd-scaffolding/wait.js` to `features/support/helpers/wait.js` and `templates/bdd-scaffolding/world.js` to `features/support/world.js`.
|
|
205
|
+
|
|
206
|
+
**Helper Reuse:** Before writing step definitions, glob `features/support/helpers/**/*.js` and read each file. Import and call existing helpers in your steps — never duplicate functionality that already exists.
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
200
210
|
### Step 2: Write Regression Test
|
|
201
211
|
|
|
202
212
|
**Goal:** Create a test that verifies the bug is fixed and prevents regression.
|
|
@@ -239,7 +249,7 @@ npx cucumber-js features/<bug-slug>.feature
|
|
|
239
249
|
**Confirm test passes:**
|
|
240
250
|
|
|
241
251
|
```
|
|
242
|
-
|
|
252
|
+
Regression Test Passing
|
|
243
253
|
|
|
244
254
|
Test: features/<bug-slug>.feature
|
|
245
255
|
Status: PASSING
|
|
@@ -272,7 +282,7 @@ npx cucumber-js features/<related-feature>.feature 2>/dev/null || true
|
|
|
272
282
|
**If regressions found:**
|
|
273
283
|
|
|
274
284
|
```
|
|
275
|
-
|
|
285
|
+
Regression Detected
|
|
276
286
|
|
|
277
287
|
Failing test: [test name]
|
|
278
288
|
Error: [error message]
|
|
@@ -285,16 +295,18 @@ Fix the regression and re-run tests. Iterate until all related tests pass.
|
|
|
285
295
|
**When all related tests pass:**
|
|
286
296
|
|
|
287
297
|
```
|
|
288
|
-
|
|
298
|
+
Related Tests Passing
|
|
289
299
|
|
|
290
|
-
- Regression test:
|
|
291
|
-
- Related area tests:
|
|
300
|
+
- Regression test: PASS
|
|
301
|
+
- Related area tests: [X] scenarios passing
|
|
292
302
|
|
|
293
303
|
Ready to commit and merge.
|
|
294
304
|
```
|
|
295
305
|
|
|
296
306
|
**Proceed to Step 4.**
|
|
297
307
|
|
|
308
|
+
**Quality Gate:** Before committing, review every `.steps.js` file you wrote or modified. Check for banlist violations: `setTimeout`/`sleep`, module-level `let`/`var`, raw SQL, assertions in Given/When, self-fulfilling mocks, steps longer than 5 lines. Fix violations before proceeding.
|
|
309
|
+
|
|
298
310
|
---
|
|
299
311
|
|
|
300
312
|
### Step 4: Commit and Merge
|
|
@@ -315,7 +327,7 @@ Fix: [What was changed]
|
|
|
315
327
|
|
|
316
328
|
Closes #<bug-id>
|
|
317
329
|
|
|
318
|
-
|
|
330
|
+
Generated with [Claude Code](https://claude.com/claude-code)
|
|
319
331
|
|
|
320
332
|
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>"
|
|
321
333
|
```
|
|
@@ -342,13 +354,10 @@ jettypod work cleanup <bug-id>
|
|
|
342
354
|
|
|
343
355
|
---
|
|
344
356
|
|
|
345
|
-
### Step 5:
|
|
357
|
+
### Step 5: Completion
|
|
346
358
|
|
|
347
|
-
**
|
|
348
|
-
|
|
349
|
-
```bash
|
|
350
|
-
jettypod work status <bug-id> done
|
|
351
|
-
```
|
|
359
|
+
**The merge command automatically sets the bug as ready for review.**
|
|
360
|
+
It will appear with accept/reject buttons on the kanban board. Do NOT call `jettypod work status <bug-id> done` — that bypasses the review gate.
|
|
352
361
|
|
|
353
362
|
**Emit gate signal:**
|
|
354
363
|
|
|
@@ -360,11 +369,11 @@ jettypod ui gate complete --data='{"summary":"Bug fixed: [brief description]","f
|
|
|
360
369
|
|
|
361
370
|
```
|
|
362
371
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
363
|
-
|
|
372
|
+
Bug Fix Complete
|
|
364
373
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
365
374
|
|
|
366
|
-
|
|
367
|
-
|
|
375
|
+
Bug: #[bug-id] [title]
|
|
376
|
+
Regression test: features/<bug-slug>.feature
|
|
368
377
|
|
|
369
378
|
What was fixed:
|
|
370
379
|
- Root cause: [brief description]
|
|
@@ -387,7 +396,7 @@ Before ending bug-mode skill, ensure:
|
|
|
387
396
|
- [ ] Fix committed with descriptive message
|
|
388
397
|
- [ ] Bug merged to main
|
|
389
398
|
- [ ] Worktree cleaned up
|
|
390
|
-
- [ ] Bug
|
|
399
|
+
- [ ] Bug merged (review gate set automatically by merge command)
|
|
391
400
|
|
|
392
401
|
---
|
|
393
402
|
|
|
@@ -403,7 +412,9 @@ cd <main-repo> # Change to main repo
|
|
|
403
412
|
jettypod work cleanup <bug-id> # Clean up worktree
|
|
404
413
|
```
|
|
405
414
|
|
|
406
|
-
**
|
|
415
|
+
**Force-cleanup stale worktree** (when merge is blocked or fix was superseded):
|
|
407
416
|
```bash
|
|
408
|
-
jettypod work
|
|
417
|
+
jettypod work cleanup --force <bug-id> # Bypasses merge requirement, prompts for confirmation
|
|
409
418
|
```
|
|
419
|
+
|
|
420
|
+
**Do NOT mark bug as done manually — the merge command sets the review gate automatically.**
|
|
@@ -30,7 +30,7 @@ When this skill is activated, you are investigating a bug to identify root cause
|
|
|
30
30
|
**Gather from user:**
|
|
31
31
|
|
|
32
32
|
```
|
|
33
|
-
|
|
33
|
+
Bug Investigation
|
|
34
34
|
|
|
35
35
|
I need to understand the bug before we investigate.
|
|
36
36
|
|
|
@@ -61,7 +61,7 @@ I need to understand the bug before we investigate.
|
|
|
61
61
|
|
|
62
62
|
```
|
|
63
63
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
64
|
-
|
|
64
|
+
Bug Report
|
|
65
65
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
66
66
|
|
|
67
67
|
**Symptom:** [What's happening]
|
|
@@ -105,7 +105,7 @@ git log --oneline -10 -- <relevant-files>
|
|
|
105
105
|
**Display hypotheses:**
|
|
106
106
|
|
|
107
107
|
```
|
|
108
|
-
|
|
108
|
+
Hypotheses
|
|
109
109
|
|
|
110
110
|
Based on symptoms and codebase analysis:
|
|
111
111
|
|
|
@@ -148,12 +148,12 @@ Starting evidence gathering with H1...
|
|
|
148
148
|
```
|
|
149
149
|
━━━ Testing H1: [Title] ━━━
|
|
150
150
|
|
|
151
|
-
|
|
152
|
-
|
|
151
|
+
Reading: [file:lines]
|
|
152
|
+
Looking for: [specific issue]
|
|
153
153
|
|
|
154
154
|
**Finding:** [What you discovered]
|
|
155
155
|
|
|
156
|
-
**Verdict:**
|
|
156
|
+
**Verdict:** CONFIRMED / ELIMINATED / PARTIAL
|
|
157
157
|
```
|
|
158
158
|
|
|
159
159
|
**If ELIMINATED:** Move to next hypothesis.
|
|
@@ -163,7 +163,7 @@ Starting evidence gathering with H1...
|
|
|
163
163
|
**If all hypotheses eliminated:**
|
|
164
164
|
|
|
165
165
|
```
|
|
166
|
-
|
|
166
|
+
Initial hypotheses eliminated
|
|
167
167
|
|
|
168
168
|
What we learned:
|
|
169
169
|
- H1: Eliminated because [reason]
|
|
@@ -182,11 +182,11 @@ Loop back to Phase 2 with new information.
|
|
|
182
182
|
|
|
183
183
|
**Goal:** Confirm root cause with user before creating work item.
|
|
184
184
|
|
|
185
|
-
|
|
185
|
+
**ASYNC BOUNDARY - Wait for user confirmation**
|
|
186
186
|
|
|
187
187
|
```
|
|
188
188
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
189
|
-
|
|
189
|
+
Root Cause Identified
|
|
190
190
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
191
191
|
|
|
192
192
|
**What's broken:** [Specific code/logic]
|
|
@@ -212,24 +212,20 @@ If user disagrees or adds context, revise understanding and confirm again.
|
|
|
212
212
|
|
|
213
213
|
**Goal:** Create tracked bug work item with breadcrumbs for implementation.
|
|
214
214
|
|
|
215
|
-
**Create bug work item with description:**
|
|
215
|
+
**Create bug work item with description (using --from for truncation safety):**
|
|
216
216
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
- [Step 1]
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
Files to modify:
|
|
226
|
-
- [file1]: [what to change]
|
|
227
|
-
- [file2]: [what to change]
|
|
217
|
+
Use the **Write** tool to create `/tmp/jettypod-create.json`:
|
|
218
|
+
```json
|
|
219
|
+
{
|
|
220
|
+
"type": "bug",
|
|
221
|
+
"title": "[Bug title - brief description]",
|
|
222
|
+
"description": "Root cause: [What's broken and why]\nLocation: [file:line]\n\nFix approach:\n- [Step 1]\n- [Step 2]\n\nFiles to modify:\n- [file1]: [what to change]\n- [file2]: [what to change]\n\nVerification:\n- Regression test passes\n- Original bug no longer reproduces\n- [Any other checks]"
|
|
223
|
+
}
|
|
224
|
+
```
|
|
228
225
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
- [Any other checks]"
|
|
226
|
+
Then run via **Bash**:
|
|
227
|
+
```bash
|
|
228
|
+
jettypod work create --from=/tmp/jettypod-create.json
|
|
233
229
|
```
|
|
234
230
|
|
|
235
231
|
**Capture the bug ID from output.**
|
|
@@ -252,12 +248,12 @@ jettypod ui gate work-created --data='{"id":<bug-id>,"title":"[bug title]"}'
|
|
|
252
248
|
|
|
253
249
|
```
|
|
254
250
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
255
|
-
|
|
251
|
+
Bug Planning Complete
|
|
256
252
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
257
253
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
254
|
+
Bug: #<bug-id> [title]
|
|
255
|
+
Root cause: [file:line]
|
|
256
|
+
Fix approach: [brief summary]
|
|
261
257
|
|
|
262
258
|
Ready to implement the fix?
|
|
263
259
|
```
|