fix: prevent CLAUDE.md overwrite by using imports (#949)
* fix: prevent CLAUDE.md overwrite by using imports - Copy Task Master instructions to .taskmaster/CLAUDE.md - Add import section to user's CLAUDE.md instead of overwriting - Preserve existing user content - Clean removal of Task Master content on uninstall Closes #929 * chore: add changeset for Claude import fix
This commit is contained in:
12
.changeset/claude-import-fix-new.md
Normal file
12
.changeset/claude-import-fix-new.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
"task-master-ai": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Prevent CLAUDE.md overwrite by using Claude Code's import feature
|
||||||
|
|
||||||
|
- Task Master now creates its instructions in `.taskmaster/CLAUDE.md` instead of overwriting the user's `CLAUDE.md`
|
||||||
|
- Adds an import section to the user's CLAUDE.md that references the Task Master instructions
|
||||||
|
- Preserves existing user content in CLAUDE.md files
|
||||||
|
- Provides clean uninstall that only removes Task Master's additions
|
||||||
|
|
||||||
|
**Breaking Change**: Task Master instructions for Claude Code are now stored in `.taskmaster/CLAUDE.md` and imported into the main CLAUDE.md file. Users who previously had Task Master content directly in their CLAUDE.md will need to run `task-master rules remove claude` followed by `task-master rules add claude` to migrate to the new structure.
|
||||||
@@ -59,6 +59,63 @@ function onAddRulesProfile(targetDir, assetsDir) {
|
|||||||
`[Claude] An error occurred during directory copy: ${err.message}`
|
`[Claude] An error occurred during directory copy: ${err.message}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle CLAUDE.md import for non-destructive integration
|
||||||
|
const sourceFile = path.join(assetsDir, 'AGENTS.md');
|
||||||
|
const userClaudeFile = path.join(targetDir, 'CLAUDE.md');
|
||||||
|
const taskMasterClaudeFile = path.join(targetDir, '.taskmaster', 'CLAUDE.md');
|
||||||
|
const importLine = '@./.taskmaster/CLAUDE.md';
|
||||||
|
const importSection = `\n## Task Master AI Instructions\n**Import Task Master's development workflow commands and guidelines, treat as if import is in the main CLAUDE.md file.**\n${importLine}`;
|
||||||
|
|
||||||
|
if (fs.existsSync(sourceFile)) {
|
||||||
|
try {
|
||||||
|
// Ensure .taskmaster directory exists
|
||||||
|
const taskMasterDir = path.join(targetDir, '.taskmaster');
|
||||||
|
if (!fs.existsSync(taskMasterDir)) {
|
||||||
|
fs.mkdirSync(taskMasterDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy Task Master instructions to .taskmaster/CLAUDE.md
|
||||||
|
fs.copyFileSync(sourceFile, taskMasterClaudeFile);
|
||||||
|
log(
|
||||||
|
'debug',
|
||||||
|
`[Claude] Created Task Master instructions at ${taskMasterClaudeFile}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handle user's CLAUDE.md
|
||||||
|
if (fs.existsSync(userClaudeFile)) {
|
||||||
|
// Check if import already exists
|
||||||
|
const content = fs.readFileSync(userClaudeFile, 'utf8');
|
||||||
|
if (!content.includes(importLine)) {
|
||||||
|
// Append import section at the end
|
||||||
|
const updatedContent = content.trim() + '\n' + importSection + '\n';
|
||||||
|
fs.writeFileSync(userClaudeFile, updatedContent);
|
||||||
|
log(
|
||||||
|
'info',
|
||||||
|
`[Claude] Added Task Master import to existing ${userClaudeFile}`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
log(
|
||||||
|
'info',
|
||||||
|
`[Claude] Task Master import already present in ${userClaudeFile}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Create minimal CLAUDE.md with the import section
|
||||||
|
const minimalContent = `# Claude Code Instructions\n${importSection}\n`;
|
||||||
|
fs.writeFileSync(userClaudeFile, minimalContent);
|
||||||
|
log(
|
||||||
|
'info',
|
||||||
|
`[Claude] Created ${userClaudeFile} with Task Master import`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
log(
|
||||||
|
'error',
|
||||||
|
`[Claude] Failed to set up Claude instructions: ${err.message}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onRemoveRulesProfile(targetDir) {
|
function onRemoveRulesProfile(targetDir) {
|
||||||
@@ -67,6 +124,77 @@ function onRemoveRulesProfile(targetDir) {
|
|||||||
if (removeDirectoryRecursive(claudeDir)) {
|
if (removeDirectoryRecursive(claudeDir)) {
|
||||||
log('debug', `[Claude] Removed .claude directory from ${claudeDir}`);
|
log('debug', `[Claude] Removed .claude directory from ${claudeDir}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean up CLAUDE.md import
|
||||||
|
const userClaudeFile = path.join(targetDir, 'CLAUDE.md');
|
||||||
|
const taskMasterClaudeFile = path.join(targetDir, '.taskmaster', 'CLAUDE.md');
|
||||||
|
const importLine = '@./.taskmaster/CLAUDE.md';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Remove Task Master CLAUDE.md from .taskmaster
|
||||||
|
if (fs.existsSync(taskMasterClaudeFile)) {
|
||||||
|
fs.rmSync(taskMasterClaudeFile, { force: true });
|
||||||
|
log('debug', `[Claude] Removed ${taskMasterClaudeFile}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up import from user's CLAUDE.md
|
||||||
|
if (fs.existsSync(userClaudeFile)) {
|
||||||
|
const content = fs.readFileSync(userClaudeFile, 'utf8');
|
||||||
|
const lines = content.split('\n');
|
||||||
|
const filteredLines = [];
|
||||||
|
let skipNextLines = 0;
|
||||||
|
|
||||||
|
// Remove the Task Master section
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
if (skipNextLines > 0) {
|
||||||
|
skipNextLines--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this is the start of our Task Master section
|
||||||
|
if (lines[i].includes('## Task Master AI Instructions')) {
|
||||||
|
// Skip this line and the next two lines (bold text and import)
|
||||||
|
skipNextLines = 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also remove standalone import lines (for backward compatibility)
|
||||||
|
if (lines[i].trim() === importLine) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredLines.push(lines[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join back and clean up excessive newlines
|
||||||
|
let updatedContent = filteredLines
|
||||||
|
.join('\n')
|
||||||
|
.replace(/\n{3,}/g, '\n\n')
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
// Check if file only contained our minimal template
|
||||||
|
if (
|
||||||
|
updatedContent === '# Claude Code Instructions' ||
|
||||||
|
updatedContent === ''
|
||||||
|
) {
|
||||||
|
// File only contained our import, remove it
|
||||||
|
fs.rmSync(userClaudeFile, { force: true });
|
||||||
|
log('debug', `[Claude] Removed empty ${userClaudeFile}`);
|
||||||
|
} else {
|
||||||
|
// Write back without the import
|
||||||
|
fs.writeFileSync(userClaudeFile, updatedContent + '\n');
|
||||||
|
log(
|
||||||
|
'debug',
|
||||||
|
`[Claude] Removed Task Master import from ${userClaudeFile}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
log(
|
||||||
|
'error',
|
||||||
|
`[Claude] Failed to remove Claude instructions: ${err.message}`
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPostConvertRulesProfile(targetDir, assetsDir) {
|
function onPostConvertRulesProfile(targetDir, assetsDir) {
|
||||||
@@ -86,7 +214,7 @@ export const claudeProfile = createProfile({
|
|||||||
mcpConfigName: null,
|
mcpConfigName: null,
|
||||||
includeDefaultRules: false,
|
includeDefaultRules: false,
|
||||||
fileMap: {
|
fileMap: {
|
||||||
'AGENTS.md': 'CLAUDE.md'
|
'AGENTS.md': '.taskmaster/CLAUDE.md'
|
||||||
},
|
},
|
||||||
onAdd: onAddRulesProfile,
|
onAdd: onAddRulesProfile,
|
||||||
onRemove: onRemoveRulesProfile,
|
onRemove: onRemoveRulesProfile,
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ describe('Claude Profile Initialization Functionality', () => {
|
|||||||
expect(claudeProfileContent).toContain("rulesDir: '.'"); // non-default
|
expect(claudeProfileContent).toContain("rulesDir: '.'"); // non-default
|
||||||
expect(claudeProfileContent).toContain('mcpConfig: false'); // non-default
|
expect(claudeProfileContent).toContain('mcpConfig: false'); // non-default
|
||||||
expect(claudeProfileContent).toContain('includeDefaultRules: false'); // non-default
|
expect(claudeProfileContent).toContain('includeDefaultRules: false'); // non-default
|
||||||
expect(claudeProfileContent).toContain("'AGENTS.md': 'CLAUDE.md'");
|
expect(claudeProfileContent).toContain(
|
||||||
|
"'AGENTS.md': '.taskmaster/CLAUDE.md'"
|
||||||
|
);
|
||||||
|
|
||||||
// Check the final computed properties on the profile object
|
// Check the final computed properties on the profile object
|
||||||
expect(claudeProfile.profileName).toBe('claude');
|
expect(claudeProfile.profileName).toBe('claude');
|
||||||
@@ -33,7 +35,7 @@ describe('Claude Profile Initialization Functionality', () => {
|
|||||||
expect(claudeProfile.mcpConfig).toBe(false);
|
expect(claudeProfile.mcpConfig).toBe(false);
|
||||||
expect(claudeProfile.mcpConfigName).toBe(null); // computed
|
expect(claudeProfile.mcpConfigName).toBe(null); // computed
|
||||||
expect(claudeProfile.includeDefaultRules).toBe(false);
|
expect(claudeProfile.includeDefaultRules).toBe(false);
|
||||||
expect(claudeProfile.fileMap['AGENTS.md']).toBe('CLAUDE.md');
|
expect(claudeProfile.fileMap['AGENTS.md']).toBe('.taskmaster/CLAUDE.md');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('claude.js has lifecycle functions for file management', () => {
|
test('claude.js has lifecycle functions for file management', () => {
|
||||||
@@ -44,9 +46,11 @@ describe('Claude Profile Initialization Functionality', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('claude.js handles .claude directory in lifecycle functions', () => {
|
test('claude.js handles .claude directory and .taskmaster/CLAUDE.md import in lifecycle functions', () => {
|
||||||
expect(claudeProfileContent).toContain('.claude');
|
expect(claudeProfileContent).toContain('.claude');
|
||||||
expect(claudeProfileContent).toContain('copyRecursiveSync');
|
expect(claudeProfileContent).toContain('copyRecursiveSync');
|
||||||
|
expect(claudeProfileContent).toContain('.taskmaster/CLAUDE.md');
|
||||||
|
expect(claudeProfileContent).toContain('@./.taskmaster/CLAUDE.md');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('claude.js has proper error handling in lifecycle functions', () => {
|
test('claude.js has proper error handling in lifecycle functions', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user