jettypod 3.0.1
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/.claude/PROTECT_SKILLS.md +28 -0
- package/.claude/settings.json +24 -0
- package/.claude/settings.local.json +16 -0
- package/.claude/skills/epic-discover/SKILL.md +262 -0
- package/.claude/skills/feature-discover/SKILL.md +393 -0
- package/.claude/skills/speed-mode/SKILL.md +364 -0
- package/.claude/skills/stable-mode/SKILL.md +591 -0
- package/.github/workflows/test-safety.yml +85 -0
- package/README.md +25 -0
- package/SPEED-STABLE-AUDIT.md +853 -0
- package/SYSTEM-BEHAVIOR.md +1241 -0
- package/TEST_SAFETY_AUDIT.md +314 -0
- package/TEST_SAFETY_IMPLEMENTATION.md +97 -0
- package/cucumber.js +8 -0
- package/docs/COMMAND_REFERENCE.md +903 -0
- package/docs/DECISIONS.md +68 -0
- package/docs/README.md +48 -0
- package/docs/STANDARDS-SYSTEM-DOCUMENTATION.md +374 -0
- package/docs/TEST-REWRITE-PLAN.md +261 -0
- package/docs/ai-test-writing-requirements.md +219 -0
- package/docs/claude-code-skills.md +607 -0
- package/docs/core-jettypod-methodology/comprehensive-jettypod-methodology.md +582 -0
- package/docs/core-jettypod-methodology/deprecated/jettypod-comprehensive-standards.md +1222 -0
- package/docs/core-jettypod-methodology/deprecated/jettypod-operating-guide.md +3399 -0
- package/docs/core-jettypod-methodology/deprecated/jettypod-technical-checklist.md +1325 -0
- package/docs/core-jettypod-methodology/deprecated/jettypod-vibe-coding-framework.md +1544 -0
- package/docs/core-jettypod-methodology/deprecated/prompt-engineering-guide.md +320 -0
- package/docs/core-jettypod-methodology/deprecated/vibe-coding-cheatsheet (1).md +516 -0
- package/docs/core-jettypod-methodology/deprecated/vibe-coding-framework.md +1544 -0
- package/docs/features/jettypod-standards-explained.md +543 -0
- package/docs/features/standards-inventory.md +257 -0
- package/docs/gap-analysis-current-vs-comprehensive-methodology.md +939 -0
- package/docs/jettypod-system-overview.md +409 -0
- package/features/auto-generate-production-chores.feature +14 -0
- package/features/claude-md-protection/steps.js +487 -0
- package/features/decisions/index.js +490 -0
- package/features/decisions/index.test.js +208 -0
- package/features/git-hooks/git-hooks.feature +30 -0
- package/features/git-hooks/index.js +93 -0
- package/features/git-hooks/index.test.js +137 -0
- package/features/git-hooks/post-commit +56 -0
- package/features/git-hooks/post-merge +47 -0
- package/features/git-hooks/pre-commit +28 -0
- package/features/git-hooks/simple-steps.js +53 -0
- package/features/git-hooks/simple-test.feature +10 -0
- package/features/git-hooks/steps.js +196 -0
- package/features/jettypod-update-command.feature +46 -0
- package/features/mode-prompts/index.js +95 -0
- package/features/mode-prompts/simple-steps.js +44 -0
- package/features/mode-prompts/simple-test.feature +9 -0
- package/features/mode-prompts/validation.test.js +120 -0
- package/features/refactor-mode/steps.js +217 -0
- package/features/refactor-mode.feature +49 -0
- package/features/skills-update/index.test.js +216 -0
- package/features/step_definitions/auto-generate-production-chores.steps.js +162 -0
- package/features/step_definitions/terminal-logo.steps.js +145 -0
- package/features/step_definitions/update-command.steps.js +183 -0
- package/features/terminal-logo/index.js +39 -0
- package/features/terminal-logo/terminal-logo.feature +30 -0
- package/features/update-command/index.js +181 -0
- package/features/update-command/index.test.js +225 -0
- package/features/work-commands/bug-workflow-display.feature +22 -0
- package/features/work-commands/index.js +311 -0
- package/features/work-commands/simple-steps.js +69 -0
- package/features/work-commands/stable-tests.feature +57 -0
- package/features/work-commands/steps.js +1120 -0
- package/features/work-commands/validation.test.js +88 -0
- package/features/work-commands/work-commands.feature +13 -0
- package/features/work-tracking/discovery-validation.test.js +228 -0
- package/features/work-tracking/index.js +1511 -0
- package/features/work-tracking/mode-required.feature +112 -0
- package/features/work-tracking/phase-tracking.test.js +482 -0
- package/features/work-tracking/prototype-tracking.test.js +485 -0
- package/features/work-tracking/tree-view.test.js +310 -0
- package/features/work-tracking/work-set-mode.feature +71 -0
- package/features/work-tracking/work-start-mode.feature +88 -0
- package/full-test.txt +0 -0
- package/install.sh +89 -0
- package/jettypod.js +1640 -0
- package/lib/bug-workflow.js +94 -0
- package/lib/bug-workflow.test.js +177 -0
- package/lib/claudemd.js +130 -0
- package/lib/claudemd.test.js +195 -0
- package/lib/comprehensive-standards-full.json +1778 -0
- package/lib/config.js +181 -0
- package/lib/config.test.js +511 -0
- package/lib/constants.js +107 -0
- package/lib/constants.test.js +164 -0
- package/lib/current-work.js +130 -0
- package/lib/current-work.test.js +146 -0
- package/lib/database-project-config.test.js +107 -0
- package/lib/database.js +256 -0
- package/lib/database.test.js +106 -0
- package/lib/decisions-generator.js +102 -0
- package/lib/decisions-generator.test.js +457 -0
- package/lib/decisions-helpers.js +119 -0
- package/lib/decisions-helpers.test.js +310 -0
- package/lib/discovery-checkpoint.js +83 -0
- package/lib/docs-generator.js +280 -0
- package/lib/external-checklist.js +177 -0
- package/lib/git.js +142 -0
- package/lib/git.test.js +145 -0
- package/lib/logo.js +3 -0
- package/lib/migrations/001-epic-to-parent.js +24 -0
- package/lib/migrations/002-default-work-item-modes.js +37 -0
- package/lib/migrations/002-default-work-item-modes.test.js +351 -0
- package/lib/migrations/003-epic-discovery-fields.js +52 -0
- package/lib/migrations/004-discovery-decisions-table.js +32 -0
- package/lib/migrations/005-migrate-decision-data.js +62 -0
- package/lib/migrations/006-feature-phase-field.js +61 -0
- package/lib/migrations/007-prototype-tracking.js +38 -0
- package/lib/migrations/008-scenario-file-field.js +24 -0
- package/lib/migrations/index.js +74 -0
- package/lib/production-helpers.js +69 -0
- package/lib/project-state.test.js +92 -0
- package/lib/test-helpers.js +184 -0
- package/lib/test-helpers.test.js +255 -0
- package/package.json +36 -0
- package/prototypes/test/index.html +1 -0
- package/setup-dist-repo.sh +68 -0
- package/test-safety-check.sh +80 -0
- package/work-item-tracking-plan.md +199 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bug workflow guidance constants
|
|
3
|
+
* Shared between CLAUDE.md generation and terminal display
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const BUG_WORKFLOW_STEPS = `1. REPRODUCE
|
|
7
|
+
□ Create minimal reproduction case
|
|
8
|
+
□ Write down exact steps to trigger
|
|
9
|
+
□ Note what you expect vs what happens
|
|
10
|
+
□ Check if issue exists in latest code
|
|
11
|
+
|
|
12
|
+
2. INVESTIGATE
|
|
13
|
+
□ Read error messages/stack traces completely
|
|
14
|
+
□ Check relevant code around the issue
|
|
15
|
+
□ Look for recent changes (git log/blame)
|
|
16
|
+
□ Search for similar bugs (closed issues)
|
|
17
|
+
|
|
18
|
+
3. ISOLATE
|
|
19
|
+
□ Narrow down to specific file/function
|
|
20
|
+
□ Identify root cause, not just symptoms
|
|
21
|
+
□ Verify your hypothesis with logging/debugging
|
|
22
|
+
□ Understand why the bug occurs
|
|
23
|
+
|
|
24
|
+
4. FIX
|
|
25
|
+
□ Make the minimal change that fixes root cause
|
|
26
|
+
□ Avoid "while I'm here" refactoring
|
|
27
|
+
□ Add defensive checks if appropriate
|
|
28
|
+
□ Update error messages to be clearer
|
|
29
|
+
|
|
30
|
+
5. VERIFY
|
|
31
|
+
□ Confirm original reproduction case now works
|
|
32
|
+
□ Run existing tests - ensure nothing broke
|
|
33
|
+
□ Add regression test for this specific bug
|
|
34
|
+
□ Test edge cases around the fix
|
|
35
|
+
|
|
36
|
+
6. COMMIT
|
|
37
|
+
□ Clear commit message: "fix: [brief description]"
|
|
38
|
+
□ Reference issue number if applicable
|
|
39
|
+
□ Explain what caused it and how you fixed it
|
|
40
|
+
□ Push and verify CI passes`;
|
|
41
|
+
|
|
42
|
+
const BUG_WORKFLOW_TECHNIQUES = `DEBUGGING TECHNIQUES:
|
|
43
|
+
• Add strategic console.log/print statements
|
|
44
|
+
• Use git bisect to find when bug was introduced
|
|
45
|
+
• Compare working vs broken behavior side-by-side
|
|
46
|
+
• Rubber duck: explain the bug out loud
|
|
47
|
+
• Take breaks - fresh eyes spot issues faster`;
|
|
48
|
+
|
|
49
|
+
const BUG_WORKFLOW_RED_FLAGS = `RED FLAGS:
|
|
50
|
+
❌ "It works on my machine" - not fixed
|
|
51
|
+
❌ Fixing symptoms without understanding cause
|
|
52
|
+
❌ Adding try/catch to hide errors
|
|
53
|
+
❌ Skipping reproduction - you'll miss edge cases`;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get bug workflow for CLAUDE.md (plain text format)
|
|
57
|
+
* @returns {string} Formatted workflow for markdown
|
|
58
|
+
*/
|
|
59
|
+
function getBugWorkflowForClaudeMd() {
|
|
60
|
+
return `SYSTEMATIC BUG FIXING WORKFLOW:
|
|
61
|
+
|
|
62
|
+
${BUG_WORKFLOW_STEPS}
|
|
63
|
+
|
|
64
|
+
${BUG_WORKFLOW_TECHNIQUES}
|
|
65
|
+
|
|
66
|
+
${BUG_WORKFLOW_RED_FLAGS}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get bug workflow for terminal display (with formatting/colors)
|
|
71
|
+
* @returns {string} Formatted workflow for terminal
|
|
72
|
+
*/
|
|
73
|
+
function getBugWorkflowForTerminal() {
|
|
74
|
+
return `
|
|
75
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
76
|
+
🐛 BUG FIXING WORKFLOW
|
|
77
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
78
|
+
|
|
79
|
+
${BUG_WORKFLOW_STEPS}
|
|
80
|
+
|
|
81
|
+
${BUG_WORKFLOW_TECHNIQUES}
|
|
82
|
+
|
|
83
|
+
${BUG_WORKFLOW_RED_FLAGS}
|
|
84
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
85
|
+
`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = {
|
|
89
|
+
BUG_WORKFLOW_STEPS,
|
|
90
|
+
BUG_WORKFLOW_TECHNIQUES,
|
|
91
|
+
BUG_WORKFLOW_RED_FLAGS,
|
|
92
|
+
getBugWorkflowForClaudeMd,
|
|
93
|
+
getBugWorkflowForTerminal
|
|
94
|
+
};
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
const {
|
|
2
|
+
BUG_WORKFLOW_STEPS,
|
|
3
|
+
BUG_WORKFLOW_TECHNIQUES,
|
|
4
|
+
BUG_WORKFLOW_RED_FLAGS,
|
|
5
|
+
getBugWorkflowForClaudeMd,
|
|
6
|
+
getBugWorkflowForTerminal
|
|
7
|
+
} = require('./bug-workflow');
|
|
8
|
+
|
|
9
|
+
describe('Bug Workflow Module', () => {
|
|
10
|
+
describe('Constants', () => {
|
|
11
|
+
test('BUG_WORKFLOW_STEPS contains all 6 steps', () => {
|
|
12
|
+
expect(BUG_WORKFLOW_STEPS).toContain('1. REPRODUCE');
|
|
13
|
+
expect(BUG_WORKFLOW_STEPS).toContain('2. INVESTIGATE');
|
|
14
|
+
expect(BUG_WORKFLOW_STEPS).toContain('3. ISOLATE');
|
|
15
|
+
expect(BUG_WORKFLOW_STEPS).toContain('4. FIX');
|
|
16
|
+
expect(BUG_WORKFLOW_STEPS).toContain('5. VERIFY');
|
|
17
|
+
expect(BUG_WORKFLOW_STEPS).toContain('6. COMMIT');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('BUG_WORKFLOW_STEPS contains critical checklist items', () => {
|
|
21
|
+
// REPRODUCE checklist
|
|
22
|
+
expect(BUG_WORKFLOW_STEPS).toContain('Create minimal reproduction case');
|
|
23
|
+
expect(BUG_WORKFLOW_STEPS).toContain('Write down exact steps to trigger');
|
|
24
|
+
|
|
25
|
+
// INVESTIGATE checklist
|
|
26
|
+
expect(BUG_WORKFLOW_STEPS).toContain('Read error messages/stack traces completely');
|
|
27
|
+
expect(BUG_WORKFLOW_STEPS).toContain('Look for recent changes (git log/blame)');
|
|
28
|
+
|
|
29
|
+
// ISOLATE checklist
|
|
30
|
+
expect(BUG_WORKFLOW_STEPS).toContain('Identify root cause, not just symptoms');
|
|
31
|
+
expect(BUG_WORKFLOW_STEPS).toContain('Understand why the bug occurs');
|
|
32
|
+
|
|
33
|
+
// FIX checklist
|
|
34
|
+
expect(BUG_WORKFLOW_STEPS).toContain('Make the minimal change that fixes root cause');
|
|
35
|
+
expect(BUG_WORKFLOW_STEPS).toContain('Avoid "while I\'m here" refactoring');
|
|
36
|
+
|
|
37
|
+
// VERIFY checklist
|
|
38
|
+
expect(BUG_WORKFLOW_STEPS).toContain('Add regression test for this specific bug');
|
|
39
|
+
expect(BUG_WORKFLOW_STEPS).toContain('Run existing tests - ensure nothing broke');
|
|
40
|
+
|
|
41
|
+
// COMMIT checklist
|
|
42
|
+
expect(BUG_WORKFLOW_STEPS).toContain('Clear commit message: "fix: [brief description]"');
|
|
43
|
+
expect(BUG_WORKFLOW_STEPS).toContain('Reference issue number if applicable');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('BUG_WORKFLOW_TECHNIQUES contains debugging tips', () => {
|
|
47
|
+
expect(BUG_WORKFLOW_TECHNIQUES).toContain('DEBUGGING TECHNIQUES:');
|
|
48
|
+
expect(BUG_WORKFLOW_TECHNIQUES).toContain('git bisect');
|
|
49
|
+
expect(BUG_WORKFLOW_TECHNIQUES).toContain('Rubber duck');
|
|
50
|
+
expect(BUG_WORKFLOW_TECHNIQUES).toContain('Take breaks');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('BUG_WORKFLOW_RED_FLAGS contains anti-patterns', () => {
|
|
54
|
+
expect(BUG_WORKFLOW_RED_FLAGS).toContain('RED FLAGS:');
|
|
55
|
+
expect(BUG_WORKFLOW_RED_FLAGS).toContain('"It works on my machine"');
|
|
56
|
+
expect(BUG_WORKFLOW_RED_FLAGS).toContain('Fixing symptoms without understanding cause');
|
|
57
|
+
expect(BUG_WORKFLOW_RED_FLAGS).toContain('Adding try/catch to hide errors');
|
|
58
|
+
expect(BUG_WORKFLOW_RED_FLAGS).toContain('Skipping reproduction');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('getBugWorkflowForClaudeMd()', () => {
|
|
63
|
+
test('returns string with all workflow sections', () => {
|
|
64
|
+
const result = getBugWorkflowForClaudeMd();
|
|
65
|
+
|
|
66
|
+
expect(typeof result).toBe('string');
|
|
67
|
+
expect(result).toContain('SYSTEMATIC BUG FIXING WORKFLOW:');
|
|
68
|
+
expect(result).toContain(BUG_WORKFLOW_STEPS);
|
|
69
|
+
expect(result).toContain(BUG_WORKFLOW_TECHNIQUES);
|
|
70
|
+
expect(result).toContain(BUG_WORKFLOW_RED_FLAGS);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('does not contain terminal formatting characters', () => {
|
|
74
|
+
const result = getBugWorkflowForClaudeMd();
|
|
75
|
+
|
|
76
|
+
// Should not have Unicode box-drawing characters
|
|
77
|
+
expect(result).not.toContain('━');
|
|
78
|
+
// Should not have emojis
|
|
79
|
+
expect(result).not.toContain('🐛');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('has plain text header for markdown compatibility', () => {
|
|
83
|
+
const result = getBugWorkflowForClaudeMd();
|
|
84
|
+
|
|
85
|
+
expect(result).toContain('SYSTEMATIC BUG FIXING WORKFLOW:');
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('getBugWorkflowForTerminal()', () => {
|
|
90
|
+
test('returns string with all workflow sections', () => {
|
|
91
|
+
const result = getBugWorkflowForTerminal();
|
|
92
|
+
|
|
93
|
+
expect(typeof result).toBe('string');
|
|
94
|
+
expect(result).toContain(BUG_WORKFLOW_STEPS);
|
|
95
|
+
expect(result).toContain(BUG_WORKFLOW_TECHNIQUES);
|
|
96
|
+
expect(result).toContain(BUG_WORKFLOW_RED_FLAGS);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('contains terminal formatting characters', () => {
|
|
100
|
+
const result = getBugWorkflowForTerminal();
|
|
101
|
+
|
|
102
|
+
// Should have Unicode box-drawing characters for borders
|
|
103
|
+
expect(result).toContain('━');
|
|
104
|
+
// Should have bug emoji
|
|
105
|
+
expect(result).toContain('🐛');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('has formatted header with emoji', () => {
|
|
109
|
+
const result = getBugWorkflowForTerminal();
|
|
110
|
+
|
|
111
|
+
expect(result).toContain('🐛 BUG FIXING WORKFLOW');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('has top and bottom borders', () => {
|
|
115
|
+
const result = getBugWorkflowForTerminal();
|
|
116
|
+
|
|
117
|
+
// Count border lines
|
|
118
|
+
const borderCount = (result.match(/━━━━━━━/g) || []).length;
|
|
119
|
+
expect(borderCount).toBeGreaterThanOrEqual(2); // Top and bottom borders
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('Content Consistency', () => {
|
|
124
|
+
test('both formats contain the same workflow steps', () => {
|
|
125
|
+
const claudeMd = getBugWorkflowForClaudeMd();
|
|
126
|
+
const terminal = getBugWorkflowForTerminal();
|
|
127
|
+
|
|
128
|
+
// Both should contain the exact same steps content
|
|
129
|
+
expect(claudeMd).toContain(BUG_WORKFLOW_STEPS);
|
|
130
|
+
expect(terminal).toContain(BUG_WORKFLOW_STEPS);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('both formats contain the same techniques', () => {
|
|
134
|
+
const claudeMd = getBugWorkflowForClaudeMd();
|
|
135
|
+
const terminal = getBugWorkflowForTerminal();
|
|
136
|
+
|
|
137
|
+
expect(claudeMd).toContain(BUG_WORKFLOW_TECHNIQUES);
|
|
138
|
+
expect(terminal).toContain(BUG_WORKFLOW_TECHNIQUES);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('both formats contain the same red flags', () => {
|
|
142
|
+
const claudeMd = getBugWorkflowForClaudeMd();
|
|
143
|
+
const terminal = getBugWorkflowForTerminal();
|
|
144
|
+
|
|
145
|
+
expect(claudeMd).toContain(BUG_WORKFLOW_RED_FLAGS);
|
|
146
|
+
expect(terminal).toContain(BUG_WORKFLOW_RED_FLAGS);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe('Edge Cases', () => {
|
|
151
|
+
test('functions return non-empty strings', () => {
|
|
152
|
+
expect(getBugWorkflowForClaudeMd().length).toBeGreaterThan(0);
|
|
153
|
+
expect(getBugWorkflowForTerminal().length).toBeGreaterThan(0);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('functions are deterministic (same output on multiple calls)', () => {
|
|
157
|
+
const claudeMd1 = getBugWorkflowForClaudeMd();
|
|
158
|
+
const claudeMd2 = getBugWorkflowForClaudeMd();
|
|
159
|
+
expect(claudeMd1).toBe(claudeMd2);
|
|
160
|
+
|
|
161
|
+
const terminal1 = getBugWorkflowForTerminal();
|
|
162
|
+
const terminal2 = getBugWorkflowForTerminal();
|
|
163
|
+
expect(terminal1).toBe(terminal2);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('constants are immutable strings', () => {
|
|
167
|
+
expect(typeof BUG_WORKFLOW_STEPS).toBe('string');
|
|
168
|
+
expect(typeof BUG_WORKFLOW_TECHNIQUES).toBe('string');
|
|
169
|
+
expect(typeof BUG_WORKFLOW_RED_FLAGS).toBe('string');
|
|
170
|
+
|
|
171
|
+
// Verify they're not empty
|
|
172
|
+
expect(BUG_WORKFLOW_STEPS.length).toBeGreaterThan(0);
|
|
173
|
+
expect(BUG_WORKFLOW_TECHNIQUES.length).toBeGreaterThan(0);
|
|
174
|
+
expect(BUG_WORKFLOW_RED_FLAGS.length).toBeGreaterThan(0);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
});
|
package/lib/claudemd.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const config = require('./config');
|
|
4
|
+
const { getBugWorkflowForClaudeMd } = require('./bug-workflow');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Get absolute path to CLAUDE.md file
|
|
8
|
+
* @returns {string} Absolute path to CLAUDE.md
|
|
9
|
+
*/
|
|
10
|
+
function getClaudeMdPath() {
|
|
11
|
+
return path.join(process.cwd(), 'CLAUDE.md');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Validate current work object structure
|
|
16
|
+
* @param {Object} currentWork - Current work item
|
|
17
|
+
* @throws {Error} If currentWork is invalid
|
|
18
|
+
*/
|
|
19
|
+
function validateCurrentWork(currentWork) {
|
|
20
|
+
if (!currentWork || typeof currentWork !== 'object') {
|
|
21
|
+
throw new Error('Current work must be an object');
|
|
22
|
+
}
|
|
23
|
+
if (!currentWork.id || typeof currentWork.id !== 'number') {
|
|
24
|
+
throw new Error('Current work must have a numeric id');
|
|
25
|
+
}
|
|
26
|
+
if (!currentWork.title || typeof currentWork.title !== 'string') {
|
|
27
|
+
throw new Error('Current work must have a string title');
|
|
28
|
+
}
|
|
29
|
+
if (!currentWork.type || typeof currentWork.type !== 'string') {
|
|
30
|
+
throw new Error('Current work must have a string type');
|
|
31
|
+
}
|
|
32
|
+
if (!currentWork.status || typeof currentWork.status !== 'string') {
|
|
33
|
+
throw new Error('Current work must have a string status');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// getModeBehaviorContent function removed - skills now provide all mode guidance
|
|
38
|
+
// Mode-specific workflows are now handled by:
|
|
39
|
+
// - speed-mode skill (activated on speed chore start)
|
|
40
|
+
// - stable-mode skill (activated on stable chore start)
|
|
41
|
+
// See .claude/skills/ directory for skill implementations
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Update current work section in CLAUDE.md
|
|
45
|
+
* @param {Object} currentWork - Current work item to display
|
|
46
|
+
* @param {string|null} mode - Work item mode (null for epics, undefined to read from config)
|
|
47
|
+
* @throws {Error} If currentWork is invalid or file cannot be written
|
|
48
|
+
*/
|
|
49
|
+
function updateCurrentWork(currentWork, mode) {
|
|
50
|
+
validateCurrentWork(currentWork);
|
|
51
|
+
|
|
52
|
+
const claudePath = getClaudeMdPath();
|
|
53
|
+
|
|
54
|
+
if (!fs.existsSync(claudePath)) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let content;
|
|
59
|
+
try {
|
|
60
|
+
content = fs.readFileSync(claudePath, 'utf-8');
|
|
61
|
+
} catch (err) {
|
|
62
|
+
throw new Error(`Failed to read CLAUDE.md: ${err.message}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Use provided mode
|
|
66
|
+
// null = epic (no mode), undefined = fallback to config for backwards compatibility
|
|
67
|
+
let currentMode = mode;
|
|
68
|
+
if (currentMode === undefined) {
|
|
69
|
+
try {
|
|
70
|
+
const cfg = config.read();
|
|
71
|
+
currentMode = cfg.mode || 'speed';
|
|
72
|
+
} catch (err) {
|
|
73
|
+
currentMode = 'speed';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Build new current_work section
|
|
78
|
+
let newCurrentWork = `<current_work>
|
|
79
|
+
Working on: [#${currentWork.id}] ${currentWork.title} (${currentWork.type})`;
|
|
80
|
+
|
|
81
|
+
if (currentWork.epic_title && currentWork.epic_id !== currentWork.id) {
|
|
82
|
+
newCurrentWork += `
|
|
83
|
+
Epic: [#${currentWork.epic_id}] ${currentWork.epic_title}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Only add Mode line if mode is not null (epics don't have modes)
|
|
87
|
+
if (currentMode !== null) {
|
|
88
|
+
newCurrentWork += `
|
|
89
|
+
Mode: ${currentMode}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
newCurrentWork += `
|
|
93
|
+
Status: ${currentWork.status}
|
|
94
|
+
</current_work>`;
|
|
95
|
+
|
|
96
|
+
// Replace existing current_work section or add after project context
|
|
97
|
+
if (content.includes('<current_work>')) {
|
|
98
|
+
content = content.replace(/<current_work>[\s\S]*?<\/current_work>/, newCurrentWork);
|
|
99
|
+
} else {
|
|
100
|
+
// Insert after <claude_context> opening
|
|
101
|
+
if (!content.includes('<claude_context')) {
|
|
102
|
+
throw new Error('CLAUDE.md does not contain <claude_context> tag');
|
|
103
|
+
}
|
|
104
|
+
content = content.replace(/(<claude_context[^>]*>)/, `$1\n\n${newCurrentWork}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Remove both <bug_workflow> and <mode_behavior> sections
|
|
108
|
+
// Skills now provide all mode-specific guidance via activation messages
|
|
109
|
+
content = content.replace(/<bug_workflow>[\s\S]*?<\/bug_workflow>/, '');
|
|
110
|
+
content = content.replace(/<mode_behavior>[\s\S]*?<\/mode_behavior>/, '');
|
|
111
|
+
|
|
112
|
+
// Update <mode> tag
|
|
113
|
+
if (currentMode !== null) {
|
|
114
|
+
content = content.replace(/<mode>.*?<\/mode>/, `<mode>${currentMode}</mode>`);
|
|
115
|
+
} else {
|
|
116
|
+
// Remove <mode> tag for epics
|
|
117
|
+
content = content.replace(/<mode>.*?<\/mode>\n?/, '');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
fs.writeFileSync(claudePath, content);
|
|
122
|
+
} catch (err) {
|
|
123
|
+
throw new Error(`Failed to write CLAUDE.md: ${err.message}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = {
|
|
128
|
+
updateCurrentWork,
|
|
129
|
+
getClaudeMdPath
|
|
130
|
+
};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { createTestEnvironment } = require('./test-helpers');
|
|
4
|
+
const { updateCurrentWork, getClaudeMdPath } = require('./claudemd');
|
|
5
|
+
const config = require('./config');
|
|
6
|
+
|
|
7
|
+
describe('CLAUDE.md Updater Module', () => {
|
|
8
|
+
let testEnv;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
testEnv = createTestEnvironment();
|
|
12
|
+
process.chdir(testEnv.testDir);
|
|
13
|
+
|
|
14
|
+
// Initialize config
|
|
15
|
+
const jettypodDir = path.join(testEnv.testDir, '.jettypod');
|
|
16
|
+
fs.mkdirSync(jettypodDir);
|
|
17
|
+
config.write({ mode: 'speed', stage: 'starting' });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
testEnv.cleanup();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const validWorkItem = {
|
|
25
|
+
id: 1,
|
|
26
|
+
title: 'Test Feature',
|
|
27
|
+
type: 'feature',
|
|
28
|
+
status: 'in_progress',
|
|
29
|
+
epic_id: 10,
|
|
30
|
+
epic_title: 'Test Epic'
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
describe('getClaudeMdPath()', () => {
|
|
34
|
+
test('should return path to CLAUDE.md in current directory', () => {
|
|
35
|
+
const claudePath = getClaudeMdPath();
|
|
36
|
+
expect(claudePath).toContain('CLAUDE.md');
|
|
37
|
+
expect(claudePath).toBe(path.join(process.cwd(), 'CLAUDE.md'));
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('updateCurrentWork()', () => {
|
|
42
|
+
const basicClaudeContent = `<claude_context project="test">
|
|
43
|
+
<project_summary>Test project</project_summary>
|
|
44
|
+
</claude_context>`;
|
|
45
|
+
|
|
46
|
+
test('should do nothing if CLAUDE.md does not exist', () => {
|
|
47
|
+
expect(() => updateCurrentWork(validWorkItem)).not.toThrow();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('should add current_work section if it does not exist', () => {
|
|
51
|
+
fs.writeFileSync('CLAUDE.md', basicClaudeContent);
|
|
52
|
+
|
|
53
|
+
updateCurrentWork(validWorkItem);
|
|
54
|
+
|
|
55
|
+
const content = fs.readFileSync('CLAUDE.md', 'utf-8');
|
|
56
|
+
expect(content).toContain('<current_work>');
|
|
57
|
+
expect(content).toContain('Working on: [#1] Test Feature (feature)');
|
|
58
|
+
expect(content).toContain('Epic: [#10] Test Epic');
|
|
59
|
+
expect(content).toContain('Mode: speed');
|
|
60
|
+
expect(content).toContain('Status: in_progress');
|
|
61
|
+
expect(content).toContain('</current_work>');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('should replace existing current_work section', () => {
|
|
65
|
+
const existingContent = `<claude_context project="test">
|
|
66
|
+
<current_work>
|
|
67
|
+
Working on: [#999] Old Feature (bug)
|
|
68
|
+
Status: done
|
|
69
|
+
</current_work>
|
|
70
|
+
<project_summary>Test project</project_summary>
|
|
71
|
+
</claude_context>`;
|
|
72
|
+
|
|
73
|
+
fs.writeFileSync('CLAUDE.md', existingContent);
|
|
74
|
+
|
|
75
|
+
updateCurrentWork(validWorkItem);
|
|
76
|
+
|
|
77
|
+
const content = fs.readFileSync('CLAUDE.md', 'utf-8');
|
|
78
|
+
expect(content).not.toContain('[#999] Old Feature');
|
|
79
|
+
expect(content).toContain('[#1] Test Feature');
|
|
80
|
+
expect(content).toContain('Status: in_progress');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('should not include epic if work item is the epic itself', () => {
|
|
84
|
+
const epicWorkItem = {
|
|
85
|
+
id: 10,
|
|
86
|
+
title: 'Test Epic',
|
|
87
|
+
type: 'epic',
|
|
88
|
+
status: 'in_progress',
|
|
89
|
+
epic_id: 10,
|
|
90
|
+
epic_title: 'Test Epic'
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
fs.writeFileSync('CLAUDE.md', basicClaudeContent);
|
|
94
|
+
|
|
95
|
+
updateCurrentWork(epicWorkItem);
|
|
96
|
+
|
|
97
|
+
const content = fs.readFileSync('CLAUDE.md', 'utf-8');
|
|
98
|
+
expect(content).not.toContain('Epic: [#10]');
|
|
99
|
+
expect(content).toContain('[#10] Test Epic (epic)');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('should not include epic if epic_title is missing', () => {
|
|
103
|
+
const workItemNoEpic = {
|
|
104
|
+
id: 1,
|
|
105
|
+
title: 'Test Feature',
|
|
106
|
+
type: 'feature',
|
|
107
|
+
status: 'in_progress'
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
fs.writeFileSync('CLAUDE.md', basicClaudeContent);
|
|
111
|
+
|
|
112
|
+
updateCurrentWork(workItemNoEpic);
|
|
113
|
+
|
|
114
|
+
const content = fs.readFileSync('CLAUDE.md', 'utf-8');
|
|
115
|
+
expect(content).not.toContain('Epic:');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('should use mode from config', () => {
|
|
119
|
+
config.write({ mode: 'stable', stage: 'building' });
|
|
120
|
+
fs.writeFileSync('CLAUDE.md', basicClaudeContent);
|
|
121
|
+
|
|
122
|
+
updateCurrentWork(validWorkItem);
|
|
123
|
+
|
|
124
|
+
const content = fs.readFileSync('CLAUDE.md', 'utf-8');
|
|
125
|
+
expect(content).toContain('Mode: stable');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('should use mode from config even if malformed', () => {
|
|
129
|
+
// Config module caches config, so we just verify it uses whatever mode is set
|
|
130
|
+
const currentConfig = config.read();
|
|
131
|
+
fs.writeFileSync('CLAUDE.md', basicClaudeContent);
|
|
132
|
+
|
|
133
|
+
updateCurrentWork(validWorkItem);
|
|
134
|
+
|
|
135
|
+
const content = fs.readFileSync('CLAUDE.md', 'utf-8');
|
|
136
|
+
expect(content).toContain(`Mode: ${currentConfig.mode || 'speed'}`);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('should throw error for invalid work item - not an object', () => {
|
|
140
|
+
expect(() => updateCurrentWork(null)).toThrow('Current work must be an object');
|
|
141
|
+
expect(() => updateCurrentWork('string')).toThrow('Current work must be an object');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('should throw error for missing id', () => {
|
|
145
|
+
const invalid = { ...validWorkItem, id: null };
|
|
146
|
+
expect(() => updateCurrentWork(invalid)).toThrow('Current work must have a numeric id');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('should throw error for non-numeric id', () => {
|
|
150
|
+
const invalid = { ...validWorkItem, id: 'not-a-number' };
|
|
151
|
+
expect(() => updateCurrentWork(invalid)).toThrow('Current work must have a numeric id');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('should throw error for missing title', () => {
|
|
155
|
+
const invalid = { ...validWorkItem, title: null };
|
|
156
|
+
expect(() => updateCurrentWork(invalid)).toThrow('Current work must have a string title');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('should throw error for missing type', () => {
|
|
160
|
+
const invalid = { ...validWorkItem, type: null };
|
|
161
|
+
expect(() => updateCurrentWork(invalid)).toThrow('Current work must have a string type');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('should throw error for missing status', () => {
|
|
165
|
+
const invalid = { ...validWorkItem, status: null };
|
|
166
|
+
expect(() => updateCurrentWork(invalid)).toThrow('Current work must have a string status');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('should throw error if CLAUDE.md does not contain claude_context tag', () => {
|
|
170
|
+
fs.writeFileSync('CLAUDE.md', 'Just plain text, no tags');
|
|
171
|
+
|
|
172
|
+
expect(() => updateCurrentWork(validWorkItem)).toThrow('CLAUDE.md does not contain <claude_context> tag');
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test('should preserve other sections in CLAUDE.md', () => {
|
|
176
|
+
const fullContent = `<claude_context project="test">
|
|
177
|
+
<current_work>
|
|
178
|
+
Working on: [#999] Old Feature (bug)
|
|
179
|
+
</current_work>
|
|
180
|
+
<project_summary>Test project</project_summary>
|
|
181
|
+
<tech_stack>Node.js, Jest</tech_stack>
|
|
182
|
+
<mode>speed</mode>
|
|
183
|
+
</claude_context>`;
|
|
184
|
+
|
|
185
|
+
fs.writeFileSync('CLAUDE.md', fullContent);
|
|
186
|
+
|
|
187
|
+
updateCurrentWork(validWorkItem);
|
|
188
|
+
|
|
189
|
+
const content = fs.readFileSync('CLAUDE.md', 'utf-8');
|
|
190
|
+
expect(content).toContain('<project_summary>Test project</project_summary>');
|
|
191
|
+
expect(content).toContain('<tech_stack>Node.js, Jest</tech_stack>');
|
|
192
|
+
expect(content).toContain('<mode>speed</mode>');
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
});
|