@damper/cli 0.9.19 → 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.
@@ -1,469 +0,0 @@
1
- import * as fs from 'node:fs';
2
- import * as path from 'node:path';
3
- import { execa } from 'execa';
4
- import pc from 'picocolors';
5
- import { addWorktree, removeWorktree, getWorktreeByTaskId } from './state.js';
6
- /**
7
- * Convert a task title to a safe slug for branch/directory names
8
- */
9
- function slugify(text) {
10
- return text
11
- .toLowerCase()
12
- .replace(/[^a-z0-9]+/g, '-')
13
- .replace(/^-+|-+$/g, '')
14
- .slice(0, 50);
15
- }
16
- /**
17
- * Get the project name from package.json or directory name
18
- */
19
- function getProjectName(projectRoot) {
20
- const packageJsonPath = path.join(projectRoot, 'package.json');
21
- if (fs.existsSync(packageJsonPath)) {
22
- try {
23
- const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
24
- if (pkg.name) {
25
- // Strip scope if present
26
- return pkg.name.replace(/^@[^/]+\//, '');
27
- }
28
- }
29
- catch {
30
- // Fall through to directory name
31
- }
32
- }
33
- return path.basename(projectRoot);
34
- }
35
- /**
36
- * Detect monorepo structure by looking for workspaces in package.json
37
- */
38
- function detectMonorepoPackages(projectRoot) {
39
- const packageJsonPath = path.join(projectRoot, 'package.json');
40
- if (!fs.existsSync(packageJsonPath)) {
41
- return [];
42
- }
43
- try {
44
- const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
45
- const workspaces = pkg.workspaces || [];
46
- const packages = [];
47
- for (const pattern of workspaces) {
48
- // Simple glob expansion for common patterns
49
- if (pattern.includes('*')) {
50
- const base = pattern.replace(/\/\*$/, '').replace(/\*$/, '');
51
- const basePath = path.join(projectRoot, base);
52
- if (fs.existsSync(basePath) && fs.statSync(basePath).isDirectory()) {
53
- const dirs = fs.readdirSync(basePath).filter(d => {
54
- const fullPath = path.join(basePath, d);
55
- return fs.statSync(fullPath).isDirectory() &&
56
- fs.existsSync(path.join(fullPath, 'package.json'));
57
- });
58
- packages.push(...dirs.map(d => path.join(base, d)));
59
- }
60
- }
61
- else {
62
- // Direct path
63
- const pkgPath = path.join(projectRoot, pattern);
64
- if (fs.existsSync(path.join(pkgPath, 'package.json'))) {
65
- packages.push(pattern);
66
- }
67
- }
68
- }
69
- return packages;
70
- }
71
- catch {
72
- return [];
73
- }
74
- }
75
- /**
76
- * Find all node_modules directories to symlink
77
- */
78
- function findNodeModulesDirs(projectRoot) {
79
- const dirs = [];
80
- // Root node_modules
81
- const rootModules = path.join(projectRoot, 'node_modules');
82
- if (fs.existsSync(rootModules)) {
83
- dirs.push('node_modules');
84
- }
85
- // Monorepo package node_modules
86
- const packages = detectMonorepoPackages(projectRoot);
87
- for (const pkg of packages) {
88
- const pkgModules = path.join(projectRoot, pkg, 'node_modules');
89
- if (fs.existsSync(pkgModules)) {
90
- dirs.push(path.join(pkg, 'node_modules'));
91
- }
92
- }
93
- return dirs;
94
- }
95
- /**
96
- * Find all .env files to copy
97
- */
98
- function findEnvFiles(projectRoot) {
99
- const envFiles = [];
100
- // Root .env files
101
- const rootEnvFiles = ['.env', '.env.local', '.env.development'];
102
- for (const envFile of rootEnvFiles) {
103
- if (fs.existsSync(path.join(projectRoot, envFile))) {
104
- envFiles.push(envFile);
105
- }
106
- }
107
- // Monorepo package .env files
108
- const packages = detectMonorepoPackages(projectRoot);
109
- for (const pkg of packages) {
110
- for (const envFile of rootEnvFiles) {
111
- const envPath = path.join(pkg, envFile);
112
- if (fs.existsSync(path.join(projectRoot, envPath))) {
113
- envFiles.push(envPath);
114
- }
115
- }
116
- }
117
- // Common server locations
118
- const serverDirs = ['server', 'api', 'backend', 'packages/server'];
119
- for (const serverDir of serverDirs) {
120
- for (const envFile of rootEnvFiles) {
121
- const envPath = path.join(serverDir, envFile);
122
- if (fs.existsSync(path.join(projectRoot, envPath)) && !envFiles.includes(envPath)) {
123
- envFiles.push(envPath);
124
- }
125
- }
126
- }
127
- return envFiles;
128
- }
129
- /**
130
- * Check if a git branch exists
131
- */
132
- async function branchExists(branchName, projectRoot) {
133
- try {
134
- await execa('git', ['rev-parse', '--verify', branchName], {
135
- cwd: projectRoot,
136
- stdio: 'pipe',
137
- });
138
- return true;
139
- }
140
- catch {
141
- return false;
142
- }
143
- }
144
- /**
145
- * Create a new git worktree for a task
146
- */
147
- export async function createWorktree(options) {
148
- const { taskId, taskTitle, projectRoot } = options;
149
- // Check if worktree already exists for this task
150
- const existing = getWorktreeByTaskId(taskId);
151
- if (existing && fs.existsSync(existing.path)) {
152
- return {
153
- path: existing.path,
154
- branch: existing.branch,
155
- isNew: false,
156
- };
157
- }
158
- const shortTaskId = taskId.slice(0, 8);
159
- const slug = slugify(taskTitle);
160
- // Put worktrees in .worktrees folder inside the project
161
- const worktreesDir = path.join(projectRoot, '.worktrees');
162
- const worktreePath = path.join(worktreesDir, `${shortTaskId}-${slug}`);
163
- const branchName = `feature/${slug}`;
164
- // Ensure .worktrees is gitignored
165
- const gitignorePath = path.join(projectRoot, '.gitignore');
166
- if (fs.existsSync(gitignorePath)) {
167
- const gitignore = fs.readFileSync(gitignorePath, 'utf-8');
168
- if (!gitignore.includes('.worktrees')) {
169
- fs.appendFileSync(gitignorePath, '\n# Damper CLI worktrees\n.worktrees/\n');
170
- console.log(pc.dim('Added .worktrees/ to .gitignore'));
171
- }
172
- }
173
- else {
174
- fs.writeFileSync(gitignorePath, '# Damper CLI worktrees\n.worktrees/\n');
175
- console.log(pc.dim('Created .gitignore with .worktrees/'));
176
- }
177
- // Check if worktree directory already exists
178
- if (fs.existsSync(worktreePath)) {
179
- console.log(pc.dim(`Worktree directory already exists at ${worktreePath}`));
180
- // Check if it's a valid worktree and has the expected branch
181
- try {
182
- const { stdout } = await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
183
- cwd: worktreePath,
184
- stdio: 'pipe',
185
- });
186
- const currentBranch = stdout.trim();
187
- console.log(pc.dim(`Using existing worktree on branch ${currentBranch}`));
188
- // Save to state if not tracked
189
- const worktreeState = {
190
- taskId,
191
- path: worktreePath,
192
- branch: currentBranch,
193
- projectRoot,
194
- createdAt: new Date().toISOString(),
195
- };
196
- addWorktree(worktreeState);
197
- return {
198
- path: worktreePath,
199
- branch: currentBranch,
200
- isNew: false,
201
- };
202
- }
203
- catch {
204
- // Not a valid git worktree, remove it
205
- console.log(pc.dim(`Removing invalid directory at ${worktreePath}...`));
206
- fs.rmSync(worktreePath, { recursive: true });
207
- }
208
- }
209
- // Check if branch already exists
210
- const branchAlreadyExists = await branchExists(branchName, projectRoot);
211
- // Create worktree
212
- console.log(pc.dim(`Creating worktree at ${worktreePath}...`));
213
- if (branchAlreadyExists) {
214
- // Use existing branch
215
- console.log(pc.dim(`Using existing branch ${branchName}`));
216
- await execa('git', ['worktree', 'add', worktreePath, branchName], {
217
- cwd: projectRoot,
218
- stdio: 'pipe',
219
- });
220
- }
221
- else {
222
- // Create new branch
223
- await execa('git', ['worktree', 'add', worktreePath, '-b', branchName], {
224
- cwd: projectRoot,
225
- stdio: 'pipe',
226
- });
227
- }
228
- // Symlink node_modules
229
- const nodeModulesDirs = findNodeModulesDirs(projectRoot);
230
- for (const dir of nodeModulesDirs) {
231
- const source = path.join(projectRoot, dir);
232
- const target = path.join(worktreePath, dir);
233
- // Ensure parent directory exists
234
- const targetDir = path.dirname(target);
235
- if (!fs.existsSync(targetDir)) {
236
- fs.mkdirSync(targetDir, { recursive: true });
237
- }
238
- console.log(pc.dim(`Linking ${dir}...`));
239
- await fs.promises.symlink(source, target, 'junction');
240
- }
241
- // Copy .env files
242
- const envFiles = findEnvFiles(projectRoot);
243
- if (envFiles.length > 0) {
244
- console.log(pc.dim(`Copying ${envFiles.length} .env file(s) to worktree...`));
245
- for (const envFile of envFiles) {
246
- const source = path.join(projectRoot, envFile);
247
- const target = path.join(worktreePath, envFile);
248
- // Ensure parent directory exists
249
- const targetDir = path.dirname(target);
250
- if (!fs.existsSync(targetDir)) {
251
- fs.mkdirSync(targetDir, { recursive: true });
252
- }
253
- console.log(pc.dim(` ${envFile}`));
254
- await fs.promises.copyFile(source, target);
255
- }
256
- }
257
- // Create .mcp.json with Damper MCP configured (project-level MCP config)
258
- const mcpConfig = {
259
- mcpServers: {
260
- damper: {
261
- command: 'npx',
262
- args: ['-y', '@damper/mcp'],
263
- env: {
264
- DAMPER_API_KEY: options.apiKey,
265
- },
266
- },
267
- },
268
- };
269
- fs.writeFileSync(path.join(worktreePath, '.mcp.json'), JSON.stringify(mcpConfig, null, 2));
270
- console.log(pc.dim('Created .mcp.json with Damper MCP'));
271
- // Create .claude/settings.local.json - copy root permissions and ensure Damper MCP is allowed
272
- const claudeDir = path.join(worktreePath, '.claude');
273
- if (!fs.existsSync(claudeDir)) {
274
- fs.mkdirSync(claudeDir, { recursive: true });
275
- }
276
- // Read existing settings from root project
277
- const rootSettingsPath = path.join(projectRoot, '.claude', 'settings.local.json');
278
- let claudeSettings = {};
279
- if (fs.existsSync(rootSettingsPath)) {
280
- try {
281
- claudeSettings = JSON.parse(fs.readFileSync(rootSettingsPath, 'utf-8'));
282
- }
283
- catch { }
284
- }
285
- // Ensure permissions.allow includes damper MCP
286
- const permissions = (claudeSettings.permissions ?? {});
287
- const allow = Array.isArray(permissions.allow) ? [...permissions.allow] : [];
288
- if (!allow.includes('mcp__damper__*')) {
289
- allow.unshift('mcp__damper__*');
290
- }
291
- claudeSettings.permissions = { ...permissions, allow };
292
- fs.writeFileSync(path.join(claudeDir, 'settings.local.json'), JSON.stringify(claudeSettings, null, 2));
293
- console.log(pc.dim('Created .claude/settings.local.json with root permissions + MCP'));
294
- // Save to state
295
- const worktreeState = {
296
- taskId,
297
- path: worktreePath,
298
- branch: branchName,
299
- projectRoot,
300
- createdAt: new Date().toISOString(),
301
- };
302
- addWorktree(worktreeState);
303
- return {
304
- path: worktreePath,
305
- branch: branchName,
306
- isNew: true,
307
- };
308
- }
309
- /**
310
- * Remove a git worktree and its branch
311
- */
312
- export async function removeWorktreeDir(worktreePath, projectRoot) {
313
- console.log(pc.dim(`Removing worktree at ${worktreePath}...`));
314
- // Get branch name before removing worktree
315
- let branchName;
316
- try {
317
- const { stdout } = await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
318
- cwd: worktreePath,
319
- stdio: 'pipe',
320
- });
321
- branchName = stdout.trim();
322
- }
323
- catch {
324
- // Ignore - might not be able to get branch
325
- }
326
- // Remove symlinks first to avoid issues
327
- const nodeModulesDirs = findNodeModulesDirs(projectRoot);
328
- for (const dir of nodeModulesDirs) {
329
- const target = path.join(worktreePath, dir);
330
- if (fs.existsSync(target)) {
331
- try {
332
- await fs.promises.unlink(target);
333
- }
334
- catch {
335
- // Ignore errors
336
- }
337
- }
338
- }
339
- // Remove worktree
340
- await execa('git', ['worktree', 'remove', worktreePath, '--force'], {
341
- cwd: projectRoot,
342
- stdio: 'pipe',
343
- });
344
- // Delete the branch if it's a feature branch
345
- if (branchName && branchName.startsWith('feature/')) {
346
- try {
347
- await execa('git', ['branch', '-d', branchName], {
348
- cwd: projectRoot,
349
- stdio: 'pipe',
350
- });
351
- console.log(pc.dim(`Deleted branch: ${branchName}`));
352
- }
353
- catch {
354
- // Branch might not be fully merged - try force delete
355
- try {
356
- await execa('git', ['branch', '-D', branchName], {
357
- cwd: projectRoot,
358
- stdio: 'pipe',
359
- });
360
- console.log(pc.dim(`Force deleted branch: ${branchName}`));
361
- }
362
- catch {
363
- console.log(pc.yellow(`Could not delete branch: ${branchName}`));
364
- }
365
- }
366
- }
367
- // Get the worktree state to find the task ID
368
- const state = await import('./state.js');
369
- const worktree = state.getWorktreeByPath(worktreePath);
370
- if (worktree) {
371
- removeWorktree(worktree.taskId);
372
- }
373
- }
374
- /**
375
- * List all git worktrees for a project
376
- */
377
- export async function listWorktrees(projectRoot) {
378
- const { stdout } = await execa('git', ['worktree', 'list', '--porcelain'], {
379
- cwd: projectRoot,
380
- stdio: 'pipe',
381
- });
382
- const worktrees = [];
383
- let current = {};
384
- for (const line of stdout.split('\n')) {
385
- if (line.startsWith('worktree ')) {
386
- current.path = line.slice(9);
387
- }
388
- else if (line.startsWith('HEAD ')) {
389
- current.head = line.slice(5);
390
- }
391
- else if (line.startsWith('branch ')) {
392
- current.branch = line.slice(7);
393
- }
394
- else if (line === '' && current.path) {
395
- if (current.path && current.branch && current.head) {
396
- worktrees.push({
397
- path: current.path,
398
- branch: current.branch,
399
- head: current.head,
400
- });
401
- }
402
- current = {};
403
- }
404
- }
405
- // Handle last entry
406
- if (current.path && current.branch && current.head) {
407
- worktrees.push({
408
- path: current.path,
409
- branch: current.branch,
410
- head: current.head,
411
- });
412
- }
413
- return worktrees;
414
- }
415
- /**
416
- * Check if a path is inside a git worktree
417
- */
418
- export async function isInWorktree(dir) {
419
- try {
420
- const { stdout } = await execa('git', ['rev-parse', '--is-inside-work-tree'], {
421
- cwd: dir,
422
- stdio: 'pipe',
423
- });
424
- return stdout.trim() === 'true';
425
- }
426
- catch {
427
- return false;
428
- }
429
- }
430
- /**
431
- * Get the git root directory
432
- */
433
- export async function getGitRoot(dir) {
434
- const { stdout } = await execa('git', ['rev-parse', '--show-toplevel'], {
435
- cwd: dir,
436
- stdio: 'pipe',
437
- });
438
- return stdout.trim();
439
- }
440
- /**
441
- * Get the main project root (not a worktree).
442
- * If we're in a worktree, this returns the main repo's path.
443
- */
444
- export async function getMainProjectRoot(dir) {
445
- // Get the common git directory (shared by all worktrees)
446
- const { stdout: gitCommonDir } = await execa('git', ['rev-parse', '--git-common-dir'], {
447
- cwd: dir,
448
- stdio: 'pipe',
449
- });
450
- const commonDir = gitCommonDir.trim();
451
- // If it's just '.git', we're in the main repo
452
- if (commonDir === '.git') {
453
- return getGitRoot(dir);
454
- }
455
- // Otherwise, the common dir is something like '/path/to/main-repo/.git'
456
- // Strip the '/.git' to get the main repo path
457
- if (commonDir.endsWith('/.git')) {
458
- return commonDir.slice(0, -5);
459
- }
460
- // Fallback: resolve relative path
461
- const gitRoot = await getGitRoot(dir);
462
- const resolvedCommon = path.resolve(gitRoot, commonDir);
463
- // Strip .git suffix
464
- if (resolvedCommon.endsWith('.git')) {
465
- return resolvedCommon.slice(0, -4);
466
- }
467
- // Last resort: just return git root
468
- return gitRoot;
469
- }
@@ -1,7 +0,0 @@
1
- interface ClaudeAppendOptions {
2
- taskId: string;
3
- taskTitle: string;
4
- yolo?: boolean;
5
- }
6
- export declare function generateClaudeAppend(options: ClaudeAppendOptions): string;
7
- export {};
@@ -1,35 +0,0 @@
1
- export function generateClaudeAppend(options) {
2
- const { taskId, taskTitle, yolo } = options;
3
- const planSection = yolo
4
- ? `
5
- **MANDATORY: Plan First, Then Execute**
6
- Before making ANY code changes, you MUST enter plan mode using the EnterPlanMode tool.
7
- Read TASK_CONTEXT.md first, then create a plan. Once the plan is ready, execute it without waiting for user approval.
8
- `
9
- : `
10
- **MANDATORY: Plan Mode**
11
- Before making ANY code changes, you MUST enter plan mode using the EnterPlanMode tool.
12
- Read TASK_CONTEXT.md first, then create a plan for user approval. Do NOT write code until the plan is approved.
13
- `;
14
- return `
15
- ## Current Task: #${taskId} ${taskTitle}
16
-
17
- **IMPORTANT**: Read TASK_CONTEXT.md for full task details and architecture context.
18
- If you feel you've lost context, re-read that file.
19
- ${planSection}
20
- **NEVER commit these files** (they are generated by the CLI and gitignored):
21
- - \`CLAUDE.md\` changes (this task section is temporary)
22
- - \`TASK_CONTEXT.md\`
23
- - \`.mcp.json\`
24
- - \`.claude/settings.local.json\`
25
-
26
- **Your responsibilities (via Damper MCP):**
27
- 1. **Do NOT commit or complete tasks without explicit user confirmation** - Always ask the user before running \`git commit\` or calling \`complete_task\`
28
- 2. Use \`add_commit\` after each git commit
29
- 3. Use \`add_note\` ONLY for non-obvious approach decisions (e.g. "Decision: chose X because Y")
30
- 4. When user confirms: call \`complete_task\` with summary and \`reviewInstructions\` (what to test/verify)
31
- 5. If stopping early: call \`abandon_task\` with what remains and blockers
32
-
33
- The CLI just bootstrapped this environment - YOU handle the task lifecycle.
34
- `.trim();
35
- }
@@ -1,17 +0,0 @@
1
- import type { TaskDetail, Module } from '../services/damper-api.js';
2
- import type { SectionBlockIndex } from '../services/context-bootstrap.js';
3
- interface TaskContextOptions {
4
- task: TaskDetail;
5
- criticalRules: string[];
6
- completionChecklist?: string[];
7
- blockIndices: SectionBlockIndex[];
8
- templates: Array<{
9
- name: string;
10
- description?: string | null;
11
- filePattern?: string | null;
12
- }>;
13
- modules: Module[];
14
- damperInstructions: string;
15
- }
16
- export declare function generateTaskContext(options: TaskContextOptions): string;
17
- export {};
@@ -1,149 +0,0 @@
1
- export function generateTaskContext(options) {
2
- const { task, criticalRules, completionChecklist, blockIndices, templates, modules, damperInstructions } = options;
3
- const typeIcon = task.type === 'bug' ? 'Bug' : task.type === 'feature' ? 'Feature' : task.type === 'improvement' ? 'Improvement' : 'Task';
4
- const lines = [];
5
- // Header
6
- lines.push(`# Task: ${task.title} (#${task.id})`);
7
- lines.push('');
8
- lines.push(`**Type:** ${typeIcon} | **Status:** ${task.status} | **Priority:** ${task.priority || 'none'}`);
9
- if (task.effort)
10
- lines.push(`**Effort:** ${task.effort}`);
11
- if (task.quarter)
12
- lines.push(`**Quarter:** ${task.quarter}`);
13
- lines.push('');
14
- // Critical Rules
15
- if (criticalRules.length > 0) {
16
- lines.push('## Critical Rules (DO NOT SKIP)');
17
- lines.push('');
18
- for (const rule of criticalRules) {
19
- lines.push(`- ${rule}`);
20
- }
21
- lines.push('');
22
- }
23
- // Completion Checklist
24
- if (completionChecklist && completionChecklist.length > 0) {
25
- lines.push('## Completion Checklist (required for complete_task)');
26
- lines.push('');
27
- lines.push('You MUST verify each item and pass them as `confirmations` when calling `complete_task`:');
28
- lines.push('');
29
- for (const item of completionChecklist) {
30
- lines.push(`- [ ] ${item}`);
31
- }
32
- lines.push('');
33
- }
34
- // Damper Workflow
35
- lines.push('## Damper Workflow');
36
- lines.push('');
37
- lines.push(damperInstructions);
38
- lines.push('');
39
- // CLI & Tooling Info
40
- lines.push('## About This Session');
41
- lines.push('');
42
- lines.push('You were launched by `@damper/cli` (npm package). The CLI:');
43
- lines.push('- Created this git worktree for isolated work');
44
- lines.push('- Generated this TASK_CONTEXT.md from Damper API');
45
- lines.push('- Configured Damper MCP tools for task lifecycle');
46
- lines.push('');
47
- lines.push('**Cleanup:** Ask the user for confirmation before committing or completing the task.');
48
- lines.push('Once confirmed, call `complete_task` or `abandon_task`. The user will run');
49
- lines.push('`npx @damper/cli cleanup` to remove the worktree and branch - you don\'t need to');
50
- lines.push('provide manual cleanup instructions.');
51
- lines.push('');
52
- lines.push('**Help improve the tooling:** If you encounter friction, bugs, or have ideas,');
53
- lines.push('use `report_issue` to log them. Your feedback improves tooling for all future tasks.');
54
- lines.push('');
55
- // Task Description
56
- if (task.description) {
57
- lines.push('## Task Description');
58
- lines.push('');
59
- lines.push(task.description);
60
- lines.push('');
61
- }
62
- // Implementation Plan
63
- if (task.implementationPlan) {
64
- lines.push('## Implementation Plan');
65
- lines.push('');
66
- lines.push(task.implementationPlan);
67
- lines.push('');
68
- }
69
- // Subtasks
70
- if (task.subtasks && task.subtasks.length > 0) {
71
- const done = task.subtasks.filter(s => s.done).length;
72
- lines.push(`## Subtasks (${done}/${task.subtasks.length})`);
73
- lines.push('');
74
- for (const subtask of task.subtasks) {
75
- lines.push(`- [${subtask.done ? 'x' : ' '}] ${subtask.title} (id: ${subtask.id})`);
76
- }
77
- lines.push('');
78
- }
79
- // Previous Session Notes
80
- if (task.agentNotes) {
81
- lines.push('## Previous Notes');
82
- lines.push('');
83
- lines.push(task.agentNotes);
84
- lines.push('');
85
- }
86
- // Previous Commits
87
- if (task.commits && task.commits.length > 0) {
88
- lines.push(`## Previous Commits (${task.commits.length})`);
89
- lines.push('');
90
- for (const commit of task.commits) {
91
- lines.push(`- ${commit.hash.slice(0, 7)}: ${commit.message}`);
92
- }
93
- lines.push('');
94
- }
95
- // Project Context Block Index (load-on-demand)
96
- if (blockIndices.length > 0) {
97
- lines.push('## Available Architecture Context');
98
- lines.push('');
99
- lines.push('**BEFORE starting work**, load the blocks relevant to your task using the MCP tool:');
100
- lines.push('`get_section_block_content(section, blockIds)` — pass the section name and an array of block IDs from the list below.');
101
- lines.push('Only load what you need to keep token usage low.');
102
- lines.push('');
103
- for (const index of blockIndices) {
104
- lines.push(`### ${index.section} (${index.totalChars} chars)`);
105
- lines.push('');
106
- for (const block of index.blocks) {
107
- const heading = block.heading ? block.heading.replace(/^#+\s*/, '') : '(intro)';
108
- lines.push(`- \`${block.id}\`: ${heading} (${block.charCount} chars)`);
109
- }
110
- lines.push('');
111
- }
112
- }
113
- // Templates
114
- if (templates.length > 0) {
115
- lines.push('## Templates Available');
116
- lines.push('');
117
- for (const template of templates) {
118
- const pattern = template.filePattern ? ` (${template.filePattern})` : '';
119
- const desc = template.description ? `: ${template.description}` : '';
120
- lines.push(`- \`${template.name}\`${pattern}${desc}`);
121
- }
122
- lines.push('');
123
- }
124
- // Modules
125
- if (modules.length > 0) {
126
- lines.push('## Module Structure');
127
- lines.push('');
128
- for (const mod of modules) {
129
- const port = mod.port ? ` (port ${mod.port})` : '';
130
- const deps = mod.dependsOn && mod.dependsOn.length > 0 ? ` → ${mod.dependsOn.join(', ')}` : '';
131
- lines.push(`- **${mod.name}**: ${mod.path}${port}${deps}`);
132
- }
133
- lines.push('');
134
- }
135
- // Linked Feedback
136
- if (task.feedback && task.feedback.length > 0) {
137
- lines.push('## Linked Feedback (customer requests driving this task)');
138
- lines.push('');
139
- for (const fb of task.feedback) {
140
- lines.push(`- "${fb.title}" (${fb.voterCount} votes)`);
141
- }
142
- lines.push('');
143
- }
144
- // Footer
145
- lines.push('---');
146
- lines.push(`Generated by @damper/cli at ${new Date().toISOString()}.`);
147
- lines.push('Re-read this file if conversation context is lost.');
148
- return lines.join('\n');
149
- }