agileflow 2.49.0 → 2.51.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/package.json +1 -1
- package/scripts/obtain-context.js +342 -184
- package/src/core/agents/accessibility.md +1 -1
- package/src/core/agents/adr-writer.md +1 -1
- package/src/core/agents/analytics.md +1 -1
- package/src/core/agents/api.md +1 -1
- package/src/core/agents/ci.md +1 -1
- package/src/core/agents/compliance.md +1 -1
- package/src/core/agents/configuration/archival.md +6 -6
- package/src/core/agents/configuration/attribution.md +1 -1
- package/src/core/agents/configuration/ci.md +1 -1
- package/src/core/agents/configuration/git-config.md +1 -1
- package/src/core/agents/configuration/hooks.md +16 -16
- package/src/core/agents/configuration/precompact.md +4 -4
- package/src/core/agents/configuration/status-line.md +10 -10
- package/src/core/agents/configuration/verify.md +2 -2
- package/src/core/agents/database.md +1 -1
- package/src/core/agents/datamigration.md +1 -1
- package/src/core/agents/design.md +1 -1
- package/src/core/agents/devops.md +1 -1
- package/src/core/agents/documentation.md +1 -1
- package/src/core/agents/epic-planner.md +1 -1
- package/src/core/agents/integrations.md +1 -1
- package/src/core/agents/mentor.md +1 -1
- package/src/core/agents/mobile.md +1 -1
- package/src/core/agents/monitoring.md +1 -1
- package/src/core/agents/multi-expert.md +1 -1
- package/src/core/agents/performance.md +1 -1
- package/src/core/agents/product.md +1 -1
- package/src/core/agents/qa.md +1 -1
- package/src/core/agents/readme-updater.md +1 -1
- package/src/core/agents/refactor.md +1 -1
- package/src/core/agents/research.md +1 -1
- package/src/core/agents/security.md +1 -1
- package/src/core/agents/testing.md +1 -1
- package/src/core/agents/ui.md +1 -1
- package/src/core/commands/adr.md +1 -1
- package/src/core/commands/agent.md +1 -1
- package/src/core/commands/assign.md +1 -1
- package/src/core/commands/auto.md +1 -1
- package/src/core/commands/babysit.md +4 -4
- package/src/core/commands/baseline.md +1 -1
- package/src/core/commands/blockers.md +1 -1
- package/src/core/commands/board.md +1 -1
- package/src/core/commands/compress.md +7 -7
- package/src/core/commands/configure.md +21 -21
- package/src/core/commands/deps.md +1 -1
- package/src/core/commands/diagnose.md +2 -2
- package/src/core/commands/docs.md +1 -1
- package/src/core/commands/epic.md +1 -1
- package/src/core/commands/help.md +1 -1
- package/src/core/commands/metrics.md +1 -1
- package/src/core/commands/packages.md +1 -1
- package/src/core/commands/pr.md +1 -1
- package/src/core/commands/readme-sync.md +1 -1
- package/src/core/commands/research.md +1 -1
- package/src/core/commands/retro.md +1 -1
- package/src/core/commands/sprint.md +1 -1
- package/src/core/commands/status.md +1 -1
- package/src/core/commands/story-validate.md +1 -1
- package/src/core/commands/story.md +1 -1
- package/src/core/commands/template.md +1 -1
- package/src/core/commands/tests.md +1 -1
- package/src/core/commands/update.md +1 -1
- package/src/core/commands/validate-expertise.md +3 -3
- package/src/core/commands/velocity.md +1 -1
- package/src/core/commands/verify.md +1 -1
- package/src/core/experts/devops/expertise.yaml +2 -2
- package/src/core/experts/monitoring/expertise.yaml +1 -1
- package/src/core/templates/agileflow-configure.js +12 -12
- package/src/core/templates/agileflow-welcome.js +1 -1
- package/src/core/templates/claude-settings.advanced.example.json +2 -2
- package/src/core/templates/claude-settings.example.json +1 -1
- package/tools/cli/installers/core/installer.js +2 -2
package/package.json
CHANGED
|
@@ -3,19 +3,24 @@
|
|
|
3
3
|
* obtain-context.js
|
|
4
4
|
*
|
|
5
5
|
* Gathers all project context in a single execution for any AgileFlow command or agent.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
*
|
|
7
|
+
* SMART OUTPUT STRATEGY:
|
|
8
|
+
* - Calculates summary character count dynamically
|
|
9
|
+
* - Shows (30K - summary_chars) of full content first
|
|
10
|
+
* - Then shows the summary (so user sees it at their display cutoff)
|
|
11
|
+
* - Then shows rest of full content (for Claude)
|
|
8
12
|
*
|
|
9
13
|
* Usage:
|
|
10
14
|
* node scripts/obtain-context.js # Just gather context
|
|
11
15
|
* node scripts/obtain-context.js babysit # Gather + register 'babysit'
|
|
12
|
-
* node scripts/obtain-context.js mentor # Gather + register 'mentor'
|
|
13
16
|
*/
|
|
14
17
|
|
|
15
18
|
const fs = require('fs');
|
|
16
19
|
const path = require('path');
|
|
17
20
|
const { execSync } = require('child_process');
|
|
18
21
|
|
|
22
|
+
const DISPLAY_LIMIT = 30000; // Claude Code's Bash tool display limit
|
|
23
|
+
|
|
19
24
|
// Optional: Register command for PreCompact context preservation
|
|
20
25
|
const commandName = process.argv[2];
|
|
21
26
|
if (commandName) {
|
|
@@ -41,6 +46,11 @@ const C = {
|
|
|
41
46
|
green: '\x1b[32m',
|
|
42
47
|
red: '\x1b[31m',
|
|
43
48
|
magenta: '\x1b[35m',
|
|
49
|
+
blue: '\x1b[34m',
|
|
50
|
+
brightCyan: '\x1b[96m',
|
|
51
|
+
brightYellow: '\x1b[93m',
|
|
52
|
+
brightGreen: '\x1b[92m',
|
|
53
|
+
brand: '\x1b[38;2;232;104;58m', // AgileFlow brand orange
|
|
44
54
|
};
|
|
45
55
|
|
|
46
56
|
function safeRead(filePath) {
|
|
@@ -75,219 +85,367 @@ function safeExec(cmd) {
|
|
|
75
85
|
}
|
|
76
86
|
}
|
|
77
87
|
|
|
78
|
-
function section(title) {
|
|
79
|
-
console.log(`\n${C.cyan}${C.bold}═══ ${title} ═══${C.reset}`);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function subsection(title) {
|
|
83
|
-
console.log(`${C.dim}───${C.reset} ${title}`);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
88
|
// ============================================
|
|
87
|
-
//
|
|
89
|
+
// GENERATE SUMMARY (calculated first for positioning)
|
|
88
90
|
// ============================================
|
|
89
91
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const statusLines = status.split('\n').filter(Boolean);
|
|
99
|
-
const lastCommit = safeExec('git log -1 --format="%h %s"') || 'no commits';
|
|
100
|
-
|
|
101
|
-
console.log(`Branch: ${C.green}${branch}${C.reset}`);
|
|
102
|
-
console.log(`Last commit: ${C.dim}${lastCommit}${C.reset}`);
|
|
103
|
-
if (statusLines.length > 0) {
|
|
104
|
-
console.log(`Uncommitted: ${C.yellow}${statusLines.length} file(s)${C.reset}`);
|
|
105
|
-
statusLines.slice(0, 10).forEach(line => console.log(` ${C.dim}${line}${C.reset}`));
|
|
106
|
-
if (statusLines.length > 10) console.log(` ${C.dim}... and ${statusLines.length - 10} more${C.reset}`);
|
|
107
|
-
} else {
|
|
108
|
-
console.log(`Uncommitted: ${C.green}clean${C.reset}`);
|
|
109
|
-
}
|
|
92
|
+
function generateSummary() {
|
|
93
|
+
// Box drawing characters
|
|
94
|
+
const box = {
|
|
95
|
+
tl: '╭', tr: '╮', bl: '╰', br: '╯',
|
|
96
|
+
h: '─', v: '│',
|
|
97
|
+
lT: '├', rT: '┤', tT: '┬', bT: '┴',
|
|
98
|
+
cross: '┼',
|
|
99
|
+
};
|
|
110
100
|
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const statusColor = epic.status === 'complete' ? C.green : epic.status === 'active' ? C.yellow : C.dim;
|
|
122
|
-
console.log(` ${id}: ${epic.title} ${statusColor}[${epic.status}]${C.reset}`);
|
|
123
|
-
});
|
|
101
|
+
const W = 58; // Total inner width (matches welcome script)
|
|
102
|
+
const L = 20; // Left column width
|
|
103
|
+
const R = W - L - 3; // Right column width (35 chars)
|
|
104
|
+
|
|
105
|
+
// Pad string to length, accounting for ANSI codes
|
|
106
|
+
function pad(str, len) {
|
|
107
|
+
const stripped = str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
108
|
+
const diff = len - stripped.length;
|
|
109
|
+
if (diff <= 0) return str;
|
|
110
|
+
return str + ' '.repeat(diff);
|
|
124
111
|
}
|
|
125
112
|
|
|
126
|
-
//
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
storyList.forEach(([id, story]) => {
|
|
131
|
-
const s = story.status || 'unknown';
|
|
132
|
-
if (!byStatus[s]) byStatus[s] = [];
|
|
133
|
-
byStatus[s].push({ id, ...story });
|
|
134
|
-
});
|
|
113
|
+
// Truncate string to max length, respecting ANSI codes
|
|
114
|
+
function truncate(str, maxLen, suffix = '..') {
|
|
115
|
+
const stripped = str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
116
|
+
if (stripped.length <= maxLen) return str;
|
|
135
117
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
118
|
+
const targetLen = maxLen - suffix.length;
|
|
119
|
+
let visibleCount = 0;
|
|
120
|
+
let cutIndex = 0;
|
|
121
|
+
let inEscape = false;
|
|
122
|
+
|
|
123
|
+
for (let i = 0; i < str.length; i++) {
|
|
124
|
+
if (str[i] === '\x1b') {
|
|
125
|
+
inEscape = true;
|
|
126
|
+
} else if (inEscape && str[i] === 'm') {
|
|
127
|
+
inEscape = false;
|
|
128
|
+
} else if (!inEscape) {
|
|
129
|
+
visibleCount++;
|
|
130
|
+
if (visibleCount >= targetLen) {
|
|
131
|
+
cutIndex = i + 1;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
152
134
|
}
|
|
153
135
|
}
|
|
154
|
-
|
|
136
|
+
return str.substring(0, cutIndex) + suffix;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Create a row with auto-truncation
|
|
140
|
+
function row(left, right, leftColor = '', rightColor = '') {
|
|
141
|
+
const leftStr = `${leftColor}${left}${leftColor ? C.reset : ''}`;
|
|
142
|
+
const rightTrunc = truncate(right, R);
|
|
143
|
+
const rightStr = `${rightColor}${rightTrunc}${rightColor ? C.reset : ''}`;
|
|
144
|
+
return `${C.dim}${box.v}${C.reset} ${pad(leftStr, L)} ${C.dim}${box.v}${C.reset} ${pad(rightStr, R)} ${C.dim}${box.v}${C.reset}\n`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const divider = () => `${C.dim}${box.lT}${box.h.repeat(L + 2)}${box.cross}${box.h.repeat(R + 2)}${box.rT}${C.reset}\n`;
|
|
148
|
+
const headerTopBorder = `${C.dim}${box.tl}${box.h.repeat(W + 2)}${box.tr}${C.reset}\n`;
|
|
149
|
+
const headerDivider = `${C.dim}${box.lT}${box.h.repeat(L + 2)}${box.tT}${box.h.repeat(R + 2)}${box.rT}${C.reset}\n`;
|
|
150
|
+
const bottomBorder = `${C.dim}${box.bl}${box.h.repeat(L + 2)}${box.bT}${box.h.repeat(R + 2)}${box.br}${C.reset}\n`;
|
|
155
151
|
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
152
|
+
// Gather data
|
|
153
|
+
const branch = safeExec('git branch --show-current') || 'unknown';
|
|
154
|
+
const lastCommitShort = safeExec('git log -1 --format="%h"') || '?';
|
|
155
|
+
const lastCommitMsg = safeExec('git log -1 --format="%s"') || 'no commits';
|
|
156
|
+
const statusLines = (safeExec('git status --short') || '').split('\n').filter(Boolean);
|
|
157
|
+
const statusJson = safeReadJSON('docs/09-agents/status.json');
|
|
158
|
+
const sessionState = safeReadJSON('docs/09-agents/session-state.json');
|
|
159
|
+
const researchFiles = safeLs('docs/10-research').filter(f => f.endsWith('.md') && f !== 'README.md').sort().reverse();
|
|
160
|
+
const epicFiles = safeLs('docs/05-epics').filter(f => f.endsWith('.md') && f !== 'README.md');
|
|
161
|
+
|
|
162
|
+
// Count stories by status
|
|
163
|
+
let byStatus = {};
|
|
164
|
+
let readyStories = [];
|
|
165
|
+
if (statusJson && statusJson.stories) {
|
|
166
|
+
Object.entries(statusJson.stories).forEach(([id, story]) => {
|
|
167
|
+
const s = story.status || 'unknown';
|
|
168
|
+
byStatus[s] = (byStatus[s] || 0) + 1;
|
|
169
|
+
if (s === 'ready') readyStories.push(id);
|
|
161
170
|
});
|
|
162
171
|
}
|
|
163
|
-
|
|
164
|
-
|
|
172
|
+
|
|
173
|
+
// Session info
|
|
174
|
+
let sessionDuration = null;
|
|
175
|
+
let currentStory = null;
|
|
176
|
+
if (sessionState && sessionState.current_session && sessionState.current_session.started_at) {
|
|
177
|
+
const started = new Date(sessionState.current_session.started_at);
|
|
178
|
+
sessionDuration = Math.round((Date.now() - started.getTime()) / 60000);
|
|
179
|
+
currentStory = sessionState.current_session.current_story;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Build table
|
|
183
|
+
let summary = '\n';
|
|
184
|
+
summary += headerTopBorder;
|
|
185
|
+
|
|
186
|
+
// Header row (full width, no column divider)
|
|
187
|
+
const title = commandName ? `Context [${commandName}]` : 'Context Summary';
|
|
188
|
+
const branchColor = branch === 'main' ? C.green : branch.startsWith('fix') ? C.red : C.cyan;
|
|
189
|
+
const maxBranchLen = 20;
|
|
190
|
+
const branchDisplay = branch.length > maxBranchLen ? branch.substring(0, maxBranchLen - 2) + '..' : branch;
|
|
191
|
+
const header = `${C.brand}${C.bold}${title}${C.reset} ${branchColor}${branchDisplay}${C.reset} ${C.dim}(${lastCommitShort})${C.reset}`;
|
|
192
|
+
summary += `${C.dim}${box.v}${C.reset} ${pad(header, W)} ${C.dim}${box.v}${C.reset}\n`;
|
|
193
|
+
|
|
194
|
+
summary += headerDivider;
|
|
195
|
+
|
|
196
|
+
// Story counts with colorful labels
|
|
197
|
+
summary += row('In Progress', byStatus['in-progress'] ? `${byStatus['in-progress']}` : '0', C.yellow, byStatus['in-progress'] ? C.brightYellow : C.dim);
|
|
198
|
+
summary += row('Blocked', byStatus['blocked'] ? `${byStatus['blocked']}` : '0', C.red, byStatus['blocked'] ? C.red : C.dim);
|
|
199
|
+
summary += row('Ready', byStatus['ready'] ? `${byStatus['ready']}` : '0', C.cyan, byStatus['ready'] ? C.brightCyan : C.dim);
|
|
200
|
+
const completedColor = `${C.bold}${C.green}`;
|
|
201
|
+
summary += row('Completed', byStatus['done'] ? `${byStatus['done']}` : '0', completedColor, byStatus['done'] ? completedColor : C.dim);
|
|
202
|
+
|
|
203
|
+
summary += divider();
|
|
204
|
+
|
|
205
|
+
// Git status
|
|
206
|
+
const uncommittedStatus = statusLines.length > 0 ? `${statusLines.length} uncommitted` : '✓ clean';
|
|
207
|
+
summary += row('Git', uncommittedStatus, C.blue, statusLines.length > 0 ? C.yellow : C.green);
|
|
208
|
+
|
|
209
|
+
// Session
|
|
210
|
+
const sessionText = sessionDuration !== null ? `${sessionDuration} min active` : 'no session';
|
|
211
|
+
summary += row('Session', sessionText, C.blue, sessionDuration !== null ? C.brightGreen : C.dim);
|
|
212
|
+
|
|
213
|
+
// Current story
|
|
214
|
+
const storyText = currentStory ? currentStory : 'none';
|
|
215
|
+
summary += row('Working on', storyText, C.blue, currentStory ? C.brightYellow : C.dim);
|
|
216
|
+
|
|
217
|
+
// Ready stories (if any)
|
|
218
|
+
if (readyStories.length > 0) {
|
|
219
|
+
summary += row('⭐ Up Next', readyStories.slice(0, 3).join(', '), C.brightCyan, C.cyan);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
summary += divider();
|
|
223
|
+
|
|
224
|
+
// Key files
|
|
225
|
+
const keyFileChecks = [
|
|
226
|
+
{ path: 'CLAUDE.md', label: 'CLAUDE' },
|
|
227
|
+
{ path: 'README.md', label: 'README' },
|
|
228
|
+
{ path: 'docs/04-architecture/README.md', label: 'arch' },
|
|
229
|
+
{ path: 'docs/02-practices/README.md', label: 'practices' },
|
|
230
|
+
];
|
|
231
|
+
const keyFileStatus = keyFileChecks.map(f => {
|
|
232
|
+
const exists = fs.existsSync(f.path);
|
|
233
|
+
return exists ? `${C.brightGreen}✓${C.reset}${f.label}` : `${C.dim}○${f.label}${C.reset}`;
|
|
234
|
+
}).join(' ');
|
|
235
|
+
summary += row('Key files', keyFileStatus, C.magenta, '');
|
|
236
|
+
|
|
237
|
+
// Research
|
|
238
|
+
const researchText = researchFiles.length > 0 ? `${researchFiles.length} notes` : 'none';
|
|
239
|
+
summary += row('Research', researchText, C.magenta, researchFiles.length > 0 ? C.cyan : C.dim);
|
|
240
|
+
|
|
241
|
+
// Epics
|
|
242
|
+
const epicText = epicFiles.length > 0 ? `${epicFiles.length} epics` : 'none';
|
|
243
|
+
summary += row('Epics', epicText, C.magenta, epicFiles.length > 0 ? C.cyan : C.dim);
|
|
244
|
+
|
|
245
|
+
summary += divider();
|
|
246
|
+
|
|
247
|
+
// Last commit
|
|
248
|
+
summary += row('Last commit', `${C.yellow}${lastCommitShort}${C.reset} ${lastCommitMsg}`, C.dim, '');
|
|
249
|
+
|
|
250
|
+
summary += bottomBorder;
|
|
251
|
+
|
|
252
|
+
summary += `${C.dim}Full context continues below (Claude sees all)...${C.reset}\n\n`;
|
|
253
|
+
|
|
254
|
+
return summary;
|
|
165
255
|
}
|
|
166
256
|
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
257
|
+
// ============================================
|
|
258
|
+
// GENERATE FULL CONTENT
|
|
259
|
+
// ============================================
|
|
260
|
+
|
|
261
|
+
function generateFullContent() {
|
|
262
|
+
let content = '';
|
|
263
|
+
|
|
264
|
+
const title = commandName ? `AgileFlow Context [${commandName}]` : 'AgileFlow Context';
|
|
265
|
+
content += `${C.magenta}${C.bold}${title}${C.reset}\n`;
|
|
266
|
+
content += `${C.dim}Generated: ${new Date().toISOString()}${C.reset}\n`;
|
|
267
|
+
|
|
268
|
+
// 1. GIT STATUS
|
|
269
|
+
content += `\n${C.cyan}${C.bold}═══ Git Status ═══${C.reset}\n`;
|
|
270
|
+
const branch = safeExec('git branch --show-current') || 'unknown';
|
|
271
|
+
const status = safeExec('git status --short') || '';
|
|
272
|
+
const statusLines = status.split('\n').filter(Boolean);
|
|
273
|
+
const lastCommit = safeExec('git log -1 --format="%h %s"') || 'no commits';
|
|
274
|
+
|
|
275
|
+
content += `Branch: ${C.green}${branch}${C.reset}\n`;
|
|
276
|
+
content += `Last commit: ${C.dim}${lastCommit}${C.reset}\n`;
|
|
277
|
+
if (statusLines.length > 0) {
|
|
278
|
+
content += `Uncommitted: ${C.yellow}${statusLines.length} file(s)${C.reset}\n`;
|
|
279
|
+
statusLines.slice(0, 10).forEach(line => content += ` ${C.dim}${line}${C.reset}\n`);
|
|
280
|
+
if (statusLines.length > 10) content += ` ${C.dim}... and ${statusLines.length - 10} more${C.reset}\n`;
|
|
281
|
+
} else {
|
|
282
|
+
content += `Uncommitted: ${C.green}clean${C.reset}\n`;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// 2. STATUS.JSON - Full Content
|
|
286
|
+
content += `\n${C.cyan}${C.bold}═══ Status.json (Full Content) ═══${C.reset}\n`;
|
|
287
|
+
const statusJsonPath = 'docs/09-agents/status.json';
|
|
288
|
+
const statusJson = safeReadJSON(statusJsonPath);
|
|
289
|
+
|
|
290
|
+
if (statusJson) {
|
|
291
|
+
content += `${C.dim}${'─'.repeat(50)}${C.reset}\n`;
|
|
292
|
+
content += JSON.stringify(statusJson, null, 2).split('\n').map(l => ` ${l}`).join('\n') + '\n';
|
|
293
|
+
content += `${C.dim}${'─'.repeat(50)}${C.reset}\n`;
|
|
294
|
+
} else {
|
|
295
|
+
content += `${C.dim}No status.json found${C.reset}\n`;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// 3. SESSION STATE
|
|
299
|
+
content += `\n${C.cyan}${C.bold}═══ Session State ═══${C.reset}\n`;
|
|
300
|
+
const sessionState = safeReadJSON('docs/09-agents/session-state.json');
|
|
301
|
+
if (sessionState) {
|
|
302
|
+
const current = sessionState.current_session;
|
|
303
|
+
if (current && current.started_at) {
|
|
304
|
+
const started = new Date(current.started_at);
|
|
305
|
+
const duration = Math.round((Date.now() - started.getTime()) / 60000);
|
|
306
|
+
content += `Active session: ${C.green}${duration} min${C.reset}\n`;
|
|
307
|
+
if (current.current_story) {
|
|
308
|
+
content += `Working on: ${C.yellow}${current.current_story}${C.reset}\n`;
|
|
309
|
+
}
|
|
310
|
+
} else {
|
|
311
|
+
content += `${C.dim}No active session${C.reset}\n`;
|
|
312
|
+
}
|
|
313
|
+
if (sessionState.active_command) {
|
|
314
|
+
content += `Active command: ${C.cyan}${sessionState.active_command.name}${C.reset}\n`;
|
|
178
315
|
}
|
|
179
316
|
} else {
|
|
180
|
-
|
|
317
|
+
content += `${C.dim}No session-state.json found${C.reset}\n`;
|
|
181
318
|
}
|
|
182
319
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
320
|
+
// 4. DOCS STRUCTURE
|
|
321
|
+
content += `\n${C.cyan}${C.bold}═══ Documentation ═══${C.reset}\n`;
|
|
322
|
+
const docsDir = 'docs';
|
|
323
|
+
const docFolders = safeLs(docsDir).filter(f => {
|
|
324
|
+
try { return fs.statSync(path.join(docsDir, f)).isDirectory(); } catch { return false; }
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
if (docFolders.length > 0) {
|
|
328
|
+
docFolders.forEach(folder => {
|
|
329
|
+
const folderPath = path.join(docsDir, folder);
|
|
330
|
+
const files = safeLs(folderPath);
|
|
331
|
+
const mdFiles = files.filter(f => f.endsWith('.md'));
|
|
332
|
+
const jsonFiles = files.filter(f => f.endsWith('.json') || f.endsWith('.jsonl'));
|
|
333
|
+
let info = [];
|
|
334
|
+
if (mdFiles.length > 0) info.push(`${mdFiles.length} md`);
|
|
335
|
+
if (jsonFiles.length > 0) info.push(`${jsonFiles.length} json`);
|
|
336
|
+
content += ` ${C.dim}${folder}/${C.reset} ${info.length > 0 ? `(${info.join(', ')})` : ''}\n`;
|
|
337
|
+
});
|
|
187
338
|
}
|
|
188
339
|
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
|
|
340
|
+
// 5. RESEARCH NOTES - List + Full content of most recent
|
|
341
|
+
content += `\n${C.cyan}${C.bold}═══ Research Notes ═══${C.reset}\n`;
|
|
342
|
+
const researchDir = 'docs/10-research';
|
|
343
|
+
const researchFiles = safeLs(researchDir).filter(f => f.endsWith('.md') && f !== 'README.md');
|
|
344
|
+
if (researchFiles.length > 0) {
|
|
345
|
+
researchFiles.sort().reverse();
|
|
346
|
+
content += `${C.dim}───${C.reset} Available Research Notes\n`;
|
|
347
|
+
researchFiles.forEach(file => content += ` ${C.dim}${file}${C.reset}\n`);
|
|
348
|
+
|
|
349
|
+
const mostRecentFile = researchFiles[0];
|
|
350
|
+
const mostRecentPath = path.join(researchDir, mostRecentFile);
|
|
351
|
+
const mostRecentContent = safeRead(mostRecentPath);
|
|
352
|
+
|
|
353
|
+
if (mostRecentContent) {
|
|
354
|
+
content += `\n${C.green}📄 Most Recent: ${mostRecentFile}${C.reset}\n`;
|
|
355
|
+
content += `${C.dim}${'─'.repeat(60)}${C.reset}\n`;
|
|
356
|
+
content += mostRecentContent + '\n';
|
|
357
|
+
content += `${C.dim}${'─'.repeat(60)}${C.reset}\n`;
|
|
358
|
+
}
|
|
359
|
+
} else {
|
|
360
|
+
content += `${C.dim}No research notes${C.reset}\n`;
|
|
192
361
|
}
|
|
193
|
-
} else {
|
|
194
|
-
console.log(`${C.dim}No session-state.json found${C.reset}`);
|
|
195
|
-
}
|
|
196
362
|
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
const
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
363
|
+
// 6. BUS MESSAGES
|
|
364
|
+
content += `\n${C.cyan}${C.bold}═══ Recent Agent Messages ═══${C.reset}\n`;
|
|
365
|
+
const busPath = 'docs/09-agents/bus/log.jsonl';
|
|
366
|
+
const busContent = safeRead(busPath);
|
|
367
|
+
if (busContent) {
|
|
368
|
+
const lines = busContent.trim().split('\n').filter(Boolean);
|
|
369
|
+
const recent = lines.slice(-5);
|
|
370
|
+
if (recent.length > 0) {
|
|
371
|
+
recent.forEach(line => {
|
|
372
|
+
try {
|
|
373
|
+
const msg = JSON.parse(line);
|
|
374
|
+
const time = msg.timestamp ? new Date(msg.timestamp).toLocaleTimeString() : '?';
|
|
375
|
+
content += ` ${C.dim}[${time}]${C.reset} ${msg.from || '?'}: ${msg.type || msg.message || '?'}\n`;
|
|
376
|
+
} catch {
|
|
377
|
+
content += ` ${C.dim}${line.substring(0, 80)}...${C.reset}\n`;
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
} else {
|
|
381
|
+
content += `${C.dim}No messages${C.reset}\n`;
|
|
382
|
+
}
|
|
383
|
+
} else {
|
|
384
|
+
content += `${C.dim}No bus log found${C.reset}\n`;
|
|
205
385
|
}
|
|
206
|
-
});
|
|
207
386
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const folderPath = path.join(docsDir, folder);
|
|
211
|
-
const files = safeLs(folderPath);
|
|
212
|
-
const mdFiles = files.filter(f => f.endsWith('.md'));
|
|
213
|
-
const jsonFiles = files.filter(f => f.endsWith('.json') || f.endsWith('.jsonl'));
|
|
387
|
+
// 7. KEY FILES - Full content
|
|
388
|
+
content += `\n${C.cyan}${C.bold}═══ Key Context Files (Full Content) ═══${C.reset}\n`;
|
|
214
389
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
390
|
+
const keyFilesToRead = [
|
|
391
|
+
{ path: 'CLAUDE.md', label: 'CLAUDE.md (Project Instructions)' },
|
|
392
|
+
{ path: 'README.md', label: 'README.md (Project Overview)' },
|
|
393
|
+
{ path: 'docs/04-architecture/README.md', label: 'Architecture Index' },
|
|
394
|
+
{ path: 'docs/02-practices/README.md', label: 'Practices Index' },
|
|
395
|
+
{ path: 'docs/08-project/roadmap.md', label: 'Roadmap' },
|
|
396
|
+
];
|
|
218
397
|
|
|
219
|
-
|
|
398
|
+
keyFilesToRead.forEach(({ path: filePath, label }) => {
|
|
399
|
+
const fileContent = safeRead(filePath);
|
|
400
|
+
if (fileContent) {
|
|
401
|
+
content += `\n${C.green}✓ ${label}${C.reset} ${C.dim}(${filePath})${C.reset}\n`;
|
|
402
|
+
content += `${C.dim}${'─'.repeat(60)}${C.reset}\n`;
|
|
403
|
+
content += fileContent + '\n';
|
|
404
|
+
content += `${C.dim}${'─'.repeat(60)}${C.reset}\n`;
|
|
405
|
+
} else {
|
|
406
|
+
content += `${C.dim}○ ${label} (not found)${C.reset}\n`;
|
|
407
|
+
}
|
|
220
408
|
});
|
|
221
|
-
}
|
|
222
409
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const researchDir = 'docs/10-research';
|
|
226
|
-
const researchFiles = safeLs(researchDir).filter(f => f.endsWith('.md') && f !== 'README.md');
|
|
227
|
-
if (researchFiles.length > 0) {
|
|
228
|
-
// Sort by date (filename starts with YYYYMMDD)
|
|
229
|
-
researchFiles.sort().reverse();
|
|
230
|
-
researchFiles.slice(0, 5).forEach(file => {
|
|
231
|
-
console.log(` ${C.dim}${file}${C.reset}`);
|
|
232
|
-
});
|
|
233
|
-
if (researchFiles.length > 5) {
|
|
234
|
-
console.log(` ${C.dim}... and ${researchFiles.length - 5} more${C.reset}`);
|
|
235
|
-
}
|
|
236
|
-
} else {
|
|
237
|
-
console.log(`${C.dim}No research notes${C.reset}`);
|
|
238
|
-
}
|
|
410
|
+
const settingsExists = fs.existsSync('.claude/settings.json');
|
|
411
|
+
content += `\n ${settingsExists ? `${C.green}✓${C.reset}` : `${C.dim}○${C.reset}`} .claude/settings.json\n`;
|
|
239
412
|
|
|
240
|
-
//
|
|
241
|
-
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const lines = busContent.trim().split('\n').filter(Boolean);
|
|
246
|
-
const recent = lines.slice(-5);
|
|
247
|
-
if (recent.length > 0) {
|
|
248
|
-
recent.forEach(line => {
|
|
249
|
-
try {
|
|
250
|
-
const msg = JSON.parse(line);
|
|
251
|
-
const time = msg.timestamp ? new Date(msg.timestamp).toLocaleTimeString() : '?';
|
|
252
|
-
console.log(` ${C.dim}[${time}]${C.reset} ${msg.from || '?'}: ${msg.type || msg.message || '?'}`);
|
|
253
|
-
} catch {
|
|
254
|
-
console.log(` ${C.dim}${line.substring(0, 80)}...${C.reset}`);
|
|
255
|
-
}
|
|
256
|
-
});
|
|
413
|
+
// 8. EPICS FOLDER
|
|
414
|
+
content += `\n${C.cyan}${C.bold}═══ Epic Files ═══${C.reset}\n`;
|
|
415
|
+
const epicFiles = safeLs('docs/05-epics').filter(f => f.endsWith('.md') && f !== 'README.md');
|
|
416
|
+
if (epicFiles.length > 0) {
|
|
417
|
+
epicFiles.forEach(file => content += ` ${C.dim}${file}${C.reset}\n`);
|
|
257
418
|
} else {
|
|
258
|
-
|
|
419
|
+
content += `${C.dim}No epic files${C.reset}\n`;
|
|
259
420
|
}
|
|
260
|
-
|
|
261
|
-
|
|
421
|
+
|
|
422
|
+
// FOOTER
|
|
423
|
+
content += `\n${C.dim}─────────────────────────────────────────${C.reset}\n`;
|
|
424
|
+
content += `${C.dim}Context gathered in single execution. Claude has full context.${C.reset}\n`;
|
|
425
|
+
|
|
426
|
+
return content;
|
|
262
427
|
}
|
|
263
428
|
|
|
264
|
-
//
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
console.log(
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
// 8. EPICS FOLDER
|
|
281
|
-
section('Epic Files');
|
|
282
|
-
const epicFiles = safeLs('docs/05-epics').filter(f => f.endsWith('.md') && f !== 'README.md');
|
|
283
|
-
if (epicFiles.length > 0) {
|
|
284
|
-
epicFiles.forEach(file => {
|
|
285
|
-
console.log(` ${C.dim}${file}${C.reset}`);
|
|
286
|
-
});
|
|
429
|
+
// ============================================
|
|
430
|
+
// MAIN: Output with smart summary positioning
|
|
431
|
+
// ============================================
|
|
432
|
+
|
|
433
|
+
const summary = generateSummary();
|
|
434
|
+
const fullContent = generateFullContent();
|
|
435
|
+
|
|
436
|
+
const summaryLength = summary.length;
|
|
437
|
+
const cutoffPoint = DISPLAY_LIMIT - summaryLength;
|
|
438
|
+
|
|
439
|
+
if (fullContent.length <= cutoffPoint) {
|
|
440
|
+
// Full content fits before summary - just output everything
|
|
441
|
+
console.log(fullContent);
|
|
442
|
+
console.log(summary);
|
|
287
443
|
} else {
|
|
288
|
-
|
|
289
|
-
|
|
444
|
+
// Split: content before cutoff + summary + content after cutoff
|
|
445
|
+
const contentBefore = fullContent.substring(0, cutoffPoint);
|
|
446
|
+
const contentAfter = fullContent.substring(cutoffPoint);
|
|
290
447
|
|
|
291
|
-
|
|
292
|
-
console.log(
|
|
293
|
-
console.log(
|
|
448
|
+
console.log(contentBefore);
|
|
449
|
+
console.log(summary);
|
|
450
|
+
console.log(contentAfter);
|
|
451
|
+
}
|
package/src/core/agents/api.md
CHANGED