jettypod 4.4.0 → 4.4.2

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/docs/DECISIONS.md CHANGED
@@ -6,61 +6,13 @@ This document records key decisions made during project discovery and epic plann
6
6
 
7
7
  ## Epic-Level Decisions
8
8
 
9
- ### Epic #237: Real-time Collaboration
9
+ ### Epic #2: Standalone Chore Workflow System
10
10
 
11
- **Architecture:** WebSockets with Socket.io
11
+ **Architecture:** Two-tier skill structure (chore-planning → chore-mode)
12
12
 
13
- *Rationale:* Bidirectional communication needed, Socket.io provides automatic fallbacks and reconnection
13
+ *Rationale:* Two-tier skill structure chosen to mirror existing feature workflow patterns. Separates planning concerns (scope, criteria, impact) from execution concerns (guidance, verification). Type-dependent test analysis balances thoroughness with pragmatism.
14
14
 
15
- *Date:* 10/29/2025
16
-
17
- **State Management:** Redux Toolkit
18
-
19
- *Rationale:* Need centralized state for connection status across multiple components
20
-
21
- *Date:* 10/29/2025
22
-
23
- **Error Handling:** Exponential backoff with jitter for reconnection
24
-
25
- *Rationale:* Prevents thundering herd when server recovers, jitter distributes reconnection attempts evenly
26
-
27
- *Date:* 10/31/2025
28
-
29
- **Testing Strategy:** Integration tests with mock Socket.io server
30
-
31
- *Rationale:* Allows testing reconnection logic and state synchronization without real server dependency
32
-
33
- *Date:* 10/31/2025
34
-
35
- ---
36
-
37
- ### Epic #238: Real-time System
38
-
39
- **Architecture:** WebSockets
40
-
41
- *Rationale:* Real-time bidirectional communication required
42
-
43
- *Date:* 10/29/2025
44
-
45
- ---
46
-
47
- ### Epic #242: Epic Needs Discovery
48
-
49
- **Architecture:** GraphQL API
50
-
51
- *Rationale:* Flexible querying and strong typing needed for complex data requirements
52
-
53
- *Date:* 10/29/2025
54
-
55
- ---
56
-
57
- ### Epic #1840: Multiple AI Coding Assistant Instances
58
-
59
- **Concurrent Merge Architecture:** Database-backed merge queue with rebase-first strategy and smart conflict resolution
60
-
61
- *Rationale:* Multiple Claude Code instances need coordinated access to main branch. Using database as coordination point provides simple, reliable serialization without complex distributed locking. Rebase-first strategy ensures linear history and makes conflicts easier to reason about. Smart conflict resolution (auto-resolve trivial, LLM-assist complex) enables autonomous operation while escalating truly ambiguous cases. Graceful degradation ensures system never enters unrecoverable state.
62
-
63
- *Date:* 11/14/2025
15
+ *Date:* 11/25/2025
64
16
 
65
17
  ---
66
18
 
package/jettypod.js CHANGED
@@ -222,6 +222,7 @@ jettypod work create feature "<title>" --parent=<id>
222
222
  jettypod work start <id>
223
223
  jettypod work status <id> cancelled
224
224
  jettypod backlog
225
+ jettypod impact <file> # Show tests/features affected by changing a file
225
226
 
226
227
  ## Advanced Commands
227
228
  For mode management, decisions, project state: docs/COMMAND_REFERENCE.md
@@ -2062,6 +2063,94 @@ Quick commands:
2062
2063
  break;
2063
2064
  }
2064
2065
 
