jettypod 4.4.82 → 4.4.84

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/lib/db-export.js CHANGED
@@ -83,17 +83,16 @@ async function exportWorkDb() {
83
83
  try {
84
84
  fs.writeFileSync(outputPath, JSON.stringify(data, null, 2), 'utf8');
85
85
  } catch (err) {
86
- // Log warning but don't throw - we don't want to block git operations
87
- console.error('Pre-commit hook warning: Failed to export work.json');
86
+ // Throw error to block commit - snapshot backup is critical
87
+ let message = 'Failed to export work.json';
88
88
  if (err.code === 'EACCES') {
89
- console.error(' Permission denied - check directory permissions');
89
+ message += ': Permission denied - check directory permissions';
90
90
  } else if (err.code === 'ENOSPC') {
91
- console.error(' No space left on device');
91
+ message += ': No space left on device';
92
92
  } else {
93
- console.error(` ${err.message}`);
93
+ message += `: ${err.message}`;
94
94
  }
95
- console.error(' Commit will proceed but snapshots were not updated');
96
- // Return path anyway so commit can continue
95
+ throw new Error(message);
97
96
  }
98
97
 
99
98
  return outputPath;
@@ -115,15 +114,16 @@ async function exportDatabaseDb() {
115
114
  try {
116
115
  fs.writeFileSync(outputPath, JSON.stringify({}, null, 2), 'utf8');
117
116
  } catch (err) {
118
- console.error('Pre-commit hook warning: Failed to export database.json');
117
+ // Throw error to block commit - snapshot backup is critical
118
+ let message = 'Failed to export database.json';
119
119
  if (err.code === 'EACCES') {
120
- console.error(' Permission denied - check directory permissions');
120
+ message += ': Permission denied - check directory permissions';
121
121
  } else if (err.code === 'ENOSPC') {
122
- console.error(' No space left on device');
122
+ message += ': No space left on device';
123
123
  } else {
124
- console.error(` ${err.message}`);
124
+ message += `: ${err.message}`;
125
125
  }
126
- console.error(' Commit will proceed but snapshots were not updated');
126
+ throw new Error(message);
127
127
  }
128
128
  return outputPath;
129
129
  }
@@ -142,15 +142,18 @@ async function exportDatabaseDb() {
142
142
  try {
143
143
  fs.writeFileSync(outputPath, JSON.stringify(data, null, 2), 'utf8');
144
144
  } catch (writeErr) {
145
- console.error('Pre-commit hook warning: Failed to export database.json');
145
+ // Throw error to block commit - snapshot backup is critical
146
+ let message = 'Failed to export database.json';
146
147
  if (writeErr.code === 'EACCES') {
147
- console.error(' Permission denied - check directory permissions');
148
+ message += ': Permission denied - check directory permissions';
148
149
  } else if (writeErr.code === 'ENOSPC') {
149
- console.error(' No space left on device');
150
+ message += ': No space left on device';
150
151
  } else {
151
- console.error(` ${writeErr.message}`);
152
+ message += `: ${writeErr.message}`;
152
153
  }
153
- console.error(' Commit will proceed but snapshots were not updated');
154
+ db.close();
155
+ reject(new Error(message));
156
+ return;
154
157
  }
155
158
 
156
159
  db.close((closeErr) => {
package/lib/db-import.js CHANGED
@@ -2,6 +2,109 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { getDb, getJettypodDir } = require('./database');
4
4
 
5
+ // Valid values for work_items fields
6
+ const VALID_TYPES = ['epic', 'feature', 'chore', 'bug'];
7
+ const VALID_STATUSES = ['backlog', 'todo', 'in_progress', 'done', 'cancelled'];
8
+
9
+ /**
10
+ * Validate a single work item against the schema
11
+ * @param {Object} item - Work item to validate
12
+ * @param {number} index - Index in array for error messages
13
+ * @returns {{valid: boolean, errors: string[]}}
14
+ */
15
+ function validateWorkItem(item, index) {
16
+ const errors = [];
17
+
18
+ if (typeof item !== 'object' || item === null) {
19
+ return { valid: false, errors: [`Item ${index}: must be an object`] };
20
+ }
21
+
22
+ // Required: id must be a number
23
+ if (item.id === undefined || item.id === null) {
24
+ errors.push(`Item ${index}: missing required field 'id'`);
25
+ } else if (typeof item.id !== 'number' || !Number.isInteger(item.id)) {
26
+ errors.push(`Item ${index}: 'id' must be an integer`);
27
+ }
28
+
29
+ // Required: type must be a valid string
30
+ if (item.type === undefined || item.type === null) {
31
+ errors.push(`Item ${index}: missing required field 'type'`);
32
+ } else if (typeof item.type !== 'string') {
33
+ errors.push(`Item ${index}: 'type' must be a string`);
34
+ } else if (!VALID_TYPES.includes(item.type)) {
35
+ errors.push(`Item ${index}: 'type' must be one of: ${VALID_TYPES.join(', ')}`);
36
+ }
37
+
38
+ // Required: title must be a string
39
+ if (item.title === undefined || item.title === null) {
40
+ errors.push(`Item ${index}: missing required field 'title'`);
41
+ } else if (typeof item.title !== 'string') {
42
+ errors.push(`Item ${index}: 'title' must be a string`);
43
+ }
44
+
45
+ // Optional: status must be valid if present
46
+ if (item.status !== undefined && item.status !== null) {
47
+ if (typeof item.status !== 'string') {
48
+ errors.push(`Item ${index}: 'status' must be a string`);
49
+ } else if (!VALID_STATUSES.includes(item.status)) {
50
+ errors.push(`Item ${index}: 'status' must be one of: ${VALID_STATUSES.join(', ')}`);
51
+ }
52
+ }
53
+
54
+ // Optional: parent_id must be integer if present
55
+ if (item.parent_id !== undefined && item.parent_id !== null) {
56
+ if (typeof item.parent_id !== 'number' || !Number.isInteger(item.parent_id)) {
57
+ errors.push(`Item ${index}: 'parent_id' must be an integer`);
58
+ }
59
+ }
60
+
61
+ // Optional: epic_id must be integer if present
62
+ if (item.epic_id !== undefined && item.epic_id !== null) {
63
+ if (typeof item.epic_id !== 'number' || !Number.isInteger(item.epic_id)) {
64
+ errors.push(`Item ${index}: 'epic_id' must be an integer`);
65
+ }
66
+ }
67
+
68
+ return { valid: errors.length === 0, errors };
69
+ }
70
+
71
+ /**
72
+ * Validate work.json data structure and content
73
+ * @param {Object} data - Parsed JSON data
74
+ * @returns {{valid: boolean, errors: string[]}}
75
+ */
76
+ function validateWorkJson(data) {
77
+ const errors = [];
78
+
79
+ if (typeof data !== 'object' || data === null) {
80
+ return { valid: false, errors: ['Data must be an object'] };
81
+ }
82
+
83
+ // work_items must be present and an array
84
+ if (!data.work_items) {
85
+ return { valid: false, errors: ['Missing required field: work_items'] };
86
+ }
87
+
88
+ if (!Array.isArray(data.work_items)) {
89
+ return { valid: false, errors: ['work_items must be an array'] };
90
+ }
91
+
92
+ // Validate each work item
93
+ data.work_items.forEach((item, index) => {
94
+ const result = validateWorkItem(item, index);
95
+ errors.push(...result.errors);
96
+ });
97
+
98
+ // Check for duplicate IDs
99
+ const ids = data.work_items.map(item => item.id).filter(id => id !== undefined);
100
+ const duplicates = ids.filter((id, index) => ids.indexOf(id) !== index);
101
+ if (duplicates.length > 0) {
102
+ errors.push(`Duplicate work item IDs found: ${[...new Set(duplicates)].join(', ')}`);
103
+ }
104
+
105
+ return { valid: errors.length === 0, errors };
106
+ }
107
+
5
108
  /**
6
109
  * Import tables from JSON structure into a database
7
110
  * @param {sqlite3.Database} db - Database connection
@@ -86,6 +189,18 @@ async function importWorkDb() {
86
189
  const jsonContent = fs.readFileSync(jsonPath, 'utf8');
87
190
  const data = JSON.parse(jsonContent);
88
191
 
192
+ // Validate schema before importing
193
+ const validation = validateWorkJson(data);
194
+ if (!validation.valid) {
195
+ console.error('Post-checkout hook warning: work.json schema validation failed');
196
+ validation.errors.slice(0, 5).forEach(err => console.error(` - ${err}`));
197
+ if (validation.errors.length > 5) {
198
+ console.error(` ... and ${validation.errors.length - 5} more errors`);
199
+ }
200
+ console.error(' Database will remain in previous state');
201
+ return jsonPath;
202
+ }
203
+
89
204
  const db = getDb();
90
205
  await importTables(db, data);
91
206
 
@@ -189,5 +304,9 @@ async function importAll() {
189
304
  module.exports = {
190
305
  importWorkDb,
191
306
  importDatabaseDb,
192
- importAll
307
+ importAll,
308
+ validateWorkJson,
309
+ validateWorkItem,
310
+ VALID_TYPES,
311
+ VALID_STATUSES
193
312
  };
@@ -55,23 +55,63 @@ if (!checkBranchRestriction()) {
55
55
  process.exit(1);
56
56
  }
57
57
 
58
- // Check if we're in a real project (not a test directory)
59
- const packageJsonPath = path.join(process.cwd(), 'package.json');
60
- if (!fs.existsSync(packageJsonPath)) {
61
- // Skip tests in test directories
62
- process.exit(0);
63
- }
58
+ // Export database snapshots (runs for all commits where .jettypod exists)
59
+ async function exportSnapshots() {
60
+ const jettypodDir = path.join(process.cwd(), '.jettypod');
61
+ if (!fs.existsSync(jettypodDir)) {
62
+ return; // No JettyPod directory, skip export
63
+ }
64
+
65
+ console.log('\n📸 Exporting database snapshots...\n');
64
66
 
65
- console.log('\n🧪 Running tests before commit...\n');
67
+ try {
68
+ const { exportAll } = require('../db-export');
69
+ const paths = await exportAll();
66
70
 
67
- try {
68
- // Run tests
69
- execSync('npm test', { stdio: 'inherit' });
71
+ // Stage the snapshot files
72
+ execSync(`git add "${paths.work}" "${paths.database}"`, {
73
+ stdio: ['pipe', 'pipe', 'pipe']
74
+ });
70
75
 
71
- console.log('\nTests passed! Proceeding with commit.\n');
72
- process.exit(0);
73
- } catch (err) {
74
- console.log('\n❌ Tests failed! Commit blocked.\n');
75
- console.log('Fix the failing tests or use --no-verify to skip this check.\n');
76
- process.exit(1);
76
+ console.log('✅ Snapshots exported and staged\n');
77
+ } catch (err) {
78
+ console.error('');
79
+ console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
80
+ console.error(' Snapshot export failed! Commit blocked.');
81
+ console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
82
+ console.error('');
83
+ console.error(`Error: ${err.message}`);
84
+ console.error('');
85
+ console.error('This prevents data loss - your work items must be backed up.');
86
+ console.error('Check disk space and directory permissions.');
87
+ console.error('');
88
+ process.exit(1);
89
+ }
77
90
  }
91
+
92
+ // Main async flow
93
+ (async () => {
94
+ // Export snapshots first (before tests, so they're included in commit)
95
+ await exportSnapshots();
96
+
97
+ // Check if we're in a real project (not a test directory)
98
+ const packageJsonPath = path.join(process.cwd(), 'package.json');
99
+ if (!fs.existsSync(packageJsonPath)) {
100
+ // Skip tests in test directories
101
+ process.exit(0);
102
+ }
103
+
104
+ console.log('🧪 Running tests before commit...\n');
105
+
106
+ try {
107
+ // Run tests
108
+ execSync('npm test', { stdio: 'inherit' });
109
+
110
+ console.log('\n✅ Tests passed! Proceeding with commit.\n');
111
+ process.exit(0);
112
+ } catch (err) {
113
+ console.log('\n❌ Tests failed! Commit blocked.\n');
114
+ console.log('Fix the failing tests or use --no-verify to skip this check.\n');
115
+ process.exit(1);
116
+ }
117
+ })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jettypod",
3
- "version": "4.4.82",
3
+ "version": "4.4.84",
4
4
  "description": "AI-powered development workflow manager with TDD, BDD, and automatic test generation",
5
5
  "main": "jettypod.js",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: bug-planning
3
- description: Guide structured bug investigation with symptom capture, hypothesis testing, and root cause identification. Creates bug work item for direct implementation. Use when user reports a bug, mentions unexpected behavior, or says "investigate bug".
3
+ description: Guide structured bug investigation with symptom capture, hypothesis testing, and root cause identification. Invoked by plan-routing when user reports a bug, mentions unexpected behavior, or describes something broken. (project)
4
4
  ---
5
5
 
6
6
  # Bug Planning Skill
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: chore-planning
3
- description: Guide standalone chore planning with automatic type classification and routing to chore-mode. Use when the implementation approach is obvious - refactoring, bug fixes, infrastructure, or simple enhancements where there's no UX decision to make. Key question - "Does this need UX exploration?" No → chore-planning. Yes → feature-planning instead.
3
+ description: Guide standalone chore planning with automatic type classification and routing to chore-mode. Invoked by plan-routing for substantial technical work - refactoring, infrastructure, migrations, or enhancements where the implementation approach is obvious. (project)
4
4
  ---
5
5
 
6
6
  # Chore Planning Skill
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: epic-planning
3
- description: Guide epic planning with feature brainstorming and optional architectural decision prototyping. Use when user asks to plan an epic, mentions planning features for an epic, says "help me plan epic", or when they just created an epic and want to break it down into features.
3
+ description: Guide epic planning with feature brainstorming and optional architectural decision prototyping. Invoked by plan-routing for large multi-feature initiatives that need to be broken down into features. (project)
4
4
  ---
5
5
 
6
6
  # Epic Planning Skill
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: feature-planning
3
- description: Guide feature planning with UX approach exploration and BDD scenario generation. Use when implementing NEW user-facing behavior that requires UX decisions (multiple valid approaches to discuss). NOT for simple/obvious changes like copy tweaks, styling fixes, or adding straightforward functionality. Key question - "Does this need UX exploration?" Yes → feature-planning.
3
+ description: Guide feature planning with UX approach exploration and BDD scenario generation. Invoked by plan-routing when implementing NEW user-facing behavior that requires UX decisions (multiple valid approaches to explore). (project)
4
4
  ---
5
5
 
6
6
  # Feature Planning Skill
@@ -63,49 +63,8 @@ jettypod workflow start feature-planning <feature-id>
63
63
 
64
64
  This creates an execution record for session resume.
65
65
 
66
- ### Step 1B: Route Simple Improvements
67
-
68
- **CRITICAL:** Before proceeding with full feature planning, determine if this is a simple improvement.
69
-
70
- **Ask the user:**
71
-
72
- ```
73
- Is this:
74
- 1. **Simple improvement** - A basic enhancement to existing functionality (e.g., copy change, styling tweak, minor behavior adjustment)
75
- 2. **New functionality** - Adding new capabilities, even if small
76
-
77
- Simple improvements skip the full feature planning workflow and use a lightweight process.
78
- ```
79
-
80
- **⚡ WAIT for user response.**
81
-
82
- **If user says "simple improvement" (or 1):**
83
-
84
- Display:
85
- ```
86
- This is a simple improvement. Routing to the lightweight simple-improvement workflow...
87
- ```
88
-
89
- **Then IMMEDIATELY invoke simple-improvement using the Skill tool:**
90
- ```
91
- Use the Skill tool with skill: "simple-improvement"
92
- ```
93
-
94
- **Feature-planning skill ends here for simple improvements.** The simple-improvement skill takes over.
95
-
96
- ---
97
-
98
- **If user says "new functionality" (or 2):**
99
-
100
- Display:
101
- ```
102
- Got it - this is new functionality. Let's explore the best approach...
103
- ```
104
-
105
66
  **Proceed to Step 2** (or Step 3 if this is a standalone feature with no parent epic).
106
67
 
107
- ---
108
-
109
68
  ### Step 2: Check Epic Architectural Decisions
110
69
 
111
70
  **Skip this step and proceed to Step 3 if `PARENT_EPIC_ID` from Step 1 is null (standalone feature).**
@@ -0,0 +1,188 @@
1
+ ---
2
+ name: plan-routing
3
+ description: Route work requests to the appropriate planning skill. Use when user describes work they want to do - "build X", "fix Y", "add Z", "plan W". Analyzes intent and complexity to route to bug-planning, chore-planning, feature-planning, epic-planning, or simple-improvement. (project)
4
+ ---
5
+
6
+ # Plan Routing Skill
7
+
8
+ Routes user work requests to the correct planning workflow with minimal friction.
9
+
10
+ ## Instructions
11
+
12
+ When this skill is activated, you are helping route a user's work request to the appropriate planning skill. Follow this structured approach:
13
+
14
+ ### Step 1: Extract Intent Signals
15
+
16
+ From the user's request, identify work type and complexity signals:
17
+
18
+ **Work type signals:**
19
+ | Signal Words | Likely Route |
20
+ |--------------|--------------|
21
+ | fix, bug, broken, crash, error, not working, regression | bug-planning |
22
+ | refactor, rename, move, clean up, upgrade, migrate, infrastructure | chore-planning |
23
+ | add, build, create, implement, new feature, capability, workflow | feature-planning |
24
+ | epic, initiative, project, roadmap, multi-feature | epic-planning |
25
+ | tweak, change, update, adjust + small scope | simple-improvement |
26
+
27
+ **Complexity signals:**
28
+ | Signal | Indicates |
29
+ |--------|-----------|
30
+ | "quick", "small", "just", "simple", "tweak" | Lower complexity |
31
+ | "feature", "workflow", "experience", "redesign" | Higher complexity |
32
+ | References specific file/function | Lower complexity |
33
+ | Describes user-facing behavior change | Higher complexity |
34
+
35
+ ### Step 2: Gather Context (Silent - No Questions)
36
+
37
+ Before routing, quickly probe the codebase for context. **Do NOT ask the user for this information.**
38
+
39
+ ```bash
40
+ # Check for related existing code
41
+ jettypod impact "<key-term-from-request>"
42
+ ```
43
+
44
+ ```bash
45
+ # Check for existing work items
46
+ jettypod backlog | grep -i "<key-term>"
47
+ ```
48
+
49
+ **Assess from results:**
50
+ - Existing code? → Likely modification vs creation
51
+ - Related work items? → May already be planned
52
+ - How many files affected? → Complexity indicator
53
+
54
+ ### Step 3: Route with Stated Assumption
55
+
56
+ **DO NOT ASK a routing question.** State your routing decision with reasoning.
57
+
58
+ ## Routing Decision Tree
59
+
60
+ ```
61
+ User request
62
+
63
+
64
+ Contains bug/fix/broken/error signals?
65
+ ├─► Yes → bug-planning
66
+
67
+
68
+ Describes large multi-feature initiative?
69
+ ├─► Yes → epic-planning
70
+
71
+
72
+ Does this need UX exploration?
73
+ (Multiple valid approaches? User-facing behavior with design choices?)
74
+ ├─► Yes → feature-planning
75
+
76
+
77
+ Is this substantial technical work?
78
+ (Refactoring, infrastructure, migrations, multi-file changes)
79
+ ├─► Yes → chore-planning
80
+
81
+
82
+ Is this a simple, obvious change?
83
+ (Copy, styling, config, minor behavior tweak, one clear approach)
84
+ └─► Yes → simple-improvement
85
+ ```
86
+
87
+ ## Route Definitions
88
+
89
+ | Route | When to Use | Progression |
90
+ |-------|-------------|-------------|
91
+ | **bug-planning** | Something is broken/not working as expected | Investigation → fix |
92
+ | **epic-planning** | Large initiative spanning multiple features | Break down → plan features |
93
+ | **feature-planning** | New user-facing behavior with UX decisions to make | UX exploration → BDD → speed → stable → production |
94
+ | **chore-planning** | Substantial technical work, clear implementation | speed → stable → production |
95
+ | **simple-improvement** | Obvious change, no UX decisions, small scope | Direct implementation |
96
+
97
+ ## Routing Examples
98
+
99
+ **→ bug-planning**
100
+ - "The login button doesn't work on mobile"
101
+ - "Getting a crash when I save"
102
+ - "Users are seeing a 500 error"
103
+
104
+ **→ epic-planning**
105
+ - "We need a full authentication system"
106
+ - "Build out the reporting dashboard"
107
+ - "Plan the v2 API migration"
108
+
109
+ **→ feature-planning**
110
+ - "Add drag-and-drop reordering for cards"
111
+ - "Implement user notifications"
112
+ - "Build a search feature"
113
+
114
+ *(Multiple valid UX approaches exist)*
115
+
116
+ **→ chore-planning**
117
+ - "Refactor the auth module to use the new pattern"
118
+ - "Migrate from Moment.js to date-fns"
119
+ - "Add TypeScript to the utils folder"
120
+ - "Set up CI/CD pipeline"
121
+
122
+ *(Technical work, obvious approach, but substantial)*
123
+
124
+ **→ simple-improvement**
125
+ - "Change the button text from 'Submit' to 'Save'"
126
+ - "Make the error message more descriptive"
127
+ - "Add a loading spinner to the save button"
128
+ - "Change the header color to blue"
129
+ - "Add a tooltip to the settings icon"
130
+
131
+ *(One obvious implementation, no UX exploration needed, just do it)*
132
+
133
+ ## Stating Your Routing Decision
134
+
135
+ **Bug:**
136
+ ```
137
+ Sounds like a bug - [X] isn't working as expected. Let me help you investigate.
138
+ ```
139
+ Then invoke bug-planning skill.
140
+
141
+ **Epic:**
142
+ ```
143
+ This is a larger initiative with multiple features. Let's break it down.
144
+ ```
145
+ Then invoke epic-planning skill.
146
+
147
+ **Feature:**
148
+ ```
149
+ This adds new user-facing behavior with some design choices to explore. Let me suggest a few approaches.
150
+ ```
151
+ Then invoke feature-planning skill.
152
+
153
+ **Chore:**
154
+ ```
155
+ This is technical work with a clear implementation path. Let me help you plan it out.
156
+ ```
157
+ Then invoke chore-planning skill.
158
+
159
+ **Simple improvement:**
160
+ ```
161
+ This is a straightforward change. Let me implement it.
162
+ ```
163
+ Then invoke simple-improvement skill.
164
+
165
+ ---
166
+
167
+ **If genuinely ambiguous (rare - should be <20% of cases):**
168
+ ```
169
+ I could approach "[brief description]" as:
170
+ - A **simple improvement** - just implement the obvious solution
171
+ - A **feature** - explore a few UX approaches first
172
+
173
+ Which feels right?
174
+ ```
175
+
176
+ Only ask when you truly cannot decide based on signals and context.
177
+
178
+ ### Step 4: Invoke Target Skill
179
+
180
+ After stating your routing decision, immediately invoke the appropriate skill using the Skill tool:
181
+ - `bug-planning`
182
+ - `chore-planning`
183
+ - `feature-planning`
184
+ - `epic-planning`
185
+ - `simple-improvement`
186
+
187
+ **This skill ends after invocation.** The target skill takes over.
188
+ # Stable mode: Verified ambiguous request handling exists
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: simple-improvement
3
- description: Guide implementation of simple improvements to existing functionality. Use when feature-planning routes here for basic enhancements like copy changes, styling tweaks, or minor behavior adjustments. (project)
3
+ description: Guide implementation of simple improvements to existing functionality. Invoked by plan-routing for straightforward changes like copy changes, styling tweaks, or minor behavior adjustments where the implementation is obvious. (project)
4
4
  ---
5
5
 
6
6
  # Simple Improvement Skill