@madkid/relay-ctx 0.1.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.
Files changed (45) hide show
  1. package/dist/commands/checkpoint.d.ts +18 -0
  2. package/dist/commands/checkpoint.d.ts.map +1 -0
  3. package/dist/commands/checkpoint.js +130 -0
  4. package/dist/commands/checkpoint.js.map +1 -0
  5. package/dist/commands/hooks.d.ts +36 -0
  6. package/dist/commands/hooks.d.ts.map +1 -0
  7. package/dist/commands/hooks.js +226 -0
  8. package/dist/commands/hooks.js.map +1 -0
  9. package/dist/commands/init.d.ts +14 -0
  10. package/dist/commands/init.d.ts.map +1 -0
  11. package/dist/commands/init.js +228 -0
  12. package/dist/commands/init.js.map +1 -0
  13. package/dist/commands/inject.d.ts +20 -0
  14. package/dist/commands/inject.d.ts.map +1 -0
  15. package/dist/commands/inject.js +209 -0
  16. package/dist/commands/inject.js.map +1 -0
  17. package/dist/commands/list.d.ts +2 -0
  18. package/dist/commands/list.d.ts.map +1 -0
  19. package/dist/commands/list.js +174 -0
  20. package/dist/commands/list.js.map +1 -0
  21. package/dist/commands/sync.d.ts +17 -0
  22. package/dist/commands/sync.d.ts.map +1 -0
  23. package/dist/commands/sync.js +239 -0
  24. package/dist/commands/sync.js.map +1 -0
  25. package/dist/core/compressor.d.ts +16 -0
  26. package/dist/core/compressor.d.ts.map +1 -0
  27. package/dist/core/compressor.js +354 -0
  28. package/dist/core/compressor.js.map +1 -0
  29. package/dist/core/scanner.d.ts +40 -0
  30. package/dist/core/scanner.d.ts.map +1 -0
  31. package/dist/core/scanner.js +366 -0
  32. package/dist/core/scanner.js.map +1 -0
  33. package/dist/core/storage.d.ts +39 -0
  34. package/dist/core/storage.d.ts.map +1 -0
  35. package/dist/core/storage.js +122 -0
  36. package/dist/core/storage.js.map +1 -0
  37. package/dist/index.d.ts +3 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +57 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/test-relay.d.ts +2 -0
  42. package/dist/test-relay.d.ts.map +1 -0
  43. package/dist/test-relay.js +433 -0
  44. package/dist/test-relay.js.map +1 -0
  45. package/package.json +32 -0
