golem-cc 2.1.2 → 3.0.0
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/commands/golem/build.md +18 -0
- package/.claude/commands/golem/config.md +39 -0
- package/.claude/commands/golem/continue.md +73 -0
- package/.claude/commands/golem/doctor.md +46 -0
- package/.claude/commands/golem/document.md +138 -0
- package/.claude/commands/golem/help.md +58 -0
- package/.claude/commands/golem/pause.md +130 -0
- package/.claude/commands/golem/plan.md +111 -0
- package/.claude/commands/golem/review.md +166 -0
- package/.claude/commands/golem/security.md +186 -0
- package/.claude/commands/golem/simplify.md +76 -0
- package/.claude/commands/golem/spec.md +105 -0
- package/.claude/commands/golem/status.md +33 -0
- package/.golem/agents/code-simplifier.md +54 -0
- package/.golem/agents/review-architecture.md +59 -0
- package/.golem/agents/review-logic.md +50 -0
- package/.golem/agents/review-security.md +50 -0
- package/.golem/agents/review-style.md +48 -0
- package/.golem/agents/review-tests.md +48 -0
- package/.golem/agents/spec-builder.md +60 -0
- package/.golem/bin/golem.mjs +270 -0
- package/.golem/lib/build.mjs +557 -0
- package/.golem/lib/claude.mjs +95 -0
- package/.golem/lib/config.mjs +421 -0
- package/.golem/lib/display.mjs +191 -0
- package/.golem/lib/doctor.mjs +197 -0
- package/.golem/lib/document.mjs +792 -0
- package/.golem/lib/gates.mjs +78 -0
- package/.golem/lib/init.mjs +166 -0
- package/.golem/lib/output.mjs +40 -0
- package/.golem/lib/ratelimit.mjs +86 -0
- package/.golem/lib/security.mjs +603 -0
- package/.golem/lib/simplify.mjs +101 -0
- package/.golem/lib/tui.mjs +368 -0
- package/.golem/lib/usage.mjs +119 -0
- package/.golem/lib/worktree.mjs +509 -0
- package/.golem/prompts/build.md +23 -0
- package/.golem/prompts/document-inline.md +66 -0
- package/.golem/prompts/document-markdown.md +80 -0
- package/.golem/prompts/simplify.md +35 -0
- package/README.md +141 -142
- package/bin/golem-shim.mjs +36 -0
- package/bin/install.mjs +193 -0
- package/package.json +27 -32
- package/.env.example +0 -17
- package/bin/golem +0 -1040
- package/commands/golem/build.md +0 -235
- package/commands/golem/config.md +0 -55
- package/commands/golem/doctor.md +0 -137
- package/commands/golem/help.md +0 -212
- package/commands/golem/plan.md +0 -214
- package/commands/golem/review.md +0 -376
- package/commands/golem/security.md +0 -204
- package/commands/golem/simplify.md +0 -94
- package/commands/golem/spec.md +0 -226
- package/commands/golem/status.md +0 -60
- package/dist/api/freshworks.d.ts +0 -61
- package/dist/api/freshworks.d.ts.map +0 -1
- package/dist/api/freshworks.js +0 -119
- package/dist/api/freshworks.js.map +0 -1
- package/dist/api/gitea.d.ts +0 -96
- package/dist/api/gitea.d.ts.map +0 -1
- package/dist/api/gitea.js +0 -154
- package/dist/api/gitea.js.map +0 -1
- package/dist/cli/index.d.ts +0 -9
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -352
- package/dist/cli/index.js.map +0 -1
- package/dist/sync/ticket-sync.d.ts +0 -53
- package/dist/sync/ticket-sync.d.ts.map +0 -1
- package/dist/sync/ticket-sync.js +0 -226
- package/dist/sync/ticket-sync.js.map +0 -1
- package/dist/types.d.ts +0 -125
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -5
- package/dist/types.js.map +0 -1
- package/dist/worktree/manager.d.ts +0 -54
- package/dist/worktree/manager.d.ts.map +0 -1
- package/dist/worktree/manager.js +0 -190
- package/dist/worktree/manager.js.map +0 -1
- package/golem/agents/code-simplifier.md +0 -81
- package/golem/agents/spec-builder.md +0 -90
- package/golem/prompts/PROMPT_build.md +0 -71
- package/golem/prompts/PROMPT_plan.md +0 -102
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
import { cp, access, stat, realpath } from 'node:fs/promises';
|
|
2
|
+
import { join, resolve, relative, normalize } from 'node:path';
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
4
|
+
import { expandTilde, getRepoName } from './config.mjs';
|
|
5
|
+
import { table } from './output.mjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Check if a source path should be excluded based on the excludes list.
|
|
9
|
+
* Matches if the source path starts with any exclude entry (after normalization).
|
|
10
|
+
*/
|
|
11
|
+
function isExcluded(relPath, excludes) {
|
|
12
|
+
const norm = normalize(relPath).replace(/\/+$/, '');
|
|
13
|
+
return excludes.some(ex => {
|
|
14
|
+
const normEx = normalize(ex).replace(/\/+$/, '');
|
|
15
|
+
return norm === normEx || norm.startsWith(normEx + '/');
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Recursively copy a source to destination, skipping excluded paths.
|
|
21
|
+
* For files: copy if not excluded.
|
|
22
|
+
* For directories: walk and copy non-excluded entries.
|
|
23
|
+
*/
|
|
24
|
+
async function copyWithExcludes(src, dest, mainRepoPath, excludes) {
|
|
25
|
+
const relPath = relative(mainRepoPath, src);
|
|
26
|
+
|
|
27
|
+
if (isExcluded(relPath, excludes)) return;
|
|
28
|
+
|
|
29
|
+
const info = await stat(src);
|
|
30
|
+
|
|
31
|
+
if (info.isFile()) {
|
|
32
|
+
await cp(src, dest);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (info.isDirectory()) {
|
|
37
|
+
const { readdir, mkdir } = await import('node:fs/promises');
|
|
38
|
+
await mkdir(dest, { recursive: true });
|
|
39
|
+
const entries = await readdir(src);
|
|
40
|
+
for (const entry of entries) {
|
|
41
|
+
await copyWithExcludes(join(src, entry), join(dest, entry), mainRepoPath, excludes);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Bootstrap a worktree by copying configured files and running setup.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} worktreePath - Path to the new worktree directory
|
|
50
|
+
* @param {string} mainRepoPath - Path to the main repository
|
|
51
|
+
* @param {object} config - Loaded golem config (with worktree defaults merged)
|
|
52
|
+
*/
|
|
53
|
+
export async function bootstrapWorktree(worktreePath, mainRepoPath, config) {
|
|
54
|
+
const { copies = [], excludes = [], setup = '' } = config.worktree || {};
|
|
55
|
+
|
|
56
|
+
// Copy each entry from main repo to worktree
|
|
57
|
+
for (const entry of copies) {
|
|
58
|
+
const src = join(mainRepoPath, entry);
|
|
59
|
+
|
|
60
|
+
// Skip silently if source doesn't exist
|
|
61
|
+
try {
|
|
62
|
+
await access(src);
|
|
63
|
+
} catch {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
await copyWithExcludes(src, join(worktreePath, entry), mainRepoPath, excludes);
|
|
69
|
+
} catch (err) {
|
|
70
|
+
console.warn(`Warning: failed to copy ${entry}: ${err.message}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Run setup command if configured
|
|
75
|
+
if (setup) {
|
|
76
|
+
try {
|
|
77
|
+
execSync(setup, { cwd: worktreePath, stdio: 'inherit' });
|
|
78
|
+
} catch (err) {
|
|
79
|
+
console.warn(`Warning: setup command failed: ${err.message}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Check if a git branch exists locally.
|
|
86
|
+
*/
|
|
87
|
+
function branchExists(name, cwd) {
|
|
88
|
+
try {
|
|
89
|
+
execSync(`git rev-parse --verify refs/heads/${name}`, { cwd, stdio: 'pipe' });
|
|
90
|
+
return true;
|
|
91
|
+
} catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Create a new git worktree with bootstrap.
|
|
98
|
+
*
|
|
99
|
+
* @param {string} name - Branch/worktree name
|
|
100
|
+
* @param {object} config - Loaded golem config (with worktree defaults merged)
|
|
101
|
+
* @returns {string} Full path to the created worktree
|
|
102
|
+
*/
|
|
103
|
+
export async function worktreeCreate(name, config) {
|
|
104
|
+
if (!name) {
|
|
105
|
+
throw new Error('Worktree name is required. Usage: golem worktree create <name>');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const mainRepoPath = process.cwd();
|
|
109
|
+
const repoName = getRepoName();
|
|
110
|
+
const baseDir = expandTilde(config.worktree.dir);
|
|
111
|
+
const worktreePath = join(baseDir, repoName, name);
|
|
112
|
+
|
|
113
|
+
if (branchExists(name, mainRepoPath)) {
|
|
114
|
+
execSync(`git worktree add ${worktreePath} ${name}`, { cwd: mainRepoPath, stdio: 'pipe' });
|
|
115
|
+
} else {
|
|
116
|
+
execSync(`git worktree add -b ${name} ${worktreePath}`, { cwd: mainRepoPath, stdio: 'pipe' });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
await bootstrapWorktree(worktreePath, mainRepoPath, config);
|
|
120
|
+
|
|
121
|
+
console.log(worktreePath);
|
|
122
|
+
return worktreePath;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Parse git worktree list --porcelain output into structured data.
|
|
127
|
+
*
|
|
128
|
+
* @param {string} porcelainOutput - Output from `git worktree list --porcelain`
|
|
129
|
+
* @returns {Array<{path: string, head: string, branch: string}>}
|
|
130
|
+
*/
|
|
131
|
+
function parseWorktreePorcelain(porcelainOutput) {
|
|
132
|
+
const worktrees = [];
|
|
133
|
+
const lines = porcelainOutput.trim().split('\n');
|
|
134
|
+
let current = {};
|
|
135
|
+
|
|
136
|
+
for (const line of lines) {
|
|
137
|
+
if (line.startsWith('worktree ')) {
|
|
138
|
+
// Start of a new worktree entry
|
|
139
|
+
if (current.path) {
|
|
140
|
+
worktrees.push(current);
|
|
141
|
+
}
|
|
142
|
+
current = { path: line.slice('worktree '.length) };
|
|
143
|
+
} else if (line.startsWith('HEAD ')) {
|
|
144
|
+
current.head = line.slice('HEAD '.length);
|
|
145
|
+
} else if (line.startsWith('branch ')) {
|
|
146
|
+
// Extract branch name from refs/heads/branch-name
|
|
147
|
+
const fullRef = line.slice('branch '.length);
|
|
148
|
+
current.branch = fullRef.replace(/^refs\/heads\//, '');
|
|
149
|
+
} else if (line === '') {
|
|
150
|
+
// Empty line marks end of an entry
|
|
151
|
+
if (current.path) {
|
|
152
|
+
worktrees.push(current);
|
|
153
|
+
current = {};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Push the last entry if it exists
|
|
159
|
+
if (current.path) {
|
|
160
|
+
worktrees.push(current);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return worktrees;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Resolve a path to its real path, following symlinks.
|
|
168
|
+
* Returns the original path if it doesn't exist or can't be resolved.
|
|
169
|
+
*/
|
|
170
|
+
async function safeRealpath(path) {
|
|
171
|
+
try {
|
|
172
|
+
return await realpath(path);
|
|
173
|
+
} catch {
|
|
174
|
+
return path;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get the repository name from git, even when inside a worktree.
|
|
180
|
+
* First tries to get the name from the git remote URL.
|
|
181
|
+
* Falls back to the main worktree directory name.
|
|
182
|
+
*/
|
|
183
|
+
function getRepoNameFromGit() {
|
|
184
|
+
try {
|
|
185
|
+
// Try to get from remote URL
|
|
186
|
+
const remoteUrl = execSync('git remote get-url origin', {
|
|
187
|
+
cwd: process.cwd(),
|
|
188
|
+
stdio: 'pipe',
|
|
189
|
+
encoding: 'utf-8'
|
|
190
|
+
}).trim();
|
|
191
|
+
const lastSegment = remoteUrl.split('/').pop();
|
|
192
|
+
return lastSegment.replace(/\.git$/, '');
|
|
193
|
+
} catch {
|
|
194
|
+
// No remote - parse the main worktree path from git worktree list
|
|
195
|
+
try {
|
|
196
|
+
const output = execSync('git worktree list --porcelain', {
|
|
197
|
+
cwd: process.cwd(),
|
|
198
|
+
stdio: 'pipe',
|
|
199
|
+
encoding: 'utf-8'
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// The first worktree entry is always the main one
|
|
203
|
+
const lines = output.trim().split('\n');
|
|
204
|
+
for (const line of lines) {
|
|
205
|
+
if (line.startsWith('worktree ')) {
|
|
206
|
+
const mainPath = line.slice('worktree '.length);
|
|
207
|
+
return mainPath.split('/').pop();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Fallback to getRepoName
|
|
212
|
+
return getRepoName();
|
|
213
|
+
} catch {
|
|
214
|
+
// Not in a git repo - fall back to getRepoName()
|
|
215
|
+
return getRepoName();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* List all worktrees for the current repository.
|
|
222
|
+
*
|
|
223
|
+
* @param {object} config - Loaded golem config (with worktree defaults merged)
|
|
224
|
+
* @param {object} opts - Options for display
|
|
225
|
+
* @param {boolean} opts.display - If true, print a formatted table (default: true)
|
|
226
|
+
* @returns {Promise<Array<{path: string, head: string, branch: string, current: boolean}>>}
|
|
227
|
+
*/
|
|
228
|
+
export async function worktreeList(config, opts = { display: true }) {
|
|
229
|
+
const repoName = getRepoNameFromGit();
|
|
230
|
+
const baseDir = expandTilde(config.worktree.dir);
|
|
231
|
+
|
|
232
|
+
// Resolve the base dir to handle symlinks (like /var -> /private/var on macOS)
|
|
233
|
+
const baseDirResolved = await safeRealpath(baseDir);
|
|
234
|
+
const expectedPrefix = join(baseDirResolved, repoName);
|
|
235
|
+
const cwd = await safeRealpath(process.cwd());
|
|
236
|
+
|
|
237
|
+
// Get all worktrees
|
|
238
|
+
let output;
|
|
239
|
+
try {
|
|
240
|
+
output = execSync('git worktree list --porcelain', {
|
|
241
|
+
cwd: process.cwd(),
|
|
242
|
+
encoding: 'utf-8',
|
|
243
|
+
stdio: 'pipe',
|
|
244
|
+
});
|
|
245
|
+
} catch (err) {
|
|
246
|
+
throw new Error(`Failed to list worktrees: ${err.message}`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Parse the porcelain output
|
|
250
|
+
const allWorktrees = parseWorktreePorcelain(output);
|
|
251
|
+
|
|
252
|
+
// Filter to only worktrees under the configured directory for this repo
|
|
253
|
+
const filtered = allWorktrees.filter(wt =>
|
|
254
|
+
wt.path.startsWith(expectedPrefix)
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
// Mark the current worktree if we're inside one
|
|
258
|
+
for (const wt of filtered) {
|
|
259
|
+
wt.current = cwd.startsWith(wt.path);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Display formatted table if requested
|
|
263
|
+
if (opts.display) {
|
|
264
|
+
if (filtered.length === 0) {
|
|
265
|
+
console.log('No worktrees found.');
|
|
266
|
+
} else {
|
|
267
|
+
const rows = filtered.map(wt => [
|
|
268
|
+
wt.current ? '* ' + wt.branch : wt.branch,
|
|
269
|
+
wt.path,
|
|
270
|
+
wt.head.slice(0, 7),
|
|
271
|
+
]);
|
|
272
|
+
table(['Branch', 'Path', 'HEAD'], rows);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return filtered;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Detect if the current working directory is inside a git worktree.
|
|
281
|
+
* Inside a worktree, --git-common-dir and --git-dir differ.
|
|
282
|
+
*
|
|
283
|
+
* @param {string} cwd - Directory to check
|
|
284
|
+
* @returns {{ isWorktree: boolean, gitCommonDir: string|null, gitDir: string|null }}
|
|
285
|
+
*/
|
|
286
|
+
function detectWorktree(cwd) {
|
|
287
|
+
try {
|
|
288
|
+
const gitDir = execSync('git rev-parse --git-dir', {
|
|
289
|
+
cwd, stdio: 'pipe', encoding: 'utf-8',
|
|
290
|
+
}).trim();
|
|
291
|
+
const gitCommonDir = execSync('git rev-parse --git-common-dir', {
|
|
292
|
+
cwd, stdio: 'pipe', encoding: 'utf-8',
|
|
293
|
+
}).trim();
|
|
294
|
+
// Inside a worktree, gitDir is something like /path/to/.git/worktrees/<name>
|
|
295
|
+
// while gitCommonDir is /path/to/.git — they differ.
|
|
296
|
+
// In the main repo, both resolve to .git (or the same absolute path).
|
|
297
|
+
// Use resolve() to handle both relative and absolute paths from git.
|
|
298
|
+
const resolvedDir = resolve(cwd, gitDir);
|
|
299
|
+
const resolvedCommon = resolve(cwd, gitCommonDir);
|
|
300
|
+
return {
|
|
301
|
+
isWorktree: resolvedDir !== resolvedCommon,
|
|
302
|
+
gitCommonDir: resolvedCommon,
|
|
303
|
+
gitDir: resolvedDir,
|
|
304
|
+
};
|
|
305
|
+
} catch {
|
|
306
|
+
return { isWorktree: false, gitCommonDir: null, gitDir: null };
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Detect the default branch name (main or master).
|
|
312
|
+
*
|
|
313
|
+
* @param {string} cwd - Directory to run git commands in
|
|
314
|
+
* @returns {string} The default branch name
|
|
315
|
+
*/
|
|
316
|
+
function detectDefaultBranch(cwd) {
|
|
317
|
+
// Check if 'main' exists
|
|
318
|
+
try {
|
|
319
|
+
execSync('git rev-parse --verify refs/heads/main', { cwd, stdio: 'pipe' });
|
|
320
|
+
return 'main';
|
|
321
|
+
} catch {
|
|
322
|
+
// fall through
|
|
323
|
+
}
|
|
324
|
+
// Check if 'master' exists
|
|
325
|
+
try {
|
|
326
|
+
execSync('git rev-parse --verify refs/heads/master', { cwd, stdio: 'pipe' });
|
|
327
|
+
return 'master';
|
|
328
|
+
} catch {
|
|
329
|
+
// fall through
|
|
330
|
+
}
|
|
331
|
+
throw new Error('Could not detect default branch (neither main nor master found).');
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Merge the current worktree branch back into the default branch.
|
|
336
|
+
* Must be run from inside a worktree — fails with clear error otherwise.
|
|
337
|
+
*
|
|
338
|
+
* Steps:
|
|
339
|
+
* 1. Stage and commit any uncommitted changes
|
|
340
|
+
* 2. Save branch name and worktree path
|
|
341
|
+
* 3. Navigate to main repo via git-common-dir/..
|
|
342
|
+
* 4. Remove the worktree (branch/commits survive in .git)
|
|
343
|
+
* 5. Rebase branch onto default branch
|
|
344
|
+
* 6. Switch to default branch
|
|
345
|
+
* 7. Fast-forward merge the branch
|
|
346
|
+
* 8. Delete the branch
|
|
347
|
+
*
|
|
348
|
+
* If rebase fails, stops and prints instructions. Branch and commits are safe.
|
|
349
|
+
*/
|
|
350
|
+
export async function worktreeMerge() {
|
|
351
|
+
const cwd = process.cwd();
|
|
352
|
+
|
|
353
|
+
// Step 0: Detect if we're inside a worktree
|
|
354
|
+
const { isWorktree, gitCommonDir } = detectWorktree(cwd);
|
|
355
|
+
if (!isWorktree) {
|
|
356
|
+
throw new Error(
|
|
357
|
+
'Not inside a worktree. Run this command from inside a worktree directory.'
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Step 1: Stage and commit any uncommitted changes
|
|
362
|
+
const status = execSync('git status --porcelain', {
|
|
363
|
+
cwd, stdio: 'pipe', encoding: 'utf-8',
|
|
364
|
+
}).trim();
|
|
365
|
+
|
|
366
|
+
if (status) {
|
|
367
|
+
execSync('git add -A', { cwd, stdio: 'pipe' });
|
|
368
|
+
execSync('git commit -m "wip: uncommitted changes before merge"', {
|
|
369
|
+
cwd, stdio: 'pipe',
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Step 2: Save branch name and worktree path
|
|
374
|
+
const branch = execSync('git rev-parse --abbrev-ref HEAD', {
|
|
375
|
+
cwd, stdio: 'pipe', encoding: 'utf-8',
|
|
376
|
+
}).trim();
|
|
377
|
+
|
|
378
|
+
const worktreePath = execSync('git rev-parse --show-toplevel', {
|
|
379
|
+
cwd, stdio: 'pipe', encoding: 'utf-8',
|
|
380
|
+
}).trim();
|
|
381
|
+
|
|
382
|
+
// Step 3: Navigate to main repo via git-common-dir/..
|
|
383
|
+
const mainRepoPath = resolve(gitCommonDir, '..');
|
|
384
|
+
|
|
385
|
+
// Step 4: Remove the worktree (branch and commits are safe in .git)
|
|
386
|
+
execSync(`git worktree remove ${worktreePath}`, {
|
|
387
|
+
cwd: mainRepoPath, stdio: 'pipe',
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// Step 5: Detect default branch and rebase
|
|
391
|
+
const defaultBranch = detectDefaultBranch(mainRepoPath);
|
|
392
|
+
|
|
393
|
+
try {
|
|
394
|
+
execSync(`git rebase ${defaultBranch} ${branch}`, {
|
|
395
|
+
cwd: mainRepoPath, stdio: 'pipe',
|
|
396
|
+
});
|
|
397
|
+
} catch {
|
|
398
|
+
// Abort the failed rebase so git is clean
|
|
399
|
+
try {
|
|
400
|
+
execSync('git rebase --abort', { cwd: mainRepoPath, stdio: 'pipe' });
|
|
401
|
+
} catch {
|
|
402
|
+
// rebase --abort may fail if there's nothing to abort
|
|
403
|
+
}
|
|
404
|
+
throw new Error(
|
|
405
|
+
`Rebase of '${branch}' onto '${defaultBranch}' failed due to conflicts.\n` +
|
|
406
|
+
`Your branch '${branch}' and all commits are safe.\n` +
|
|
407
|
+
`The worktree has been removed, but you can resolve from the main repo:\n\n` +
|
|
408
|
+
` cd ${mainRepoPath}\n` +
|
|
409
|
+
` git rebase ${defaultBranch} ${branch}\n` +
|
|
410
|
+
` # resolve conflicts, then:\n` +
|
|
411
|
+
` git rebase --continue\n` +
|
|
412
|
+
` git switch ${defaultBranch}\n` +
|
|
413
|
+
` git merge --ff-only ${branch}\n` +
|
|
414
|
+
` git branch -d ${branch}`
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Step 6: Switch to default branch
|
|
419
|
+
execSync(`git switch ${defaultBranch}`, {
|
|
420
|
+
cwd: mainRepoPath, stdio: 'pipe',
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
// Step 7: Fast-forward merge
|
|
424
|
+
execSync(`git merge --ff-only ${branch}`, {
|
|
425
|
+
cwd: mainRepoPath, stdio: 'pipe',
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
// Step 8: Delete the branch
|
|
429
|
+
execSync(`git branch -d ${branch}`, {
|
|
430
|
+
cwd: mainRepoPath, stdio: 'pipe',
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
return { branch, defaultBranch, mainRepoPath };
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Remove a worktree and delete its branch without merging.
|
|
438
|
+
* Used for abandoned work.
|
|
439
|
+
*
|
|
440
|
+
* @param {string} name - Name of the worktree/branch to remove
|
|
441
|
+
* @param {object} config - Loaded golem config (with worktree defaults merged)
|
|
442
|
+
* @returns {Promise<{path: string, branch: string}>}
|
|
443
|
+
*/
|
|
444
|
+
export async function worktreeRemove(name, config) {
|
|
445
|
+
if (!name) {
|
|
446
|
+
throw new Error('Worktree name is required. Usage: golem worktree remove <name>');
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const mainRepoPath = process.cwd();
|
|
450
|
+
const repoName = getRepoName();
|
|
451
|
+
const baseDir = expandTilde(config.worktree.dir);
|
|
452
|
+
const worktreePath = join(baseDir, repoName, name);
|
|
453
|
+
|
|
454
|
+
// Resolve both paths to handle symlinks (like /var -> /private/var on macOS)
|
|
455
|
+
const worktreePathResolved = await safeRealpath(worktreePath);
|
|
456
|
+
|
|
457
|
+
// Check if the worktree exists
|
|
458
|
+
let worktreeList;
|
|
459
|
+
try {
|
|
460
|
+
worktreeList = execSync('git worktree list --porcelain', {
|
|
461
|
+
cwd: mainRepoPath,
|
|
462
|
+
encoding: 'utf-8',
|
|
463
|
+
stdio: 'pipe',
|
|
464
|
+
});
|
|
465
|
+
} catch (err) {
|
|
466
|
+
throw new Error(`Failed to list worktrees: ${err.message}`);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const worktrees = parseWorktreePorcelain(worktreeList);
|
|
470
|
+
|
|
471
|
+
// Resolve all worktree paths and compare
|
|
472
|
+
const resolvedWorktrees = await Promise.all(
|
|
473
|
+
worktrees.map(async wt => ({
|
|
474
|
+
...wt,
|
|
475
|
+
resolvedPath: await safeRealpath(wt.path)
|
|
476
|
+
}))
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
const exists = resolvedWorktrees.some(wt =>
|
|
480
|
+
wt.resolvedPath === worktreePathResolved || wt.path === worktreePath
|
|
481
|
+
);
|
|
482
|
+
|
|
483
|
+
if (!exists) {
|
|
484
|
+
throw new Error(`Worktree '${name}' does not exist at ${worktreePath}`);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Remove the worktree
|
|
488
|
+
try {
|
|
489
|
+
execSync(`git worktree remove ${worktreePath}`, {
|
|
490
|
+
cwd: mainRepoPath,
|
|
491
|
+
stdio: 'pipe',
|
|
492
|
+
});
|
|
493
|
+
} catch (err) {
|
|
494
|
+
throw new Error(`Failed to remove worktree: ${err.message}`);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Delete the branch with -D (force delete, in case it's not merged)
|
|
498
|
+
try {
|
|
499
|
+
execSync(`git branch -D ${name}`, {
|
|
500
|
+
cwd: mainRepoPath,
|
|
501
|
+
stdio: 'pipe',
|
|
502
|
+
});
|
|
503
|
+
} catch (err) {
|
|
504
|
+
// If branch deletion fails, warn but don't fail the whole operation
|
|
505
|
+
console.warn(`Warning: failed to delete branch '${name}': ${err.message}`);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return { path: worktreePath, branch: name };
|
|
509
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
You are an autonomous coding agent working on a single task. You have fresh context — assume no prior knowledge of this codebase.
|
|
2
|
+
|
|
3
|
+
## Instructions
|
|
4
|
+
|
|
5
|
+
1. Read `.golem/AGENTS.md` for test, build, and lint commands
|
|
6
|
+
2. Read ALL relevant spec files in `.golem/specs/`
|
|
7
|
+
3. Investigate the codebase — read existing code that's related to your task
|
|
8
|
+
4. Implement the task described below
|
|
9
|
+
5. Run ALL validation gates:
|
|
10
|
+
- Test command from AGENTS.md
|
|
11
|
+
- Type checking (if applicable)
|
|
12
|
+
- Lint (if applicable)
|
|
13
|
+
6. Fix any failures before reporting success
|
|
14
|
+
|
|
15
|
+
## Rules
|
|
16
|
+
|
|
17
|
+
- Do NOT commit changes (the orchestrator handles git)
|
|
18
|
+
- Do NOT modify `.golem/IMPLEMENTATION_PLAN.md`
|
|
19
|
+
- Do NOT modify files outside the project directory
|
|
20
|
+
- Keep changes minimal and focused on the task
|
|
21
|
+
- Follow existing code patterns and conventions
|
|
22
|
+
- If tests fail, fix them — do not skip or disable tests
|
|
23
|
+
- If you can't complete the task, explain why clearly
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
You are a documentation agent. Your job is to add inline documentation to source files following the conventions of the detected project type.
|
|
2
|
+
|
|
3
|
+
## Project Type
|
|
4
|
+
|
|
5
|
+
{{PROJECT_TYPE}}
|
|
6
|
+
|
|
7
|
+
## Documentation Standards by Type
|
|
8
|
+
|
|
9
|
+
### JavaScript (JSDoc)
|
|
10
|
+
- Use `/** ... */` block comments on functions, classes, and non-obvious exports
|
|
11
|
+
- Include `@param`, `@returns`, and `@throws` tags where meaningful
|
|
12
|
+
- File header: `/** @file <description of the file's role in the system> */`
|
|
13
|
+
|
|
14
|
+
### TypeScript (TSDoc)
|
|
15
|
+
- Use `/** ... */` block comments on functions, classes, interfaces, and type aliases
|
|
16
|
+
- Include `@param`, `@returns`, `@throws`, and `@example` tags on public API functions
|
|
17
|
+
- File header: `/** @file <description of the file's role in the system> */`
|
|
18
|
+
|
|
19
|
+
### Python (Google-style docstrings)
|
|
20
|
+
- Use triple-quoted Google-style docstrings on modules, classes, and functions
|
|
21
|
+
- Include Args, Returns, Raises sections where meaningful
|
|
22
|
+
- Module docstring at the top of every file explaining its purpose
|
|
23
|
+
|
|
24
|
+
### SQL (comment blocks)
|
|
25
|
+
- Use `--` comment blocks above tables, columns, constraints, stored procedures, and views
|
|
26
|
+
- Group related comments into header blocks for each object
|
|
27
|
+
- Explain relationships, constraints, and non-obvious column purposes
|
|
28
|
+
|
|
29
|
+
### Vue / Nuxt SFCs
|
|
30
|
+
- `<script>`: JSDoc or TSDoc depending on language (JS or TS)
|
|
31
|
+
- `<template>`: `<!-- ... -->` comments for non-obvious markup, conditional rendering logic, and slot usage
|
|
32
|
+
- `<style>`: `/* ... */` comments for non-obvious selectors, layout tricks, and overrides
|
|
33
|
+
|
|
34
|
+
### React / Next
|
|
35
|
+
- JSDoc or TSDoc depending on language (JS or TS)
|
|
36
|
+
- Document prop types and their purpose on component functions
|
|
37
|
+
- Document hooks, context providers, and non-obvious rendering logic
|
|
38
|
+
|
|
39
|
+
## Rules
|
|
40
|
+
|
|
41
|
+
1. **Document non-obvious functions** — if a function's name and signature don't fully explain what it does, why it exists, or what side effects it has, add documentation
|
|
42
|
+
2. **Add file headers** — every source file gets a header comment explaining what the file does and its role in the system
|
|
43
|
+
3. **Educational tone** — write documentation so someone new to the codebase can learn from it; explain the "why", not just the "what"
|
|
44
|
+
4. **Skip trivial one-liners** — simple getters, identity functions, and self-documenting one-line arrow functions don't need docs unless they have non-obvious side effects
|
|
45
|
+
5. **Update stale docs** — if existing documentation doesn't match current code behavior, update it; stale docs are worse than no docs
|
|
46
|
+
6. **Preserve accurate docs** — don't rewrite documentation that is already correct and clear
|
|
47
|
+
7. **Don't change behavior** — documentation only; never modify logic, add type annotations to JS files, or alter control flow
|
|
48
|
+
8. **Don't restate signatures** — `@param name - the name` adds no value; describe what the parameter controls or why it matters
|
|
49
|
+
9. **Document complex logic inline** — non-obvious conditionals, "why" decisions, and tricky control flow get inline comments where they occur
|
|
50
|
+
10. **Don't touch excluded files** — skip test files, generated files, lock files, node_modules, dist/, and minified/bundled output
|
|
51
|
+
|
|
52
|
+
## Target Files
|
|
53
|
+
|
|
54
|
+
{{FILES}}
|
|
55
|
+
|
|
56
|
+
## Process
|
|
57
|
+
|
|
58
|
+
For each file:
|
|
59
|
+
1. Read the file
|
|
60
|
+
2. Determine the correct documentation standard from the project type
|
|
61
|
+
3. Add file header if missing
|
|
62
|
+
4. Document non-obvious functions, classes, and exports
|
|
63
|
+
5. Update any stale existing documentation
|
|
64
|
+
6. Add inline comments for complex logic
|
|
65
|
+
7. For Vue SFCs: cover `<template>` and `<style>` sections too
|
|
66
|
+
8. Move to the next file
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
You are a documentation agent. Your job is to generate and update project-level markdown documentation files.
|
|
2
|
+
|
|
3
|
+
## Output Directory
|
|
4
|
+
|
|
5
|
+
{{DOCS_PATH}}
|
|
6
|
+
|
|
7
|
+
## Project Structure
|
|
8
|
+
|
|
9
|
+
{{PROJECT_STRUCTURE}}
|
|
10
|
+
|
|
11
|
+
## Directory Layout
|
|
12
|
+
|
|
13
|
+
Generate the following structure under the docs path. Only create files for things that actually exist in the codebase — never create empty placeholders.
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
{docsPath}/
|
|
17
|
+
index.md — project overview
|
|
18
|
+
architecture.md — system design, how components connect
|
|
19
|
+
getting-started.md — setup, install, prerequisites, first run
|
|
20
|
+
configuration.md — all config options with explanations
|
|
21
|
+
modules/
|
|
22
|
+
{module-name}.md — one per major module or component
|
|
23
|
+
api/
|
|
24
|
+
{resource}.md — API reference (if project has endpoints)
|
|
25
|
+
database/
|
|
26
|
+
schema.md — full schema overview with relationships
|
|
27
|
+
{table-name}.md — per-table docs (columns, types, constraints, purpose)
|
|
28
|
+
guides/
|
|
29
|
+
setup.md — dev environment setup
|
|
30
|
+
{workflow}.md — project-specific how-to walkthroughs
|
|
31
|
+
deprecated/
|
|
32
|
+
{old-thing}.md — archived docs with deprecation date and replacement notes
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Frontmatter
|
|
36
|
+
|
|
37
|
+
Every markdown file must include YAML frontmatter with these fields:
|
|
38
|
+
|
|
39
|
+
```yaml
|
|
40
|
+
---
|
|
41
|
+
title: "Page Title"
|
|
42
|
+
description: "Brief description of what this page covers"
|
|
43
|
+
navigation:
|
|
44
|
+
order: 1
|
|
45
|
+
---
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Use `navigation.order` to control page ordering within each directory. Start at 1 and increment.
|
|
49
|
+
|
|
50
|
+
## Rules
|
|
51
|
+
|
|
52
|
+
1. **Use CommonMark only** — no platform-specific markdown extensions (no GitHub admonitions, no MDX unless the project uses it)
|
|
53
|
+
2. **No empty placeholders** — only create docs for modules, APIs, tables, and guides that actually exist in the codebase
|
|
54
|
+
3. **Never delete existing docs** — if a documented module has been removed from the codebase, move its doc to `{docsPath}/deprecated/` with a `deprecated_date` frontmatter field and notes on what replaced it
|
|
55
|
+
4. **Architecture overview** — include a text-based diagram or structured description showing how major components connect
|
|
56
|
+
5. **Module docs from source** — generate one doc per major module explaining purpose, exports, dependencies, and usage; reference file paths, don't dump source code
|
|
57
|
+
6. **Database docs from files** — generate schema docs from migration files, ORM definitions (Prisma, Drizzle, Knex, SQLAlchemy, Alembic), or raw SQL files in the repo; no live database connection
|
|
58
|
+
7. **Relationship descriptions** — document foreign keys, one-to-many, and other relationships in plain language
|
|
59
|
+
8. **Getting-started guide** — use actual project commands, config values, and file paths; not generic boilerplate
|
|
60
|
+
9. **Guides section** — generate project-specific how-tos based on detected workflows (e.g., "How to add a new API endpoint" if the project has an API layer)
|
|
61
|
+
10. **Cross-link related docs** — module docs link to the API docs that expose them, database docs link to the modules that query them
|
|
62
|
+
11. **Don't include source code dumps** — explain behavior and usage, reference file paths for code
|
|
63
|
+
12. **README.md** — generate or update the project root README with project name, description, quick start, and links into the docs directory
|
|
64
|
+
13. **CHANGELOG.md** — generate from git history in plain, non-technical language for managers and non-developers; group by date with sections: New Features, Improvements, Bug Fixes; no jargon
|
|
65
|
+
|
|
66
|
+
## Process
|
|
67
|
+
|
|
68
|
+
1. Read the project structure to understand what modules, APIs, database schemas, and configs exist
|
|
69
|
+
2. Create the docs directory structure
|
|
70
|
+
3. Generate `index.md` with project overview
|
|
71
|
+
4. Generate `architecture.md` with system design and component relationships
|
|
72
|
+
5. Generate `getting-started.md` with actual setup instructions
|
|
73
|
+
6. Generate `configuration.md` documenting all config options
|
|
74
|
+
7. Generate module docs for each major module
|
|
75
|
+
8. Generate API docs if the project has endpoints
|
|
76
|
+
9. Generate database docs if schema/migration files exist
|
|
77
|
+
10. Generate guides based on detected project workflows
|
|
78
|
+
11. Move docs for removed modules to `deprecated/`
|
|
79
|
+
12. Update `README.md` at project root
|
|
80
|
+
13. Generate `CHANGELOG.md` from git history
|