@damper/cli 0.9.20 → 0.10.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/dist/commands/release.js +4 -30
- package/dist/commands/setup.js +3 -11
- package/dist/commands/start.js +33 -115
- package/dist/index.js +4 -16
- package/dist/services/claude.d.ts +8 -9
- package/dist/services/claude.js +49 -85
- package/dist/services/config.js +13 -3
- package/dist/services/damper-api.d.ts +1 -0
- package/dist/services/git.d.ts +4 -0
- package/dist/services/git.js +11 -0
- package/dist/ui/format.d.ts +1 -1
- package/dist/ui/format.js +5 -5
- package/dist/ui/task-picker.d.ts +0 -3
- package/dist/ui/task-picker.js +6 -36
- package/package.json +2 -3
- package/dist/commands/cleanup.d.ts +0 -1
- package/dist/commands/cleanup.js +0 -203
- package/dist/commands/status.d.ts +0 -1
- package/dist/commands/status.js +0 -94
- package/dist/services/context-bootstrap.d.ts +0 -30
- package/dist/services/context-bootstrap.js +0 -100
- package/dist/services/state.d.ts +0 -22
- package/dist/services/state.js +0 -102
- package/dist/services/worktree.d.ts +0 -40
- package/dist/services/worktree.js +0 -469
- package/dist/templates/CLAUDE_APPEND.md.d.ts +0 -7
- package/dist/templates/CLAUDE_APPEND.md.js +0 -35
- package/dist/templates/TASK_CONTEXT.md.d.ts +0 -17
- package/dist/templates/TASK_CONTEXT.md.js +0 -149
package/dist/commands/cleanup.js
DELETED
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
import * as fs from 'node:fs';
|
|
2
|
-
import { confirm, checkbox } from '@inquirer/prompts';
|
|
3
|
-
import { execa } from 'execa';
|
|
4
|
-
import pc from 'picocolors';
|
|
5
|
-
import { getWorktrees, cleanupStaleWorktrees, removeWorktree } from '../services/state.js';
|
|
6
|
-
import { createDamperApi } from '../services/damper-api.js';
|
|
7
|
-
import { removeWorktreeDir } from '../services/worktree.js';
|
|
8
|
-
import { shortId, shortIdRaw, formatTaskLine } from '../ui/format.js';
|
|
9
|
-
async function hasUncommittedChanges(worktreePath) {
|
|
10
|
-
try {
|
|
11
|
-
const { stdout } = await execa('git', ['status', '--porcelain'], {
|
|
12
|
-
cwd: worktreePath,
|
|
13
|
-
stdio: 'pipe',
|
|
14
|
-
});
|
|
15
|
-
return stdout.trim().length > 0;
|
|
16
|
-
}
|
|
17
|
-
catch {
|
|
18
|
-
return false;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
export async function cleanupCommand() {
|
|
22
|
-
// Clean up stale entries first
|
|
23
|
-
const stale = cleanupStaleWorktrees();
|
|
24
|
-
if (stale.length > 0) {
|
|
25
|
-
console.log(pc.dim(`Removed ${stale.length} stale worktree reference(s)`));
|
|
26
|
-
}
|
|
27
|
-
const worktrees = getWorktrees();
|
|
28
|
-
if (worktrees.length === 0) {
|
|
29
|
-
console.log(pc.yellow('\nNo tracked worktrees found.'));
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
// Try to connect to Damper for task status
|
|
33
|
-
let api;
|
|
34
|
-
try {
|
|
35
|
-
api = createDamperApi();
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
console.log(pc.yellow('\nWarning: Could not connect to Damper API (no API key).'));
|
|
39
|
-
console.log(pc.dim('Will show all worktrees for manual selection.\n'));
|
|
40
|
-
}
|
|
41
|
-
// Identify cleanup candidates
|
|
42
|
-
const candidates = [];
|
|
43
|
-
console.log(pc.dim('\nAnalyzing worktrees...'));
|
|
44
|
-
for (const wt of worktrees) {
|
|
45
|
-
const exists = fs.existsSync(wt.path);
|
|
46
|
-
if (!exists) {
|
|
47
|
-
// Worktree directory is missing
|
|
48
|
-
candidates.push({
|
|
49
|
-
worktree: wt,
|
|
50
|
-
reason: 'missing',
|
|
51
|
-
});
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
// Check for uncommitted changes
|
|
55
|
-
const uncommitted = await hasUncommittedChanges(wt.path);
|
|
56
|
-
if (api) {
|
|
57
|
-
try {
|
|
58
|
-
const task = await api.getTask(wt.taskId);
|
|
59
|
-
if (task.status === 'done') {
|
|
60
|
-
candidates.push({
|
|
61
|
-
worktree: wt,
|
|
62
|
-
task,
|
|
63
|
-
reason: 'completed',
|
|
64
|
-
hasUncommittedChanges: uncommitted,
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
else if (task.status === 'planned' && !task.lockedBy) {
|
|
68
|
-
// Task was abandoned (planned and not locked)
|
|
69
|
-
candidates.push({
|
|
70
|
-
worktree: wt,
|
|
71
|
-
task,
|
|
72
|
-
reason: 'abandoned',
|
|
73
|
-
hasUncommittedChanges: uncommitted,
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
else if (task.status === 'in_progress') {
|
|
77
|
-
// Task is still in progress - can drop it
|
|
78
|
-
candidates.push({
|
|
79
|
-
worktree: wt,
|
|
80
|
-
task,
|
|
81
|
-
reason: 'in_progress',
|
|
82
|
-
hasUncommittedChanges: uncommitted,
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
catch {
|
|
87
|
-
// Could not fetch task - might be deleted
|
|
88
|
-
candidates.push({
|
|
89
|
-
worktree: wt,
|
|
90
|
-
reason: 'manual',
|
|
91
|
-
hasUncommittedChanges: uncommitted,
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
else {
|
|
96
|
-
// No API - show all for manual selection
|
|
97
|
-
candidates.push({
|
|
98
|
-
worktree: wt,
|
|
99
|
-
reason: 'manual',
|
|
100
|
-
hasUncommittedChanges: uncommitted,
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
if (candidates.length === 0) {
|
|
105
|
-
console.log(pc.green('\n✓ No worktrees to clean up.\n'));
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
// Show candidates and let user select
|
|
109
|
-
console.log(pc.bold('\nWorktrees available for cleanup:\n'));
|
|
110
|
-
const choices = candidates.map(c => {
|
|
111
|
-
const worktreeName = c.worktree.path.split('/').pop() || c.worktree.path;
|
|
112
|
-
let description = c.task ? formatTaskLine(c.task) : shortId(c.worktree.taskId);
|
|
113
|
-
let reasonBadge = '';
|
|
114
|
-
switch (c.reason) {
|
|
115
|
-
case 'completed':
|
|
116
|
-
reasonBadge = pc.green('[completed]');
|
|
117
|
-
break;
|
|
118
|
-
case 'abandoned':
|
|
119
|
-
reasonBadge = pc.yellow('[abandoned]');
|
|
120
|
-
break;
|
|
121
|
-
case 'in_progress':
|
|
122
|
-
reasonBadge = pc.cyan('[in progress - will release]');
|
|
123
|
-
break;
|
|
124
|
-
case 'missing':
|
|
125
|
-
reasonBadge = pc.red('[missing]');
|
|
126
|
-
break;
|
|
127
|
-
case 'manual':
|
|
128
|
-
reasonBadge = pc.dim('[manual]');
|
|
129
|
-
break;
|
|
130
|
-
}
|
|
131
|
-
// Warning for uncommitted changes
|
|
132
|
-
const uncommittedWarning = c.hasUncommittedChanges
|
|
133
|
-
? pc.red(' ⚠ uncommitted changes')
|
|
134
|
-
: '';
|
|
135
|
-
return {
|
|
136
|
-
name: `${description} ${reasonBadge}${uncommittedWarning}\n ${pc.dim(worktreeName)}`,
|
|
137
|
-
value: c,
|
|
138
|
-
// Don't auto-check if there are uncommitted changes
|
|
139
|
-
checked: (c.reason === 'completed' || c.reason === 'missing') && !c.hasUncommittedChanges,
|
|
140
|
-
};
|
|
141
|
-
});
|
|
142
|
-
const selected = await checkbox({
|
|
143
|
-
message: 'Select worktrees to remove:',
|
|
144
|
-
choices,
|
|
145
|
-
pageSize: 10,
|
|
146
|
-
});
|
|
147
|
-
if (selected.length === 0) {
|
|
148
|
-
console.log(pc.dim('\nNo worktrees selected. Nothing to clean up.\n'));
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
// Check for uncommitted changes in selected worktrees
|
|
152
|
-
const withUncommitted = selected.filter(c => c.hasUncommittedChanges);
|
|
153
|
-
if (withUncommitted.length > 0) {
|
|
154
|
-
console.log(pc.yellow(`\n⚠ Warning: ${withUncommitted.length} worktree(s) have uncommitted changes that will be lost!`));
|
|
155
|
-
}
|
|
156
|
-
// Confirm
|
|
157
|
-
const confirmMessage = withUncommitted.length > 0
|
|
158
|
-
? `Remove ${selected.length} worktree(s)? (${withUncommitted.length} with uncommitted changes)`
|
|
159
|
-
: `Remove ${selected.length} worktree(s)?`;
|
|
160
|
-
const confirmed = await confirm({
|
|
161
|
-
message: confirmMessage,
|
|
162
|
-
default: false,
|
|
163
|
-
});
|
|
164
|
-
if (!confirmed) {
|
|
165
|
-
console.log(pc.dim('\nCancelled.\n'));
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
// Remove selected worktrees
|
|
169
|
-
console.log();
|
|
170
|
-
for (const candidate of selected) {
|
|
171
|
-
const { worktree } = candidate;
|
|
172
|
-
const worktreeName = worktree.path.split('/').pop() || worktree.path;
|
|
173
|
-
try {
|
|
174
|
-
// Release the task first if it's in progress
|
|
175
|
-
if (candidate.reason === 'in_progress' && api) {
|
|
176
|
-
try {
|
|
177
|
-
await api.abandonTask(worktree.taskId, 'Dropped via CLI cleanup');
|
|
178
|
-
console.log(pc.green(`✓ Released task #${shortIdRaw(worktree.taskId)}`));
|
|
179
|
-
}
|
|
180
|
-
catch (err) {
|
|
181
|
-
const error = err;
|
|
182
|
-
console.log(pc.yellow(`⚠ Could not release task: ${error.message}`));
|
|
183
|
-
// Continue with worktree removal anyway
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
if (candidate.reason === 'missing') {
|
|
187
|
-
// Just remove from state
|
|
188
|
-
removeWorktree(worktree.taskId);
|
|
189
|
-
console.log(pc.green(`✓ Removed reference: ${worktreeName}`));
|
|
190
|
-
}
|
|
191
|
-
else {
|
|
192
|
-
// Remove actual worktree
|
|
193
|
-
await removeWorktreeDir(worktree.path, worktree.projectRoot);
|
|
194
|
-
console.log(pc.green(`✓ Removed: ${worktreeName}`));
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
catch (err) {
|
|
198
|
-
const error = err;
|
|
199
|
-
console.log(pc.red(`✗ Failed to remove ${worktreeName}: ${error.message}`));
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
console.log();
|
|
203
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function statusCommand(): Promise<void>;
|
package/dist/commands/status.js
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import * as fs from 'node:fs';
|
|
2
|
-
import pc from 'picocolors';
|
|
3
|
-
import { getWorktrees, cleanupStaleWorktrees } from '../services/state.js';
|
|
4
|
-
import { createDamperApi } from '../services/damper-api.js';
|
|
5
|
-
import { getGitRoot } from '../services/worktree.js';
|
|
6
|
-
import { shortId, getTypeIcon, getPriorityIcon, formatEffort, formatSubtaskProgress, relativeTime, statusColor as getStatusColor, } from '../ui/format.js';
|
|
7
|
-
export async function statusCommand() {
|
|
8
|
-
// Clean up stale entries first
|
|
9
|
-
const stale = cleanupStaleWorktrees();
|
|
10
|
-
if (stale.length > 0) {
|
|
11
|
-
console.log(pc.dim(`Cleaned up ${stale.length} stale worktree reference(s)\n`));
|
|
12
|
-
}
|
|
13
|
-
// Get current project root
|
|
14
|
-
let projectRoot;
|
|
15
|
-
try {
|
|
16
|
-
projectRoot = await getGitRoot(process.cwd());
|
|
17
|
-
}
|
|
18
|
-
catch {
|
|
19
|
-
// Not in a git repo - show all worktrees
|
|
20
|
-
}
|
|
21
|
-
const worktrees = getWorktrees();
|
|
22
|
-
if (worktrees.length === 0) {
|
|
23
|
-
console.log(pc.yellow('\nNo tracked worktrees found.'));
|
|
24
|
-
console.log(pc.dim('Use `npx @damper/cli` to start working on a task.\n'));
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
// Try to fetch task statuses from Damper
|
|
28
|
-
let api;
|
|
29
|
-
try {
|
|
30
|
-
api = createDamperApi();
|
|
31
|
-
}
|
|
32
|
-
catch {
|
|
33
|
-
// No API key - show worktrees without status
|
|
34
|
-
}
|
|
35
|
-
console.log(pc.bold('\nTracked Worktrees:\n'));
|
|
36
|
-
// Group by project
|
|
37
|
-
const byProject = new Map();
|
|
38
|
-
for (const wt of worktrees) {
|
|
39
|
-
const project = wt.projectRoot;
|
|
40
|
-
if (!byProject.has(project)) {
|
|
41
|
-
byProject.set(project, []);
|
|
42
|
-
}
|
|
43
|
-
byProject.get(project).push(wt);
|
|
44
|
-
}
|
|
45
|
-
for (const [project, projectWorktrees] of byProject) {
|
|
46
|
-
const isCurrent = projectRoot && project === projectRoot;
|
|
47
|
-
const projectName = project.split('/').pop() || project;
|
|
48
|
-
console.log(pc.bold(`${projectName}${isCurrent ? pc.cyan(' (current)') : ''}`));
|
|
49
|
-
console.log(pc.dim(` ${project}`));
|
|
50
|
-
console.log();
|
|
51
|
-
for (const wt of projectWorktrees) {
|
|
52
|
-
const exists = fs.existsSync(wt.path);
|
|
53
|
-
const statusIcon = exists ? pc.green('●') : pc.red('○');
|
|
54
|
-
// Try to get task details from Damper
|
|
55
|
-
if (api) {
|
|
56
|
-
try {
|
|
57
|
-
const task = await api.getTask(wt.taskId);
|
|
58
|
-
const priorityIcon = getPriorityIcon(task.priority);
|
|
59
|
-
const typeIcon = getTypeIcon(task.type);
|
|
60
|
-
const colorFn = getStatusColor(task.status);
|
|
61
|
-
const statusBadge = colorFn(`[${task.status}]`);
|
|
62
|
-
const effort = formatEffort(task.effort);
|
|
63
|
-
const progress = formatSubtaskProgress(task.subtaskProgress);
|
|
64
|
-
const time = relativeTime(wt.createdAt);
|
|
65
|
-
console.log(` ${statusIcon} ${priorityIcon}${typeIcon} ${shortId(task.id)} ${task.title} ${statusBadge}${effort ? ` ${effort}` : ''}`);
|
|
66
|
-
const metaParts = [wt.branch];
|
|
67
|
-
if (time)
|
|
68
|
-
metaParts.push(time);
|
|
69
|
-
if (progress)
|
|
70
|
-
metaParts.push(progress);
|
|
71
|
-
console.log(` ${metaParts.join(` ${pc.dim('\u00B7')} `)}`);
|
|
72
|
-
}
|
|
73
|
-
catch {
|
|
74
|
-
console.log(` ${statusIcon} ${shortId(wt.taskId)} ${pc.dim('[unknown]')}`);
|
|
75
|
-
const time = relativeTime(wt.createdAt);
|
|
76
|
-
const metaParts = [wt.branch];
|
|
77
|
-
if (time)
|
|
78
|
-
metaParts.push(time);
|
|
79
|
-
console.log(` ${metaParts.join(` ${pc.dim('\u00B7')} `)}`);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
else {
|
|
83
|
-
console.log(` ${statusIcon} ${shortId(wt.taskId)}`);
|
|
84
|
-
const time = relativeTime(wt.createdAt);
|
|
85
|
-
const metaParts = [wt.branch];
|
|
86
|
-
if (time)
|
|
87
|
-
metaParts.push(time);
|
|
88
|
-
console.log(` ${metaParts.join(` ${pc.dim('\u00B7')} `)}`);
|
|
89
|
-
}
|
|
90
|
-
console.log();
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
console.log(pc.dim('Use `npx @damper/cli cleanup` to remove worktrees for completed tasks.\n'));
|
|
94
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import type { DamperApi } from './damper-api.js';
|
|
2
|
-
export interface BootstrapOptions {
|
|
3
|
-
api: DamperApi;
|
|
4
|
-
taskId: string;
|
|
5
|
-
worktreePath: string;
|
|
6
|
-
yolo?: boolean;
|
|
7
|
-
completionChecklist?: string[];
|
|
8
|
-
}
|
|
9
|
-
export interface BootstrapResult {
|
|
10
|
-
taskContextPath: string;
|
|
11
|
-
claudeMdUpdated: boolean;
|
|
12
|
-
}
|
|
13
|
-
export interface SectionBlockIndex {
|
|
14
|
-
section: string;
|
|
15
|
-
blocks: Array<{
|
|
16
|
-
id: string;
|
|
17
|
-
heading: string | null;
|
|
18
|
-
level: number;
|
|
19
|
-
charCount: number;
|
|
20
|
-
}>;
|
|
21
|
-
totalChars: number;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Fetch all relevant context from Damper and write local files
|
|
25
|
-
*/
|
|
26
|
-
export declare function bootstrapContext(options: BootstrapOptions): Promise<BootstrapResult>;
|
|
27
|
-
/**
|
|
28
|
-
* Refresh TASK_CONTEXT.md with latest data from Damper (for resume scenarios)
|
|
29
|
-
*/
|
|
30
|
-
export declare function refreshContext(options: BootstrapOptions): Promise<BootstrapResult>;
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import * as fs from 'node:fs';
|
|
2
|
-
import * as path from 'node:path';
|
|
3
|
-
import pc from 'picocolors';
|
|
4
|
-
import { generateTaskContext } from '../templates/TASK_CONTEXT.md.js';
|
|
5
|
-
import { generateClaudeAppend } from '../templates/CLAUDE_APPEND.md.js';
|
|
6
|
-
const TASK_CONTEXT_FILE = 'TASK_CONTEXT.md';
|
|
7
|
-
const CLAUDE_MD_FILE = 'CLAUDE.md';
|
|
8
|
-
const TASK_SECTION_MARKER = '## Current Task:';
|
|
9
|
-
/**
|
|
10
|
-
* Fetch block indices for relevant sections.
|
|
11
|
-
* Only fetches the headings/structure - no content.
|
|
12
|
-
* The agent will use get_section_block_content to load what it needs.
|
|
13
|
-
*/
|
|
14
|
-
async function fetchBlockIndices(api, relevantSections) {
|
|
15
|
-
const indices = [];
|
|
16
|
-
for (const sectionName of relevantSections) {
|
|
17
|
-
try {
|
|
18
|
-
const blockIndex = await api.getSectionBlocks(sectionName);
|
|
19
|
-
indices.push(blockIndex);
|
|
20
|
-
}
|
|
21
|
-
catch {
|
|
22
|
-
console.log(pc.dim(` Could not fetch blocks for: ${sectionName}`));
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return indices;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Fetch all relevant context from Damper and write local files
|
|
29
|
-
*/
|
|
30
|
-
export async function bootstrapContext(options) {
|
|
31
|
-
const { api, taskId, worktreePath } = options;
|
|
32
|
-
console.log(pc.dim('Fetching task details...'));
|
|
33
|
-
const task = await api.getTask(taskId);
|
|
34
|
-
console.log(pc.dim('Fetching project context...'));
|
|
35
|
-
const context = await api.getProjectContext(taskId);
|
|
36
|
-
// Fetch block indices (not content) for relevant sections
|
|
37
|
-
const relevantSections = context.relevantSections || [];
|
|
38
|
-
let blockIndices = [];
|
|
39
|
-
if (relevantSections.length > 0) {
|
|
40
|
-
console.log(pc.dim(`Fetching block indices for ${relevantSections.length} relevant sections...`));
|
|
41
|
-
blockIndices = await fetchBlockIndices(api, relevantSections);
|
|
42
|
-
}
|
|
43
|
-
// Fetch templates
|
|
44
|
-
console.log(pc.dim('Fetching templates...'));
|
|
45
|
-
const templatesResult = await api.listTemplates();
|
|
46
|
-
const templates = templatesResult.isEmpty ? [] : templatesResult.templates;
|
|
47
|
-
// Fetch modules
|
|
48
|
-
console.log(pc.dim('Fetching modules...'));
|
|
49
|
-
const modulesResult = await api.listModules();
|
|
50
|
-
const modules = modulesResult.isEmpty ? [] : modulesResult.modules;
|
|
51
|
-
// Get agent instructions
|
|
52
|
-
console.log(pc.dim('Fetching Damper workflow instructions...'));
|
|
53
|
-
const instructions = await api.getAgentInstructions('section');
|
|
54
|
-
// Generate TASK_CONTEXT.md
|
|
55
|
-
const taskContext = generateTaskContext({
|
|
56
|
-
task,
|
|
57
|
-
criticalRules: context.criticalRules || [],
|
|
58
|
-
completionChecklist: options.completionChecklist,
|
|
59
|
-
blockIndices,
|
|
60
|
-
templates,
|
|
61
|
-
modules,
|
|
62
|
-
damperInstructions: instructions.content,
|
|
63
|
-
});
|
|
64
|
-
// Write TASK_CONTEXT.md
|
|
65
|
-
const taskContextPath = path.join(worktreePath, TASK_CONTEXT_FILE);
|
|
66
|
-
console.log(pc.dim(`Writing ${TASK_CONTEXT_FILE}...`));
|
|
67
|
-
await fs.promises.writeFile(taskContextPath, taskContext, 'utf-8');
|
|
68
|
-
// Update CLAUDE.md with task section
|
|
69
|
-
const claudeMdPath = path.join(worktreePath, CLAUDE_MD_FILE);
|
|
70
|
-
let claudeMdUpdated = false;
|
|
71
|
-
if (fs.existsSync(claudeMdPath)) {
|
|
72
|
-
console.log(pc.dim(`Updating ${CLAUDE_MD_FILE}...`));
|
|
73
|
-
let claudeMd = await fs.promises.readFile(claudeMdPath, 'utf-8');
|
|
74
|
-
// Remove any existing task section
|
|
75
|
-
const markerIndex = claudeMd.indexOf(TASK_SECTION_MARKER);
|
|
76
|
-
if (markerIndex !== -1) {
|
|
77
|
-
claudeMd = claudeMd.slice(0, markerIndex).trimEnd();
|
|
78
|
-
}
|
|
79
|
-
// Append new task section
|
|
80
|
-
const taskSection = generateClaudeAppend({
|
|
81
|
-
taskId,
|
|
82
|
-
taskTitle: task.title,
|
|
83
|
-
yolo: options.yolo,
|
|
84
|
-
});
|
|
85
|
-
claudeMd = `${claudeMd}\n\n${taskSection}\n`;
|
|
86
|
-
await fs.promises.writeFile(claudeMdPath, claudeMd, 'utf-8');
|
|
87
|
-
claudeMdUpdated = true;
|
|
88
|
-
}
|
|
89
|
-
return {
|
|
90
|
-
taskContextPath,
|
|
91
|
-
claudeMdUpdated,
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Refresh TASK_CONTEXT.md with latest data from Damper (for resume scenarios)
|
|
96
|
-
*/
|
|
97
|
-
export async function refreshContext(options) {
|
|
98
|
-
// For now, just run bootstrap again - it will overwrite with fresh data
|
|
99
|
-
return bootstrapContext(options);
|
|
100
|
-
}
|
package/dist/services/state.d.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
export interface WorktreeState {
|
|
2
|
-
taskId: string;
|
|
3
|
-
path: string;
|
|
4
|
-
branch: string;
|
|
5
|
-
projectRoot: string;
|
|
6
|
-
createdAt: string;
|
|
7
|
-
}
|
|
8
|
-
export declare function getWorktrees(): WorktreeState[];
|
|
9
|
-
export declare function getWorktreeByTaskId(taskId: string): WorktreeState | undefined;
|
|
10
|
-
export declare function getWorktreeByPath(worktreePath: string): WorktreeState | undefined;
|
|
11
|
-
export declare function addWorktree(worktree: WorktreeState): void;
|
|
12
|
-
export declare function removeWorktree(taskId: string): void;
|
|
13
|
-
export declare function removeWorktreeByPath(worktreePath: string): void;
|
|
14
|
-
export declare function cleanupStaleWorktrees(): WorktreeState[];
|
|
15
|
-
export declare function getWorktreesForProject(projectRoot: string): WorktreeState[];
|
|
16
|
-
interface Preferences {
|
|
17
|
-
tmuxPromptDismissed?: boolean;
|
|
18
|
-
[key: string]: unknown;
|
|
19
|
-
}
|
|
20
|
-
export declare function getPreference<K extends keyof Preferences>(key: K): Preferences[K];
|
|
21
|
-
export declare function setPreference<K extends keyof Preferences>(key: K, value: Preferences[K]): void;
|
|
22
|
-
export {};
|
package/dist/services/state.js
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import * as fs from 'node:fs';
|
|
2
|
-
import * as path from 'node:path';
|
|
3
|
-
import * as os from 'node:os';
|
|
4
|
-
const STATE_DIR = path.join(os.homedir(), '.damper');
|
|
5
|
-
const WORKTREES_FILE = path.join(STATE_DIR, 'worktrees.json');
|
|
6
|
-
function ensureStateDir() {
|
|
7
|
-
if (!fs.existsSync(STATE_DIR)) {
|
|
8
|
-
fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
function readState() {
|
|
12
|
-
ensureStateDir();
|
|
13
|
-
if (!fs.existsSync(WORKTREES_FILE)) {
|
|
14
|
-
return { worktrees: [] };
|
|
15
|
-
}
|
|
16
|
-
try {
|
|
17
|
-
const content = fs.readFileSync(WORKTREES_FILE, 'utf-8');
|
|
18
|
-
return JSON.parse(content);
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
return { worktrees: [] };
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
function writeState(state) {
|
|
25
|
-
ensureStateDir();
|
|
26
|
-
fs.writeFileSync(WORKTREES_FILE, JSON.stringify(state, null, 2));
|
|
27
|
-
}
|
|
28
|
-
export function getWorktrees() {
|
|
29
|
-
return readState().worktrees;
|
|
30
|
-
}
|
|
31
|
-
export function getWorktreeByTaskId(taskId) {
|
|
32
|
-
return readState().worktrees.find(w => w.taskId === taskId);
|
|
33
|
-
}
|
|
34
|
-
export function getWorktreeByPath(worktreePath) {
|
|
35
|
-
const normalized = path.resolve(worktreePath);
|
|
36
|
-
return readState().worktrees.find(w => path.resolve(w.path) === normalized);
|
|
37
|
-
}
|
|
38
|
-
export function addWorktree(worktree) {
|
|
39
|
-
const state = readState();
|
|
40
|
-
// Remove any existing entry for this task or path
|
|
41
|
-
state.worktrees = state.worktrees.filter(w => w.taskId !== worktree.taskId && path.resolve(w.path) !== path.resolve(worktree.path));
|
|
42
|
-
state.worktrees.push(worktree);
|
|
43
|
-
writeState(state);
|
|
44
|
-
}
|
|
45
|
-
export function removeWorktree(taskId) {
|
|
46
|
-
const state = readState();
|
|
47
|
-
state.worktrees = state.worktrees.filter(w => w.taskId !== taskId);
|
|
48
|
-
writeState(state);
|
|
49
|
-
}
|
|
50
|
-
export function removeWorktreeByPath(worktreePath) {
|
|
51
|
-
const normalized = path.resolve(worktreePath);
|
|
52
|
-
const state = readState();
|
|
53
|
-
state.worktrees = state.worktrees.filter(w => path.resolve(w.path) !== normalized);
|
|
54
|
-
writeState(state);
|
|
55
|
-
}
|
|
56
|
-
export function cleanupStaleWorktrees() {
|
|
57
|
-
const state = readState();
|
|
58
|
-
const validWorktrees = [];
|
|
59
|
-
const staleWorktrees = [];
|
|
60
|
-
for (const w of state.worktrees) {
|
|
61
|
-
if (fs.existsSync(w.path)) {
|
|
62
|
-
validWorktrees.push(w);
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
65
|
-
staleWorktrees.push(w);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
if (staleWorktrees.length > 0) {
|
|
69
|
-
state.worktrees = validWorktrees;
|
|
70
|
-
writeState(state);
|
|
71
|
-
}
|
|
72
|
-
return staleWorktrees;
|
|
73
|
-
}
|
|
74
|
-
export function getWorktreesForProject(projectRoot) {
|
|
75
|
-
const normalized = path.resolve(projectRoot);
|
|
76
|
-
return readState().worktrees.filter(w => path.resolve(w.projectRoot) === normalized);
|
|
77
|
-
}
|
|
78
|
-
// ── Preferences (persistent user choices) ──
|
|
79
|
-
const PREFS_FILE = path.join(STATE_DIR, 'preferences.json');
|
|
80
|
-
function readPrefs() {
|
|
81
|
-
ensureStateDir();
|
|
82
|
-
if (!fs.existsSync(PREFS_FILE))
|
|
83
|
-
return {};
|
|
84
|
-
try {
|
|
85
|
-
return JSON.parse(fs.readFileSync(PREFS_FILE, 'utf-8'));
|
|
86
|
-
}
|
|
87
|
-
catch {
|
|
88
|
-
return {};
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
function writePrefs(prefs) {
|
|
92
|
-
ensureStateDir();
|
|
93
|
-
fs.writeFileSync(PREFS_FILE, JSON.stringify(prefs, null, 2));
|
|
94
|
-
}
|
|
95
|
-
export function getPreference(key) {
|
|
96
|
-
return readPrefs()[key];
|
|
97
|
-
}
|
|
98
|
-
export function setPreference(key, value) {
|
|
99
|
-
const prefs = readPrefs();
|
|
100
|
-
prefs[key] = value;
|
|
101
|
-
writePrefs(prefs);
|
|
102
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
export interface WorktreeOptions {
|
|
2
|
-
taskId: string;
|
|
3
|
-
taskTitle: string;
|
|
4
|
-
projectRoot: string;
|
|
5
|
-
apiKey: string;
|
|
6
|
-
}
|
|
7
|
-
export interface WorktreeResult {
|
|
8
|
-
path: string;
|
|
9
|
-
branch: string;
|
|
10
|
-
isNew: boolean;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Create a new git worktree for a task
|
|
14
|
-
*/
|
|
15
|
-
export declare function createWorktree(options: WorktreeOptions): Promise<WorktreeResult>;
|
|
16
|
-
/**
|
|
17
|
-
* Remove a git worktree and its branch
|
|
18
|
-
*/
|
|
19
|
-
export declare function removeWorktreeDir(worktreePath: string, projectRoot: string): Promise<void>;
|
|
20
|
-
/**
|
|
21
|
-
* List all git worktrees for a project
|
|
22
|
-
*/
|
|
23
|
-
export declare function listWorktrees(projectRoot: string): Promise<Array<{
|
|
24
|
-
path: string;
|
|
25
|
-
branch: string;
|
|
26
|
-
head: string;
|
|
27
|
-
}>>;
|
|
28
|
-
/**
|
|
29
|
-
* Check if a path is inside a git worktree
|
|
30
|
-
*/
|
|
31
|
-
export declare function isInWorktree(dir: string): Promise<boolean>;
|
|
32
|
-
/**
|
|
33
|
-
* Get the git root directory
|
|
34
|
-
*/
|
|
35
|
-
export declare function getGitRoot(dir: string): Promise<string>;
|
|
36
|
-
/**
|
|
37
|
-
* Get the main project root (not a worktree).
|
|
38
|
-
* If we're in a worktree, this returns the main repo's path.
|
|
39
|
-
*/
|
|
40
|
-
export declare function getMainProjectRoot(dir: string): Promise<string>;
|