@@ -0,0 +1,17 @@
1
+ /**
2
+ * relay sync — Scan project and update context.
3
+ *
4
+ * Options:
5
+ * --silent Suppress all output (used by git hooks)
6
+ *
7
+ * Flow:
8
+ * 1. Scan the project directory
9
+ * 2. Read existing PROJECT.md
10
+ * 3. Update auto-generated sections, preserve human-written ones
11
+ * 4. Write updated PROJECT.md
12
+ * 5. Show diff summary (unless --silent)
13
+ */
14
+ export declare function syncCommand(options?: {
15
+ silent?: boolean;
16
+ }): Promise<void>;
17
+ //# sourceMappingURL=sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"AAsIA;;;;;;;;;;;;GAYG;AACH,wBAAsB,WAAW,CAAC,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAgH/E"}
@@ -0,0 +1,239 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.syncCommand = syncCommand;
7
+ const scanner_1 = require("../core/scanner");
8
+ const storage_1 = require("../core/storage");
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ /**
12
+ * Format GitContext data into a markdown string.
13
+ */
14
+ function formatGitSection(git) {
15
+ if (!git.isGitRepo) {
16
+ return '';
17
+ }
18
+ const lines = [];
19
+ lines.push(`- **Branch:** ${git.currentBranch || 'unknown'}`);
20
+ lines.push(`- **Last Commit:** ${git.lastCommitHash ? `\`${git.lastCommitHash.slice(0, 7)}\`` : 'none'}`);
21
+ lines.push('\n### Uncommitted changes');
22
+ if (git.uncommittedChanges.length === 0) {
23
+ lines.push('Uncommitted changes: (clean)');
24
+ }
25
+ else {
26
+ git.uncommittedChanges.forEach((change) => {
27
+ lines.push(`- \`${change.file}\` (${change.status}) | +${change.additions} -${change.deletions}`);
28
+ });
29
+ }
30
+ if (git.stagedFiles.length > 0) {
31
+ lines.push('\n### Staged files');
32
+ git.stagedFiles.forEach((file) => {
33
+ lines.push(`- \`${file}\``);
34
+ });
35
+ }
36
+ if (git.recentCommits.length > 0) {
37
+ lines.push('\n### Recent commits');
38
+ git.recentCommits.forEach((commit) => {
39
+ lines.push(`- \`${commit.hash}\` (${commit.timeAgo}) - ${commit.message}`);
40
+ if (commit.filesChanged.length > 0) {
41
+ lines.push(` _Changed:_ ${commit.filesChanged.map((f) => `\`${f}\``).join(', ')}`);
42
+ }
43
+ });
44
+ }
45
+ return '\n' + lines.join('\n');
46
+ }
47
+ /**
48
+ * Sections that are auto-updated by sync.
49
+ * These are regenerated from the scan data.
50
+ */
51
+ const AUTO_SECTIONS = new Set([
52
+ 'File structure',
53
+ 'Tech stack',
54
+ ]);
55
+ /**
56
+ * Sections that are human-written and never overwritten by sync.
57
+ */
58
+ const PROTECTED_SECTIONS = new Set([
59
+ "What we're building",
60
+ 'Conventions',
61
+ "What's next",
62
+ "What we're NOT building",
63
+ ]);
64
+ /**
65
+ * Parse PROJECT.md into sections by ## headings.
66
+ */
67
+ function parseSections(md) {
68
+ const sections = new Map();
69
+ const lines = md.split('\n');
70
+ let currentHeading = '__preamble__';
71
+ let currentLines = [];
72
+ for (const line of lines) {
73
+ const match = line.match(/^##\s+(.+)/);
74
+ if (match) {
75
+ sections.set(currentHeading, currentLines.join('\n').trimEnd());
76
+ currentHeading = match[1].trim();
77
+ currentLines = [];
78
+ }
79
+ else {
80
+ currentLines.push(line);
81
+ }
82
+ }
83
+ sections.set(currentHeading, currentLines.join('\n').trimEnd());
84
+ return sections;
85
+ }
86
+ /**
87
+ * Render a ProjectScan as a file structure section.
88
+ */
89
+ function renderFileStructure(scan) {
90
+ let content = '\n```\n' + scan.structure + '\n```\n';
91
+ // File counts
92
+ const counts = Object.entries(scan.fileCount)
93
+ .sort((a, b) => b[1] - a[1])
94
+ .slice(0, 10)
95
+ .map(([ext, count]) => ` ${ext}: ${count}`)
96
+ .join('\n');
97
+ content += `\n**File counts:**\n${counts}`;
98
+ return content;
99
+ }
100
+ /**
101
+ * Render tech stack section.
102
+ */
103
+ function renderTechStack(scan) {
104
+ return '\n' + scan.techStack.map((t) => `- ${t}`).join('\n');
105
+ }
106
+ /**
107
+ * Render "What's in progress" section from recently modified files.
108
+ */
109
+ function renderInProgress(scan, existing) {
110
+ const recentFiles = scan.recentlyModified
111
+ .map((f) => `- \`${f}\``)
112
+ .join('\n');
113
+ // Strip out any previously generated "Recently modified:" section
114
+ const cleanedExisting = existing.replace(/\*\*Recently modified:\*\*[\s\S]*/i, '').trim();
115
+ // Keep any existing human-written content and append recent changes
116
+ const lines = [];
117
+ if (cleanedExisting) {
118
+ lines.push(cleanedExisting);
119
+ }
120
+ lines.push(`\n**Recently modified:**\n${recentFiles}`);
121
+ return '\n' + lines.join('\n');
122
+ }
123
+ /**
124
+ * relay sync — Scan project and update context.
125
+ *
126
+ * Options:
127
+ * --silent Suppress all output (used by git hooks)
128
+ *
129
+ * Flow:
130
+ * 1. Scan the project directory
131
+ * 2. Read existing PROJECT.md
132
+ * 3. Update auto-generated sections, preserve human-written ones
133
+ * 4. Write updated PROJECT.md
134
+ * 5. Show diff summary (unless --silent)
135
+ */
136
+ async function syncCommand(options) {
137
+ const silent = options?.silent ?? false;
138
+ const projectName = (0, storage_1.getProjectName)();
139
+ const projectPath = (0, storage_1.getProjectPath)();
140
+ if (!fs_1.default.existsSync(projectPath)) {
141
+ if (silent)
142
+ return;
143
+ console.log('❌ Relay not initialized for this project. Run `relay init` first.');
144
+ process.exit(1);
145
+ }
146
+ if (!silent) {
147
+ console.log(`\n🔄 Syncing "${projectName}"...\n`);
148
+ }
149
+ // 1. Scan
150
+ const scan = await (0, scanner_1.scanProject)(process.cwd());
151
+ // 2. Read existing PROJECT.md
152
+ let existingMd = (0, storage_1.readProjectMd)();
153
+ // Strip any historical/duplicated sync footers
154
+ existingMd = existingMd.replace(/\n*---\n*_Last synced:[0-9: \-_]+_\s*/gi, '').trimEnd();
155
+ const sections = parseSections(existingMd);
156
+ // 3. Update sections
157
+ const updated = [];
158
+ // Track what changed
159
+ const changes = [];
160
+ // Preamble (title etc.)
161
+ if (sections.has('__preamble__')) {
162
+ updated.push(sections.get('__preamble__'));
163
+ }
164
+ // Update timestamp in preamble
165
+ const timestamp = new Date().toISOString().replace('T', ' ').slice(0, 19);
166
+ // Rebuild the document section by section
167
+ const sectionOrder = [
168
+ "What we're building",
169
+ 'Tech stack',
170
+ 'Git context',
171
+ "What's in progress",
172
+ "What's next",
173
+ 'Conventions',
174
+ 'File structure',
175
+ "What's working",
176
+ "What we're NOT building",
177
+ 'Checkpoints',
178
+ ];
179
+ for (const heading of sectionOrder) {
180
+ const existing = sections.get(heading) || '';
181
+ if (heading === 'File structure') {
182
+ updated.push(`## File structure\n${renderFileStructure(scan)}`);
183
+ changes.push('file structure');
184
+ }
185
+ else if (heading === 'Tech stack') {
186
+ updated.push(`## Tech stack\n${renderTechStack(scan)}`);
187
+ changes.push('tech stack');
188
+ }
189
+ else if (heading === "What's in progress") {
190
+ updated.push(`## What's in progress\n${renderInProgress(scan, existing)}`);
191
+ changes.push('recently modified');
192
+ }
193
+ else if (heading === 'Git context') {
194
+ const gitContent = formatGitSection(scan.gitContext);
195
+ if (gitContent) {
196
+ updated.push(`## Git context\n${gitContent}`);
197
+ changes.push('git context');
198
+ }
199
+ }
200
+ else if (existing || sections.has(heading)) {
201
+ // Keep human-written sections as-is
202
+ updated.push(`## ${heading}\n${existing}`);
203
+ }
204
+ }
205
+ // Add any extra sections not in our known order
206
+ for (const [heading, content] of sections) {
207
+ if (heading === '__preamble__')
208
+ continue;
209
+ if (sectionOrder.includes(heading))
210
+ continue;
211
+ updated.push(`## ${heading}\n${content}`);
212
+ }
213
+ // Append sync timestamp
214
+ const footer = `\n\n---\n_Last synced: ${timestamp}_`;
215
+ // 4. Write
216
+ (0, storage_1.writeProjectMd)(updated.join('\n\n') + footer + '\n');
217
+ // Update meta.json
218
+ const metaPath = path_1.default.join(projectPath, 'meta.json');
219
+ let meta = {};
220
+ if (fs_1.default.existsSync(metaPath)) {
221
+ try {
222
+ meta = JSON.parse(fs_1.default.readFileSync(metaPath, 'utf-8'));
223
+ }
224
+ catch {
225
+ // ignore
226
+ }
227
+ }
228
+ meta.name = projectName;
229
+ meta.cwd = process.cwd();
230
+ meta.lastSync = new Date().toISOString();
231
+ fs_1.default.writeFileSync(metaPath, JSON.stringify(meta, null, 2), 'utf-8');
232
+ // 5. Report
233
+ if (!silent) {
234
+ console.log(`✅ Sync complete for "${projectName}"`);
235
+ console.log(` Updated: ${changes.join(', ')}`);
236
+ console.log(` 📁 ${projectPath}\n`);
237
+ }
238
+ }
239
+ //# sourceMappingURL=sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ export type InjectIntent = 'continue' | 'newTask' | 'debug';
2
+ /**
3
+ * Compress PROJECT.md to under 1800 tokens using Claude Haiku.
4
+ */
5
+ export declare function compressContext(projectMd: string, apiKey: string, intent?: InjectIntent): Promise<string>;
6
+ /**
7
+ * Fallback naive truncation respecting section priority.
8
+ * Used when no API key is available.
9
+ */
10
+ export declare function truncateContext(projectMd: string): string;
11
+ /**
12
+ * Preprocesses PROJECT.md content based on intent.
13
+ * Enforces intent-specific section inclusion, ordering, formatting, and the 1800-token limit.
14
+ */
15
+ export declare function preprocessContext(projectMd: string, intent: InjectIntent, errorMessage?: string): string;
16
+ //# sourceMappingURL=compressor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compressor.d.ts","sourceRoot":"","sources":["../../src/core/compressor.ts"],"names":[],"mappings":"AAkBA,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,SAAS,GAAG,OAAO,CAAC;AAE5D;;GAEG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,YAAY,GACpB,OAAO,CAAC,MAAM,CAAC,CA8BjB;AAwCD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAwEzD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,EACpB,YAAY,CAAC,EAAE,MAAM,GACpB,MAAM,CAsOR"}
@@ -0,0 +1,354 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.compressContext = compressContext;
7
+ exports.truncateContext = truncateContext;
8
+ exports.preprocessContext = preprocessContext;
9
+ const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
10
+ /**
11
+ * Section priority order for context compression.
12
+ * Lower index = higher priority = kept first.
13
+ */
14
+ const SECTION_PRIORITY = [
15
+ "What we're building", // 1 — always kept, max 3 sentences
16
+ 'Tech stack', // 2 — always kept
17
+ 'Git context', // 3 — always kept
18
+ "What's in progress", // 4 — always kept
19
+ "What's next", // 5 — always kept
20
+ 'Conventions', // 6 — truncated if needed
21
+ 'File structure', // 7 — collapsed to top level only
22
+ "What's working", // 8 — dropped if token pressure
23
+ 'Checkpoints', // 9 — always dropped
24
+ ];
25
+ /**
26
+ * Compress PROJECT.md to under 1800 tokens using Claude Haiku.
27
+ */
28
+ async function compressContext(projectMd, apiKey, intent) {
29
+ const client = new sdk_1.default({ apiKey });
30
+ let systemPrompt = `You are a context compressor. Given a PROJECT.md file, compress it to under 1800 tokens while preserving the most important information.`;
31
+ if (intent) {
32
+ systemPrompt += `\n\nOptimize the compression for the following developer intent: "${intent}". Keep the focus on the sections priority listed in the user's intent.`;
33
+ }
34
+ systemPrompt += `\n\nOutput ONLY the compressed context, no explanations or meta-commentary.`;
35
+ const response = await client.messages.create({
36
+ model: 'claude-3-haiku-20240307',
37
+ max_tokens: 2048,
38
+ system: systemPrompt,
39
+ messages: [
40
+ {
41
+ role: 'user',
42
+ content: `Compress this PROJECT.md:\n\n${projectMd}`,
43
+ },
44
+ ],
45
+ });
46
+ const block = response.content[0];
47
+ if (block.type === 'text') {
48
+ return block.text;
49
+ }
50
+ // Fallback if response isn't text
51
+ return truncateContext(projectMd);
52
+ }
53
+ /**
54
+ * Parse PROJECT.md into sections by ## headings.
55
+ */
56
+ function parseSections(projectMd) {
57
+ const lines = projectMd.split('\n');
58
+ const sections = [];
59
+ let currentHeading = '';
60
+ let currentLines = [];
61
+ for (const line of lines) {
62
+ const headingMatch = line.match(/^##\s+(.+)/);
63
+ if (headingMatch) {
64
+ if (currentHeading || currentLines.length > 0) {
65
+ sections.push({
66
+ heading: currentHeading,
67
+ content: currentLines.join('\n').trim(),
68
+ });
69
+ }
70
+ currentHeading = headingMatch[1].trim();
71
+ currentLines = [];
72
+ }
73
+ else {
74
+ currentLines.push(line);
75
+ }
76
+ }
77
+ // Push final section
78
+ if (currentHeading || currentLines.length > 0) {
79
+ sections.push({
80
+ heading: currentHeading,
81
+ content: currentLines.join('\n').trim(),
82
+ });
83
+ }
84
+ return sections;
85
+ }
86
+ /**
87
+ * Fallback naive truncation respecting section priority.
88
+ * Used when no API key is available.
89
+ */
90
+ function truncateContext(projectMd) {
91
+ const sections = parseSections(projectMd);
92
+ const TOKEN_LIMIT = 2000;
93
+ // Rough approximation: 1 token ≈ 4 chars
94
+ const CHAR_LIMIT = TOKEN_LIMIT * 4;
95
+ const result = [];
96
+ let charCount = 0;
97
+ // First pass: add sections in priority order
98
+ for (const priorityName of SECTION_PRIORITY) {
99
+ const section = sections.find((s) => s.heading.toLowerCase() === priorityName.toLowerCase());
100
+ if (!section || !section.content)
101
+ continue;
102
+ // Always drop checkpoints
103
+ if (priorityName === 'Checkpoints')
104
+ continue;
105
+ let sectionText = `## ${section.heading}\n\n${section.content}`;
106
+ // Drop "What's working" if we're over 75% of limit
107
+ if (priorityName === "What's working" &&
108
+ charCount > CHAR_LIMIT * 0.75) {
109
+ continue;
110
+ }
111
+ // Collapse file structure to top level
112
+ if (priorityName === 'File structure') {
113
+ const lines = section.content.split('\n');
114
+ const topLevel = lines.filter((l) => !l.startsWith(' ') && !l.startsWith('\t\t'));
115
+ sectionText = `## ${section.heading}\n\n${topLevel.join('\n')}`;
116
+ }
117
+ // Truncate conventions if over budget
118
+ if (priorityName === 'Conventions' && charCount + sectionText.length > CHAR_LIMIT) {
119
+ const remaining = Math.max(0, CHAR_LIMIT - charCount - 50);
120
+ sectionText = sectionText.slice(0, remaining) + '\n...(truncated)';
121
+ }
122
+ if (charCount + sectionText.length > CHAR_LIMIT)
123
+ continue;
124
+ result.push(sectionText);
125
+ charCount += sectionText.length;
126
+ }
127
+ // Second pass: add custom user-defined sections that are not in SECTION_PRIORITY
128
+ for (const section of sections) {
129
+ if (section.heading === '')
130
+ continue; // preamble handled separately
131
+ const isPredefined = SECTION_PRIORITY.some((p) => p.toLowerCase() === section.heading.toLowerCase());
132
+ if (isPredefined)
133
+ continue;
134
+ const sectionText = `## ${section.heading}\n\n${section.content}`;
135
+ if (charCount + sectionText.length < CHAR_LIMIT) {
136
+ result.push(sectionText);
137
+ charCount += sectionText.length;
138
+ }
139
+ }
140
+ // Add any preamble (content before first ## heading)
141
+ const preamble = sections.find((s) => s.heading === '');
142
+ if (preamble && preamble.content && charCount + preamble.content.length < CHAR_LIMIT) {
143
+ result.unshift(preamble.content);
144
+ }
145
+ return result.join('\n\n');
146
+ }
147
+ /**
148
+ * Preprocesses PROJECT.md content based on intent.
149
+ * Enforces intent-specific section inclusion, ordering, formatting, and the 1800-token limit.
150
+ */
151
+ function preprocessContext(projectMd, intent, errorMessage) {
152
+ const sections = parseSections(projectMd);
153
+ const result = [];
154
+ const findSection = (name) => sections.find((s) => s.heading.toLowerCase() === name.toLowerCase());
155
+ const getSentences = (text, max) => {
156
+ if (!text.trim())
157
+ return '';
158
+ const sentences = text.split(/(?<=[.!?])\s+/);
159
+ return sentences.slice(0, max).join(' ').trim();
160
+ };
161
+ const trimList = (text, max) => {
162
+ const lines = text.split('\n');
163
+ const listLines = [];
164
+ let count = 0;
165
+ for (const line of lines) {
166
+ if (line.trim().startsWith('-') || line.trim().startsWith('*')) {
167
+ if (count < max) {
168
+ listLines.push(line);
169
+ count++;
170
+ }
171
+ }
172
+ else {
173
+ if (count === 0) {
174
+ listLines.push(line);
175
+ }
176
+ }
177
+ }
178
+ return listLines.join('\n').trim();
179
+ };
180
+ const collapseStructure = (text) => {
181
+ const lines = text.split('\n');
182
+ const topLevel = lines.filter((l) => !l.startsWith(' ') && !l.startsWith('\t\t'));
183
+ return topLevel.join('\n').trim();
184
+ };
185
+ const formatGitContext = (text, rule) => {
186
+ const lines = text.split('\n');
187
+ const outputLines = [];
188
+ let inRecentCommits = false;
189
+ let commitCount = 0;
190
+ const maxCommits = rule === 'continue' ? 3 : 5;
191
+ for (let i = 0; i < lines.length; i++) {
192
+ const line = lines[i];
193
+ if (line.includes('### Uncommitted changes')) {
194
+ if (rule === 'newTask') {
195
+ while (i + 1 < lines.length && !lines[i + 1].startsWith('###') && !lines[i + 1].startsWith('##')) {
196
+ i++;
197
+ }
198
+ continue;
199
+ }
200
+ outputLines.push(line);
201
+ continue;
202
+ }
203
+ if (line.includes('### Staged files')) {
204
+ if (rule === 'newTask' || rule === 'continue') {
205
+ while (i + 1 < lines.length && !lines[i + 1].startsWith('###') && !lines[i + 1].startsWith('##')) {
206
+ i++;
207
+ }
208
+ continue;
209
+ }
210
+ outputLines.push(line);
211
+ continue;
212
+ }
213
+ if (line.includes('### Recent commits')) {
214
+ outputLines.push(line);
215
+ inRecentCommits = true;
216
+ commitCount = 0;
217
+ continue;
218
+ }
219
+ if (inRecentCommits) {
220
+ if (line.trim().startsWith('- ')) {
221
+ if (commitCount < maxCommits) {
222
+ outputLines.push(line);
223
+ commitCount++;
224
+ }
225
+ else {
226
+ inRecentCommits = false;
227
+ }
228
+ }
229
+ else if (line.trim().startsWith('_Changed:_')) {
230
+ if (rule !== 'newTask') {
231
+ outputLines.push(line);
232
+ }
233
+ }
234
+ else {
235
+ outputLines.push(line);
236
+ }
237
+ continue;
238
+ }
239
+ outputLines.push(line);
240
+ }
241
+ return outputLines.join('\n').trim();
242
+ };
243
+ if (intent === 'continue') {
244
+ // 1. What We're Building (2 sentences max, truncated)
245
+ const building = findSection("What we're building") || findSection("What We're Building");
246
+ if (building) {
247
+ result.push(`## ${building.heading}\n\n${getSentences(building.content, 2)}`);
248
+ }
249
+ // 2. Current Session State
250
+ const sessionState = findSection("Current Session State") || findSection("Current session state");
251
+ if (sessionState) {
252
+ result.push(`## ${sessionState.heading}\n\n${sessionState.content}`);
253
+ }
254
+ // 3. What's In Progress
255
+ const inProgress = findSection("What's in progress") || findSection("What's In Progress");
256
+ if (inProgress) {
257
+ result.push(`## ${inProgress.heading}\n\n${inProgress.content}`);
258
+ }
259
+ // 4. Recent Activity (Git) — full uncommitted changes + last 3 commits
260
+ const git = findSection("Git context") || findSection("Recent Activity (Git)") || findSection("Recent Activity");
261
+ if (git) {
262
+ result.push(`## ${git.heading}\n\n${formatGitContext(git.content, 'continue')}`);
263
+ }
264
+ // 5. Tech Stack (just the list, no descriptions)
265
+ const tech = findSection("Tech stack") || findSection("Tech Stack");
266
+ if (tech) {
267
+ const listOnly = tech.content
268
+ .split('\n')
269
+ .filter((l) => l.trim().startsWith('-') || l.trim().startsWith('*'))
270
+ .join('\n');
271
+ result.push(`## ${tech.heading}\n\n${listOnly}`);
272
+ }
273
+ // 6. What's Next (first 3 items only)
274
+ const next = findSection("What's next") || findSection("What's Next");
275
+ if (next) {
276
+ result.push(`## ${next.heading}\n\n${trimList(next.content, 3)}`);
277
+ }
278
+ }
279
+ else if (intent === 'newTask') {
280
+ // 1. What We're Building (full)
281
+ const building = findSection("What we're building") || findSection("What We're Building");
282
+ if (building) {
283
+ result.push(`## ${building.heading}\n\n${building.content}`);
284
+ }
285
+ // 2. Tech Stack (full)
286
+ const tech = findSection("Tech stack") || findSection("Tech Stack");
287
+ if (tech) {
288
+ result.push(`## ${tech.heading}\n\n${tech.content}`);
289
+ }
290
+ // 3. Repo Structure (collapsed to top level)
291
+ const structure = findSection("File structure") || findSection("Repo Structure") || findSection("File Structure");
292
+ if (structure) {
293
+ result.push(`## ${structure.heading}\n\n${collapseStructure(structure.content)}`);
294
+ }
295
+ // 4. Conventions (full)
296
+ const conventions = findSection("Conventions");
297
+ if (conventions) {
298
+ result.push(`## ${conventions.heading}\n\n${conventions.content}`);
299
+ }
300
+ // 5. What's Working
301
+ const working = findSection("What's working") || findSection("What's Working");
302
+ if (working) {
303
+ result.push(`## ${working.heading}\n\n${working.content}`);
304
+ }
305
+ // 6. What's Next (full)
306
+ const next = findSection("What's next") || findSection("What's Next");
307
+ if (next) {
308
+ result.push(`## ${next.heading}\n\n${next.content}`);
309
+ }
310
+ // 7. Recent Activity (Git) — last 5 commits only, no diff details
311
+ const git = findSection("Git context") || findSection("Recent Activity (Git)") || findSection("Recent Activity");
312
+ if (git) {
313
+ result.push(`## ${git.heading}\n\n${formatGitContext(git.content, 'newTask')}`);
314
+ }
315
+ }
316
+ else if (intent === 'debug') {
317
+ // 1. What We're Building (1 sentence)
318
+ const building = findSection("What we're building") || findSection("What We're Building");
319
+ if (building) {
320
+ result.push(`## ${building.heading}\n\n${getSentences(building.content, 1)}`);
321
+ }
322
+ // 2. Recent Activity (Git) — full uncommitted changes + last 5 commits + staged files
323
+ const git = findSection("Git context") || findSection("Recent Activity (Git)") || findSection("Recent Activity");
324
+ if (git) {
325
+ result.push(`## ${git.heading}\n\n${formatGitContext(git.content, 'debug')}`);
326
+ }
327
+ // 3. What's In Progress
328
+ const inProgress = findSection("What's in progress") || findSection("What's In Progress");
329
+ if (inProgress) {
330
+ result.push(`## ${inProgress.heading}\n\n${inProgress.content}`);
331
+ }
332
+ // 4. Tech Stack
333
+ const tech = findSection("Tech stack") || findSection("Tech Stack");
334
+ if (tech) {
335
+ result.push(`## ${tech.heading}\n\n${tech.content}`);
336
+ }
337
+ // 5. Current Error (multi-line input collected from user)
338
+ if (errorMessage) {
339
+ result.push(`## Current Error\n\n${errorMessage}`);
340
+ }
341
+ // 6. Repo Structure
342
+ const structure = findSection("File structure") || findSection("Repo Structure") || findSection("File Structure");
343
+ if (structure) {
344
+ result.push(`## ${structure.heading}\n\n${structure.content}`);
345
+ }
346
+ }
347
+ // Enforce token budget by dropping lowest priority sections
348
+ const CHARACTER_BUDGET = 1800 * 4;
349
+ while (result.length > 0 && result.join('\n\n').length > CHARACTER_BUDGET) {
350
+ result.pop();
351
+ }
352
+ return result.join('\n\n');
353
+ }
354
+ //# sourceMappingURL=compressor.js.map