2066
+ case 'impact': {
2067
+ // Test impact analysis
2068
+ const targetFile = args[0];
2069
+
2070
+ if (!targetFile) {
2071
+ console.log('Usage: jettypod impact <file-path>');
2072
+ console.log('');
2073
+ console.log('Analyze what tests and features would be affected by changing a file.');
2074
+ console.log('');
2075
+ console.log('Examples:');
2076
+ console.log(' jettypod impact lib/database.js');
2077
+ console.log(' jettypod impact features/work-tracking/index.js');
2078
+ process.exit(1);
2079
+ }
2080
+
2081
+ try {
2082
+ const { DependencyGraph } = require('./lib/test-impact-analyzer');
2083
+ const graph = new DependencyGraph();
2084
+
2085
+ console.log('šŸ” Building dependency graph...');
2086
+ await graph.build(process.cwd());
2087
+
2088
+ const impact = graph.getImpact(targetFile);
2089
+
2090
+ if (impact.error) {
2091
+ console.error(`\nāŒ ${impact.error}`);
2092
+ console.log('');
2093
+ console.log('Make sure the file path is relative to the project root.');
2094
+ process.exit(1);
2095
+ }
2096
+
2097
+ // Pretty-print impact analysis
2098
+ const levelColors = {
2099
+ LOW: '\x1b[32m', // green
2100
+ MEDIUM: '\x1b[33m', // yellow
2101
+ HIGH: '\x1b[31m' // red
2102
+ };
2103
+ const reset = '\x1b[0m';
2104
+ const bold = '\x1b[1m';
2105
+ const dim = '\x1b[2m';
2106
+
2107
+ console.log('');
2108
+ console.log('═'.repeat(60));
2109
+ console.log(`${bold}IMPACT ANALYSIS: ${targetFile}${reset}`);
2110
+ console.log('═'.repeat(60));
2111
+ console.log('');
2112
+ console.log(`Impact Score: ${bold}${impact.impactScore}/100${reset} ${levelColors[impact.impactLevel]}(${impact.impactLevel})${reset}`);
2113
+ console.log(`Total Affected Files: ${impact.totalAffected}`);
2114
+
2115
+ if (impact.tests.length > 0) {
2116
+ console.log('');
2117
+ console.log(`${bold}AFFECTED TESTS (${impact.tests.length}):${reset}`);
2118
+ for (const t of impact.tests) {
2119
+ console.log(` ${dim}[dist=${t.distance}]${reset} ${t.file}`);
2120
+ }
2121
+ }
2122
+
2123
+ if (impact.features.length > 0) {
2124
+ console.log('');
2125
+ console.log(`${bold}AFFECTED BDD FILES (${impact.features.length}):${reset}`);
2126
+ for (const f of impact.features) {
2127
+ console.log(` ${dim}[dist=${f.distance}]${reset} ${f.file}`);
2128
+ }
2129
+ }
2130
+
2131
+ if (impact.code.length > 0) {
2132
+ console.log('');
2133
+ console.log(`${bold}AFFECTED CODE (${impact.code.length}):${reset}`);
2134
+ const displayCode = impact.code.slice(0, 10);
2135
+ for (const c of displayCode) {
2136
+ console.log(` ${dim}[dist=${c.distance}]${reset} ${c.file}`);
2137
+ }
2138
+ if (impact.code.length > 10) {
2139
+ console.log(` ${dim}... and ${impact.code.length - 10} more${reset}`);
2140
+ }
2141
+ }
2142
+
2143
+ console.log('');
2144
+ console.log('═'.repeat(60));
2145
+ console.log('');
2146
+
2147
+ } catch (err) {
2148
+ console.error(`āŒ Error: ${err.message}`);
2149
+ process.exit(1);
2150
+ }
2151
+ break;
2152
+ }
2153
+
2065
2154
  default:
2066
2155
  // Smart mode: auto-initialize if needed, otherwise show guidance
