@link-assistant/hive-mind 0.41.2 → 0.41.3
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/CHANGELOG.md +18 -0
- package/package.json +1 -1
- package/src/solve.results.lib.mjs +141 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 0.41.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- db8cef7: Fix CLAUDE.md not being deleted in continue mode
|
|
8
|
+
|
|
9
|
+
When a work session completes successfully but the CLAUDE.md commit hash was lost between sessions (e.g., due to session interruption), the system now attempts to detect the CLAUDE.md commit from the branch structure instead of silently skipping cleanup.
|
|
10
|
+
|
|
11
|
+
**Safety Checks (Preventing Issue #617 Recurrence):**
|
|
12
|
+
|
|
13
|
+
1. CLAUDE.md must exist in current branch
|
|
14
|
+
2. Find merge base to isolate PR-only commits
|
|
15
|
+
3. Must have at least 2 commits (CLAUDE.md + actual work)
|
|
16
|
+
4. First commit message must match expected pattern
|
|
17
|
+
5. First commit must ONLY change CLAUDE.md file
|
|
18
|
+
|
|
19
|
+
Fixes #940
|
|
20
|
+
|
|
3
21
|
## 0.41.2
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -50,14 +50,151 @@ const { reportError } = sentryLib;
|
|
|
50
50
|
const githubLinking = await import('./github-linking.lib.mjs');
|
|
51
51
|
const { hasGitHubLinkingKeyword } = githubLinking;
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Detect the CLAUDE.md commit hash from branch structure when not available in session
|
|
55
|
+
* This handles continue mode where the commit hash was lost between sessions
|
|
56
|
+
*
|
|
57
|
+
* Safety checks to prevent Issue #617 (wrong commit revert):
|
|
58
|
+
* 1. Only look at commits on the PR branch (not default branch commits)
|
|
59
|
+
* 2. Verify the commit message matches our expected pattern
|
|
60
|
+
* 3. Verify the commit ONLY adds CLAUDE.md (no other files changed)
|
|
61
|
+
* 4. Verify there are additional commits after it (actual work was done)
|
|
62
|
+
*
|
|
63
|
+
* @param {string} tempDir - The temporary directory with the git repo
|
|
64
|
+
* @param {string} branchName - The PR branch name
|
|
65
|
+
* @returns {string|null} - The detected commit hash or null if not found/safe
|
|
66
|
+
*/
|
|
67
|
+
const detectClaudeMdCommitFromBranch = async (tempDir, branchName) => {
|
|
68
|
+
try {
|
|
69
|
+
await log(' Attempting to detect CLAUDE.md commit from branch structure...', { verbose: true });
|
|
70
|
+
|
|
71
|
+
// First check if CLAUDE.md exists in current branch
|
|
72
|
+
const claudeMdExistsResult = await $({ cwd: tempDir })`git ls-files CLAUDE.md 2>&1`;
|
|
73
|
+
if (claudeMdExistsResult.code !== 0 || !claudeMdExistsResult.stdout || !claudeMdExistsResult.stdout.trim()) {
|
|
74
|
+
await log(' CLAUDE.md does not exist in current branch', { verbose: true });
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Get the default branch to find the fork point
|
|
79
|
+
const defaultBranchResult = await $({ cwd: tempDir })`git symbolic-ref refs/remotes/origin/HEAD 2>&1`;
|
|
80
|
+
let defaultBranch = 'main';
|
|
81
|
+
if (defaultBranchResult.code === 0 && defaultBranchResult.stdout) {
|
|
82
|
+
const match = defaultBranchResult.stdout.toString().match(/refs\/remotes\/origin\/(.+)/);
|
|
83
|
+
if (match) {
|
|
84
|
+
defaultBranch = match[1].trim();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
await log(` Using default branch: ${defaultBranch}`, { verbose: true });
|
|
88
|
+
|
|
89
|
+
// Find the merge base (fork point) between current branch and default branch
|
|
90
|
+
const mergeBaseResult = await $({ cwd: tempDir })`git merge-base origin/${defaultBranch} HEAD 2>&1`;
|
|
91
|
+
if (mergeBaseResult.code !== 0 || !mergeBaseResult.stdout) {
|
|
92
|
+
await log(' Could not find merge base, cannot safely detect CLAUDE.md commit', { verbose: true });
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
const mergeBase = mergeBaseResult.stdout.toString().trim();
|
|
96
|
+
await log(` Merge base: ${mergeBase.substring(0, 7)}`, { verbose: true });
|
|
97
|
+
|
|
98
|
+
// Get all commits on the PR branch (commits after the merge base)
|
|
99
|
+
// Format: hash|message|files_changed
|
|
100
|
+
const branchCommitsResult = await $({ cwd: tempDir })`git log ${mergeBase}..HEAD --reverse --format="%H|%s" 2>&1`;
|
|
101
|
+
if (branchCommitsResult.code !== 0 || !branchCommitsResult.stdout) {
|
|
102
|
+
await log(' No commits found on PR branch', { verbose: true });
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const branchCommits = branchCommitsResult.stdout.toString().trim().split('\n').filter(Boolean);
|
|
107
|
+
if (branchCommits.length === 0) {
|
|
108
|
+
await log(' No commits found on PR branch', { verbose: true });
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
await log(` Found ${branchCommits.length} commit(s) on PR branch`, { verbose: true });
|
|
113
|
+
|
|
114
|
+
// Safety check: Must have at least 2 commits (CLAUDE.md commit + actual work)
|
|
115
|
+
if (branchCommits.length < 2) {
|
|
116
|
+
await log(' Only 1 commit on branch - not enough commits to safely revert CLAUDE.md', { verbose: true });
|
|
117
|
+
await log(' (Need at least 2 commits: CLAUDE.md initial + actual work)', { verbose: true });
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Get the first commit on the PR branch
|
|
122
|
+
const firstCommitLine = branchCommits[0];
|
|
123
|
+
const [firstCommitHash, firstCommitMessage] = firstCommitLine.split('|');
|
|
124
|
+
|
|
125
|
+
await log(` First commit on branch: ${firstCommitHash.substring(0, 7)} - "${firstCommitMessage}"`, { verbose: true });
|
|
126
|
+
|
|
127
|
+
// Safety check: Verify commit message matches expected pattern
|
|
128
|
+
const expectedMessagePatterns = [
|
|
129
|
+
/^Initial commit with task details/i,
|
|
130
|
+
/^Add CLAUDE\.md/i,
|
|
131
|
+
/^CLAUDE\.md/i
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
const messageMatches = expectedMessagePatterns.some(pattern => pattern.test(firstCommitMessage));
|
|
135
|
+
if (!messageMatches) {
|
|
136
|
+
await log(' First commit message does not match expected CLAUDE.md pattern', { verbose: true });
|
|
137
|
+
await log(' Expected patterns: "Initial commit with task details...", "Add CLAUDE.md", etc.', { verbose: true });
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Safety check: Verify the commit ONLY adds CLAUDE.md file (no other files)
|
|
142
|
+
const filesChangedResult = await $({ cwd: tempDir })`git diff-tree --no-commit-id --name-only -r ${firstCommitHash} 2>&1`;
|
|
143
|
+
if (filesChangedResult.code !== 0 || !filesChangedResult.stdout) {
|
|
144
|
+
await log(' Could not get files changed in first commit', { verbose: true });
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const filesChanged = filesChangedResult.stdout.toString().trim().split('\n').filter(Boolean);
|
|
149
|
+
await log(` Files changed in first commit: ${filesChanged.join(', ')}`, { verbose: true });
|
|
150
|
+
|
|
151
|
+
// Check if CLAUDE.md is in the files changed
|
|
152
|
+
if (!filesChanged.includes('CLAUDE.md')) {
|
|
153
|
+
await log(' First commit does not include CLAUDE.md', { verbose: true });
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// CRITICAL SAFETY CHECK: Only allow revert if CLAUDE.md is the ONLY file changed
|
|
158
|
+
// This prevents Issue #617 where reverting a commit deleted .gitignore, LICENSE, README.md
|
|
159
|
+
if (filesChanged.length > 1) {
|
|
160
|
+
await log(` ⚠️ First commit changes more than just CLAUDE.md (${filesChanged.length} files)`, { verbose: true });
|
|
161
|
+
await log(` Files: ${filesChanged.join(', ')}`, { verbose: true });
|
|
162
|
+
await log(' Refusing to revert to prevent data loss (Issue #617 safety)', { verbose: true });
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// All safety checks passed!
|
|
167
|
+
await log(` ✅ Detected CLAUDE.md commit: ${firstCommitHash.substring(0, 7)}`, { verbose: true });
|
|
168
|
+
await log(' ✅ Commit only contains CLAUDE.md (safe to revert)', { verbose: true });
|
|
169
|
+
await log(` ✅ Branch has ${branchCommits.length - 1} additional commit(s) (work was done)`, { verbose: true });
|
|
170
|
+
|
|
171
|
+
return firstCommitHash;
|
|
172
|
+
} catch (error) {
|
|
173
|
+
reportError(error, {
|
|
174
|
+
context: 'detect_claude_md_commit',
|
|
175
|
+
tempDir,
|
|
176
|
+
branchName,
|
|
177
|
+
operation: 'detect_commit_from_branch_structure'
|
|
178
|
+
});
|
|
179
|
+
await log(` Error detecting CLAUDE.md commit: ${error.message}`, { verbose: true });
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
53
184
|
// Revert the CLAUDE.md commit to restore original state
|
|
54
185
|
export const cleanupClaudeFile = async (tempDir, branchName, claudeCommitHash = null) => {
|
|
55
186
|
try {
|
|
56
|
-
//
|
|
57
|
-
// This
|
|
187
|
+
// If no commit hash provided, try to detect it from branch structure
|
|
188
|
+
// This handles continue mode where the hash was lost between sessions
|
|
58
189
|
if (!claudeCommitHash) {
|
|
59
|
-
await log(' No CLAUDE.md commit
|
|
60
|
-
|
|
190
|
+
await log(' No CLAUDE.md commit hash from session, attempting to detect from branch...', { verbose: true });
|
|
191
|
+
claudeCommitHash = await detectClaudeMdCommitFromBranch(tempDir, branchName);
|
|
192
|
+
|
|
193
|
+
if (!claudeCommitHash) {
|
|
194
|
+
await log(' Could not safely detect CLAUDE.md commit to revert', { verbose: true });
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
await log(` Detected CLAUDE.md commit: ${claudeCommitHash.substring(0, 7)}`, { verbose: true });
|
|
61
198
|
}
|
|
62
199
|
|
|
63
200
|
await log(formatAligned('🔄', 'Cleanup:', 'Reverting CLAUDE.md commit'));
|