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}`
|
||||
);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
@@ -67,6 +124,77 @@ function onRemoveRulesProfile(targetDir) {
|
||||
if (removeDirectoryRecursive(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) {
|
||||
@@ -86,7 +214,7 @@ export const claudeProfile = createProfile({
|
||||
mcpConfigName: null,
|
||||
includeDefaultRules: false,
|
||||
fileMap: {
|
||||
'AGENTS.md': 'CLAUDE.md'
|
||||
'AGENTS.md': '.taskmaster/CLAUDE.md'
|
||||
},
|
||||
onAdd: onAddRulesProfile,
|
||||
onRemove: onRemoveRulesProfile,
|
||||
|
||||
@@ -23,7 +23,9 @@ describe('Claude Profile Initialization Functionality', () => {
|
||||
expect(claudeProfileContent).toContain("rulesDir: '.'"); // non-default
|
||||
expect(claudeProfileContent).toContain('mcpConfig: 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
|
||||
expect(claudeProfile.profileName).toBe('claude');
|
||||
@@ -33,7 +35,7 @@ describe('Claude Profile Initialization Functionality', () => {
|
||||
expect(claudeProfile.mcpConfig).toBe(false);
|
||||
expect(claudeProfile.mcpConfigName).toBe(null); // computed
|
||||
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', () => {
|
||||
@@ -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('copyRecursiveSync');
|
||||
expect(claudeProfileContent).toContain('.taskmaster/CLAUDE.md');
|
||||
expect(claudeProfileContent).toContain('@./.taskmaster/CLAUDE.md');
|
||||
});
|
||||
|
||||
test('claude.js has proper error handling in lifecycle functions', () => {
|
||||
|
||||
Reference in New Issue
Block a user