2067
2156
  if (!fs.existsSync('.jettypod')) {
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Chore Classifier
3
+ *
4
+ * Classifies chore type from title/description using keyword matching.
5
+ * Maps to the 4 chore types defined in chore-taxonomy.js
6
+ */
7
+
8
+ const { CHORE_TYPES, isValidChoreType } = require('./chore-taxonomy');
9
+
10
+ // Keywords that indicate each chore type
11
+ const TYPE_KEYWORDS = {
12
+ [CHORE_TYPES.REFACTOR]: [
13
+ 'refactor',
14
+ 'restructure',
15
+ 'reorganize',
16
+ 'extract',
17
+ 'inline',
18
+ 'rename',
19
+ 'move',
20
+ 'split',
21
+ 'consolidate',
22
+ 'simplify',
23
+ 'decouple',
24
+ 'modularize'
25
+ ],
26
+ [CHORE_TYPES.DEPENDENCY]: [
27
+ 'update',
28
+ 'upgrade',
29
+ 'bump',
30
+ 'migrate',
31
+ 'dependency',
32
+ 'dependencies',
33
+ 'package',
34
+ 'library',
35
+ 'framework',
36
+ 'version',
37
+ 'security patch',
38
+ 'vulnerability',
39
+ 'npm',
40
+ 'yarn',
41
+ 'lodash',
42
+ 'react',
43
+ 'node'
44
+ ],
45
+ [CHORE_TYPES.CLEANUP]: [
46
+ 'cleanup',
47
+ 'clean up',
48
+ 'remove',
49
+ 'delete',
50
+ 'deprecate',
51
+ 'dead code',
52
+ 'unused',
53
+ 'legacy',
54
+ 'obsolete',
55
+ 'prune',
56
+ 'trim'
57
+ ],
58
+ [CHORE_TYPES.TOOLING]: [
59
+ 'tooling',
60
+ 'ci',
61
+ 'cd',
62
+ 'pipeline',
63
+ 'build',
64
+ 'lint',
65
+ 'eslint',
66
+ 'prettier',
67
+ 'config',
68
+ 'configuration',
69
+ 'script',
70
+ 'automation',
71
+ 'github action',
72
+ 'workflow',
73
+ 'jest',
74
+ 'test setup'
75
+ ]
76
+ };
77
+
78
+ /**
79
+ * Normalize and validate input text
80
+ * @param {string} title - The chore title
81
+ * @param {string} [description] - Optional description
82
+ * @returns {string} - Normalized text for classification
83
+ * @throws {Error} - If title is invalid
84
+ */
85
+ function normalizeInput(title, description = '') {
86
+ // Validate title
87
+ if (title === null || title === undefined) {
88
+ throw new Error('Chore title is required for classification');
89
+ }
90
+ if (typeof title !== 'string') {
91
+ throw new Error(`Chore title must be a string, got ${typeof title}`);
92
+ }
93
+
94
+ const trimmedTitle = title.trim();
95
+ if (trimmedTitle.length === 0) {
96
+ throw new Error('Chore title cannot be empty or whitespace only');
97
+ }
98
+
99
+ // Normalize description (coerce to string if not)
100
+ const normalizedDesc = description != null ? String(description).trim() : '';
101
+
102
+ return `${trimmedTitle} ${normalizedDesc}`.toLowerCase();
103
+ }
104
+
105
+ /**
106
+ * Escape special regex characters in a string
107
+ * @param {string} str - String to escape
108
+ * @returns {string} - Escaped string safe for regex
109
+ */
110
+ function escapeRegex(str) {
111
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
112
+ }
113
+
114
+ /**
115
+ * Calculate scores for all chore types
116
+ * @param {string} text - Normalized text to analyze
117
+ * @returns {Object} - Scores for each type
118
+ */
119
+ function calculateScores(text) {
120
+ const scores = {};
121
+ for (const [type, keywords] of Object.entries(TYPE_KEYWORDS)) {
122
+ scores[type] = 0;
123
+ for (const keyword of keywords) {
124
+ if (text.includes(keyword.toLowerCase())) {
125
+ // Exact word match scores higher (escape special chars for safety)
126
+ const escapedKeyword = escapeRegex(keyword);
127
+ const wordBoundaryRegex = new RegExp(`\\b${escapedKeyword}\\b`, 'i');
128
+ if (wordBoundaryRegex.test(text)) {
129
+ scores[type] += 2;
130
+ } else {
131
+ scores[type] += 1;
132
+ }
133
+ }
134
+ }
135
+ }
136
+ return scores;
137
+ }
138
+
139
+ /**
140
+ * Find the winning type from scores
141
+ * @param {Object} scores - Scores for each type
142
+ * @returns {Object} - { type: string, score: number }
143
+ */
144
+ function findWinningType(scores) {
145
+ let maxScore = 0;
146
+ let classifiedType = CHORE_TYPES.REFACTOR; // Default fallback
147
+
148
+ for (const [type, score] of Object.entries(scores)) {
149
+ if (score > maxScore) {
150
+ maxScore = score;
151
+ classifiedType = type;
152
+ }
153
+ }
154
+
155
+ return { type: classifiedType, score: maxScore };
156
+ }
157
+
158
+ /**
159
+ * Classify a chore based on its title (and optionally description)
160
+ * @param {string} title - The chore title
161
+ * @param {string} [description] - Optional description for additional context
162
+ * @returns {string} - One of the 4 chore types
163
+ * @throws {Error} - If title is invalid
164
+ */
165
+ function classifyChoreType(title, description = '') {
166
+ const text = normalizeInput(title, description);
167
+ const scores = calculateScores(text);
168
+ const { type } = findWinningType(scores);
169
+ return type;
170
+ }
171
+
172
+ /**
173
+ * Determine confidence level based on score
174
+ * @param {number} score - The classification score
175
+ * @returns {string} - 'high', 'medium', or 'low'
176
+ */
177
+ function getConfidenceLevel(score) {
178
+ if (score >= 4) return 'high';
179
+ if (score >= 2) return 'medium';
180
+ return 'low';
181
+ }
182
+
183
+ /**
184
+ * Get confidence level for a classification
185
+ * @param {string} title - The chore title
186
+ * @param {string} [description] - Optional description
187
+ * @returns {object} - { type: string, confidence: 'high'|'medium'|'low', score: number }
188
+ * @throws {Error} - If title is invalid
189
+ */
190
+ function classifyWithConfidence(title, description = '') {
191
+ const text = normalizeInput(title, description);
192
+ const scores = calculateScores(text);
193
+ const { type, score } = findWinningType(scores);
194
+ const confidence = getConfidenceLevel(score);
195
+
196
+ return { type, confidence, score };
197
+ }
198
+
199
+ /**
200
+ * Get detailed classification with all type scores
201
+ * Useful for debugging or when needing transparency in classification
202
+ * @param {string} title - The chore title
203
+ * @param {string} [description] - Optional description
204
+ * @returns {object} - { type, confidence, score, allScores }
205
+ * @throws {Error} - If title is invalid
206
+ */
207
+ function classifyWithDetails(title, description = '') {
208
+ const text = normalizeInput(title, description);
209
+ const scores = calculateScores(text);
210
+ const { type, score } = findWinningType(scores);
211
+ const confidence = getConfidenceLevel(score);
212
+
213
+ return {
214
+ type,
215
+ confidence,
216
+ score,
217
+ allScores: scores
218
+ };
219
+ }
220
+
221
+ module.exports = {
222
+ classifyChoreType,
223
+ classifyWithConfidence,
224
+ classifyWithDetails,
225
+ TYPE_KEYWORDS,
226
+ // Expose helpers for testing
227
+ normalizeInput,
228
+ escapeRegex,
229
+ calculateScores,
230
+ findWinningType,
231
+ getConfidenceLevel
232
+ };
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Chore Type Taxonomy
3
+ * Defines the 4 chore types and their workflow guidance for the chore-planning and chore-mode skills.
4
+ */
5
+
6
+ /**
7
+ * Chore types
8
+ * @constant {Object}
9
+ */
10
+ const CHORE_TYPES = Object.freeze({
11
+ REFACTOR: 'refactor',
12
+ DEPENDENCY: 'dependency',
13
+ CLEANUP: 'cleanup',
14
+ TOOLING: 'tooling'
15
+ });
16
+
17
+ /**
18
+ * Valid chore type values as array
19
+ * @constant {Array<string>}
20
+ */
21
+ const VALID_CHORE_TYPES = Object.freeze(Object.values(CHORE_TYPES));
22
+
23
+ /**
24
+ * Guidance for each chore type
25
+ * @constant {Object}
26
+ */
27
+ const CHORE_TYPE_GUIDANCE = Object.freeze({
28
+ [CHORE_TYPES.REFACTOR]: {
29
+ scope: [
30
+ 'Define clear boundaries - what code is being restructured',
31
+ 'Identify all callers/dependents of code being changed',
32
+ 'Ensure behavior remains unchanged (refactor, not rewrite)',
33
+ 'Consider breaking into smaller refactors if scope is large'
34
+ ],
35
+ verification: [
36
+ 'All existing tests pass without modification',
37
+ 'No new functionality added (that requires new tests)',
38
+ 'Code review confirms behavior preservation',
39
+ 'Performance is not degraded'
40
+ ],
41
+ testHandling: {
42
+ required: true,
43
+ approach: 'Run all tests for affected modules before and after. Update test file paths/imports if moved. Do NOT change test assertions - if tests fail, the refactor broke behavior.'
44
+ }
45
+ },
46
+ [CHORE_TYPES.DEPENDENCY]: {
47
+ scope: [
48
+ 'Identify which packages are being updated',
49
+ 'Check changelogs for breaking changes',
50
+ 'Note any deprecated APIs that need migration',
51
+ 'Consider update strategy: one at a time vs batch'
52
+ ],
53
+ verification: [
54
+ 'All tests pass after update',
55
+ 'Application builds successfully',
56
+ 'No new deprecation warnings (or documented)',
57
+ 'Security vulnerabilities addressed (if security update)'
58
+ ],
59
+ testHandling: {
60
+ required: false,
61
+ approach: 'Run full test suite to catch regressions. No new tests needed unless migrating to new API patterns. Document any test changes needed due to library API changes.'
62
+ }
63
+ },
64
+ [CHORE_TYPES.CLEANUP]: {
65
+ scope: [
66
+ 'Define what is being cleaned (dead code, unused files, etc.)',
67
+ 'Verify code is actually unused (grep for references)',
68
+ 'Set clear boundaries to avoid scope creep',
69
+ 'Consider impact on git history/blame'
70
+ ],
71
+ verification: [
72
+ 'All tests still pass',
73
+ 'No broken imports or references',
74
+ 'Application runs correctly',
75
+ 'Removed code was actually unused'
76
+ ],
77
+ testHandling: {
78
+ required: false,
79
+ approach: 'Run existing tests to ensure nothing breaks. Remove tests only if they test deleted code. No new tests needed for cleanup work.'
80
+ }
81
+ },
82
+ [CHORE_TYPES.TOOLING]: {
83
+ scope: [
84
+ 'Define what tooling is being changed (CI, build, dev environment)',
85
+ 'Document current behavior before changes',
86
+ 'Consider impact on team workflows',
87
+ 'Plan rollback strategy if changes cause issues'
88
+ ],
89
+ verification: [
90
+ 'CI pipeline passes',
91
+ 'Build completes successfully',
92
+ 'Dev environment works for all team members',
93
+ 'No regression in build times or developer experience'
94
+ ],
95
+ testHandling: {
96
+ required: false,
97
+ approach: 'Verify tooling changes work via manual testing or CI runs. Add integration tests only if tooling is complex. Focus on verification over unit testing for infrastructure.'
98
+ }
99
+ }
100
+ });
101
+
102
+ /**
103
+ * Normalize a chore type string (lowercase, trimmed)
104
+ * @param {string} type - Type to normalize
105
+ * @returns {string|null} Normalized type or null if input is invalid
106
+ */
107
+ function normalizeChoreType(type) {
108
+ if (type === null || type === undefined) {
109
+ return null;
110
+ }
111
+ if (typeof type !== 'string') {
112
+ return null;
113
+ }
114
+ return type.toLowerCase().trim();
115
+ }
116
+
117
+ /**
118
+ * Check if a chore type is valid (case-insensitive)
119
+ * @param {string} type - Type to validate
120
+ * @returns {boolean} True if type is valid
121
+ */
122
+ function isValidChoreType(type) {
123
+ const normalized = normalizeChoreType(type);
124
+ if (normalized === null) {
125
+ return false;
126
+ }
127
+ return VALID_CHORE_TYPES.includes(normalized);
128
+ }
129
+
130
+ /**
131
+ * Get guidance for a chore type
132
+ * @param {string} type - Chore type (case-insensitive)
133
+ * @returns {Object} Guidance object with scope, verification, and testHandling
134
+ * @throws {Error} If type is null, undefined, or invalid
135
+ */
136
+ function getGuidance(type) {
137
+ if (type === null || type === undefined) {
138
+ throw new Error(
139
+ 'Chore type is required. Valid types: refactor, dependency, cleanup, tooling'
140
+ );
141
+ }
142
+
143
+ const normalized = normalizeChoreType(type);
144
+
145
+ if (!normalized || !VALID_CHORE_TYPES.includes(normalized)) {
146
+ throw new Error(
147
+ `Invalid chore type: "${type}". Valid types: refactor, dependency, cleanup, tooling`
148
+ );
149
+ }
150
+
151
+ return CHORE_TYPE_GUIDANCE[normalized];
152
+ }
153
+
154
+ /**
155
+ * Get all chore types
156
+ * @returns {Array<string>} Array of valid chore types
157
+ */
158
+ function getChoreTypes() {
159
+ return [...VALID_CHORE_TYPES];
160
+ }
161
+
162
+ module.exports = {
163
+ CHORE_TYPES,
164
+ VALID_CHORE_TYPES,
165
+ CHORE_TYPE_GUIDANCE,
166
+ normalizeChoreType,
167
+ isValidChoreType,
168
+ getGuidance,
169
+ getChoreTypes,
170
+ // Alias for step definitions
171
+ types: CHORE_TYPES
172
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jettypod",
3
- "version": "4.4.0",
3
+ "version": "4.4.2",
4
4
  "description": "AI-powered development workflow manager with TDD, BDD, and automatic test generation",
5
5
  "main": "jettypod.js",
6
6
  "bin": {