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.
Files changed (74) hide show
  1. package/package.json +1 -1
  2. package/scripts/obtain-context.js +342 -184
  3. package/src/core/agents/accessibility.md +1 -1
  4. package/src/core/agents/adr-writer.md +1 -1
  5. package/src/core/agents/analytics.md +1 -1
  6. package/src/core/agents/api.md +1 -1
  7. package/src/core/agents/ci.md +1 -1
  8. package/src/core/agents/compliance.md +1 -1
  9. package/src/core/agents/configuration/archival.md +6 -6
  10. package/src/core/agents/configuration/attribution.md +1 -1
  11. package/src/core/agents/configuration/ci.md +1 -1
  12. package/src/core/agents/configuration/git-config.md +1 -1
  13. package/src/core/agents/configuration/hooks.md +16 -16
  14. package/src/core/agents/configuration/precompact.md +4 -4
  15. package/src/core/agents/configuration/status-line.md +10 -10
  16. package/src/core/agents/configuration/verify.md +2 -2
  17. package/src/core/agents/database.md +1 -1
  18. package/src/core/agents/datamigration.md +1 -1
  19. package/src/core/agents/design.md +1 -1
  20. package/src/core/agents/devops.md +1 -1
  21. package/src/core/agents/documentation.md +1 -1
  22. package/src/core/agents/epic-planner.md +1 -1
  23. package/src/core/agents/integrations.md +1 -1
  24. package/src/core/agents/mentor.md +1 -1
  25. package/src/core/agents/mobile.md +1 -1
  26. package/src/core/agents/monitoring.md +1 -1
  27. package/src/core/agents/multi-expert.md +1 -1
  28. package/src/core/agents/performance.md +1 -1
  29. package/src/core/agents/product.md +1 -1
  30. package/src/core/agents/qa.md +1 -1
  31. package/src/core/agents/readme-updater.md +1 -1
  32. package/src/core/agents/refactor.md +1 -1
  33. package/src/core/agents/research.md +1 -1
  34. package/src/core/agents/security.md +1 -1
  35. package/src/core/agents/testing.md +1 -1
  36. package/src/core/agents/ui.md +1 -1
  37. package/src/core/commands/adr.md +1 -1
  38. package/src/core/commands/agent.md +1 -1
  39. package/src/core/commands/assign.md +1 -1
  40. package/src/core/commands/auto.md +1 -1
  41. package/src/core/commands/babysit.md +4 -4
  42. package/src/core/commands/baseline.md +1 -1
  43. package/src/core/commands/blockers.md +1 -1
  44. package/src/core/commands/board.md +1 -1
  45. package/src/core/commands/compress.md +7 -7
  46. package/src/core/commands/configure.md +21 -21
  47. package/src/core/commands/deps.md +1 -1
  48. package/src/core/commands/diagnose.md +2 -2
  49. package/src/core/commands/docs.md +1 -1
  50. package/src/core/commands/epic.md +1 -1
  51. package/src/core/commands/help.md +1 -1
  52. package/src/core/commands/metrics.md +1 -1
  53. package/src/core/commands/packages.md +1 -1
  54. package/src/core/commands/pr.md +1 -1
  55. package/src/core/commands/readme-sync.md +1 -1
  56. package/src/core/commands/research.md +1 -1
  57. package/src/core/commands/retro.md +1 -1
  58. package/src/core/commands/sprint.md +1 -1
  59. package/src/core/commands/status.md +1 -1
  60. package/src/core/commands/story-validate.md +1 -1
  61. package/src/core/commands/story.md +1 -1
  62. package/src/core/commands/template.md +1 -1
  63. package/src/core/commands/tests.md +1 -1
  64. package/src/core/commands/update.md +1 -1
  65. package/src/core/commands/validate-expertise.md +3 -3
  66. package/src/core/commands/velocity.md +1 -1
  67. package/src/core/commands/verify.md +1 -1
  68. package/src/core/experts/devops/expertise.yaml +2 -2
  69. package/src/core/experts/monitoring/expertise.yaml +1 -1
  70. package/src/core/templates/agileflow-configure.js +12 -12
  71. package/src/core/templates/agileflow-welcome.js +1 -1
  72. package/src/core/templates/claude-settings.advanced.example.json +2 -2
  73. package/src/core/templates/claude-settings.example.json +1 -1
  74. package/tools/cli/installers/core/installer.js +2 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agileflow",
3
- "version": "2.49.0",
3
+ "version": "2.51.0",
4
4
  "description": "AI-driven agile development system for Claude Code, Cursor, Windsurf, and more",
5
5
  "keywords": [
6
6
  "agile",
@@ -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
- * Optionally registers the command/agent for PreCompact context preservation.
7
- * Outputs structured summary to reduce tool calls and startup time.
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
- // MAIN CONTEXT GATHERING
89
+ // GENERATE SUMMARY (calculated first for positioning)
88
90
  // ============================================
89
91
 
90
- const title = commandName ? `AgileFlow Context [${commandName}]` : 'AgileFlow Context';
91
- console.log(`${C.magenta}${C.bold}${title}${C.reset}`);
92
- console.log(`${C.dim}Generated: ${new Date().toISOString()}${C.reset}`);
93
-
94
- // 1. GIT STATUS
95
- section('Git Status');
96
- const branch = safeExec('git branch --show-current') || 'unknown';
97
- const status = safeExec('git status --short') || '';
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
- // 2. STATUS.JSON - Stories & Epics
112
- section('Stories & Epics');
113
- const statusJson = safeReadJSON('docs/09-agents/status.json');
114
- if (statusJson) {
115
- // Epics summary
116
- const epics = statusJson.epics || {};
117
- const epicList = Object.entries(epics);
118
- if (epicList.length > 0) {
119
- subsection('Epics');
120
- epicList.forEach(([id, epic]) => {
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
- // Stories summary by status
127
- const stories = statusJson.stories || {};
128
- const storyList = Object.entries(stories);
129
- const byStatus = {};
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
- // Priority order for display
137
- const statusOrder = ['in-progress', 'ready', 'blocked', 'draft', 'in-review', 'done'];
138
-
139
- subsection('Stories by Status');
140
- statusOrder.forEach(status => {
141
- if (byStatus[status] && byStatus[status].length > 0) {
142
- const color = status === 'in-progress' ? C.yellow :
143
- status === 'ready' ? C.green :
144
- status === 'blocked' ? C.red :
145
- status === 'done' ? C.dim : C.reset;
146
- console.log(` ${color}${status}${C.reset}: ${byStatus[status].length}`);
147
- byStatus[status].slice(0, 5).forEach(story => {
148
- console.log(` ${C.dim}${story.id}: ${story.title}${C.reset}`);
149
- });
150
- if (byStatus[status].length > 5) {
151
- console.log(` ${C.dim}... and ${byStatus[status].length - 5} more${C.reset}`);
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
- // Show READY stories prominently (these are actionable)
157
- if (byStatus['ready'] && byStatus['ready'].length > 0) {
158
- subsection(`${C.green}⭐ Ready to Implement${C.reset}`);
159
- byStatus['ready'].forEach(story => {
160
- console.log(` ${story.id}: ${story.title} (${story.epic || 'no epic'})`);
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
- } else {
164
- console.log(`${C.dim}No status.json found${C.reset}`);
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
- // 3. SESSION STATE
168
- section('Session State');
169
- const sessionState = safeReadJSON('docs/09-agents/session-state.json');
170
- if (sessionState) {
171
- const current = sessionState.current_session;
172
- if (current && current.started_at) {
173
- const started = new Date(current.started_at);
174
- const duration = Math.round((Date.now() - started.getTime()) / 60000);
175
- console.log(`Active session: ${C.green}${duration} min${C.reset}`);
176
- if (current.current_story) {
177
- console.log(`Working on: ${C.yellow}${current.current_story}${C.reset}`);
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
- console.log(`${C.dim}No active session${C.reset}`);
317
+ content += `${C.dim}No session-state.json found${C.reset}\n`;
181
318
  }
182
319
 
183
- const last = sessionState.last_session;
184
- if (last && last.ended_at) {
185
- console.log(`Last session: ${C.dim}${last.ended_at} (${last.duration_minutes || '?'} min)${C.reset}`);
186
- if (last.summary) console.log(` Summary: ${C.dim}${last.summary}${C.reset}`);
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
- // Active command (for context preservation)
190
- if (sessionState.active_command) {
191
- console.log(`Active command: ${C.cyan}${sessionState.active_command.name}${C.reset}`);
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
- // 4. DOCS STRUCTURE
198
- section('Documentation');
199
- const docsDir = 'docs';
200
- const docFolders = safeLs(docsDir).filter(f => {
201
- try {
202
- return fs.statSync(path.join(docsDir, f)).isDirectory();
203
- } catch {
204
- return false;
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
- if (docFolders.length > 0) {
209
- docFolders.forEach(folder => {
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
- let info = [];
216
- if (mdFiles.length > 0) info.push(`${mdFiles.length} md`);
217
- if (jsonFiles.length > 0) info.push(`${jsonFiles.length} json`);
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
- console.log(` ${C.dim}${folder}/${C.reset} ${info.length > 0 ? `(${info.join(', ')})` : ''}`);
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
- // 5. RESEARCH NOTES
224
- section('Research Notes');
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
- // 6. BUS MESSAGES (last 5)
241
- section('Recent Agent Messages');
242
- const busPath = 'docs/09-agents/bus/log.jsonl';
243
- const busContent = safeRead(busPath);
244
- if (busContent) {
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
- console.log(`${C.dim}No messages${C.reset}`);
419
+ content += `${C.dim}No epic files${C.reset}\n`;
259
420
  }
260
- } else {
261
- console.log(`${C.dim}No bus log found${C.reset}`);
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
- // 7. KEY FILES PRESENCE
265
- section('Key Files');
266
- const keyFiles = [
267
- { path: 'CLAUDE.md', label: 'CLAUDE.md (project instructions)' },
268
- { path: 'README.md', label: 'README.md (project overview)' },
269
- { path: 'docs/08-project/roadmap.md', label: 'Roadmap' },
270
- { path: 'docs/02-practices/README.md', label: 'Practices index' },
271
- { path: '.claude/settings.json', label: 'Claude settings' },
272
- ];
273
-
274
- keyFiles.forEach(({ path: filePath, label }) => {
275
- const exists = fs.existsSync(filePath);
276
- const icon = exists ? `${C.green}✓${C.reset}` : `${C.dim}○${C.reset}`;
277
- console.log(` ${icon} ${label}`);
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
- console.log(`${C.dim}No epic files${C.reset}`);
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
- // FOOTER
292
- console.log(`\n${C.dim}─────────────────────────────────────────${C.reset}`);
293
- console.log(`${C.dim}Context gathered in single execution. Ready for task selection.${C.reset}\n`);
448
+ console.log(contentBefore);
449
+ console.log(summary);
450
+ console.log(contentAfter);
451
+ }
@@ -8,7 +8,7 @@ model: haiku
8
8
  ## STEP 0: Gather Context
9
9
 
10
10
  ```bash
11
- node scripts/obtain-context.js accessibility
11
+ node .agileflow/scripts/obtain-context.js accessibility
12
12
  ```
13
13
 
14
14
  ---
@@ -8,7 +8,7 @@ model: haiku
8
8
  ## STEP 0: Gather Context
9
9
 
10
10
  ```bash
11
- node scripts/obtain-context.js adr-writer
11
+ node .agileflow/scripts/obtain-context.js adr-writer
12
12
  ```
13
13
 
14
14
  ---
@@ -8,7 +8,7 @@ model: haiku
8
8
  ## STEP 0: Gather Context
9
9
 
10
10
  ```bash
11
- node scripts/obtain-context.js analytics
11
+ node .agileflow/scripts/obtain-context.js analytics
12
12
  ```
13
13
 
14
14
  ---
@@ -8,7 +8,7 @@ model: haiku
8
8
  ## STEP 0: Gather Context
9
9
 
10
10
  ```bash
11
- node scripts/obtain-context.js api
11
+ node .agileflow/scripts/obtain-context.js api
12
12
  ```
13
13
 
14
14
  ---
@@ -8,7 +8,7 @@ model: haiku
8
8
  ## STEP 0: Gather Context
9
9
 
10
10
  ```bash
11
- node scripts/obtain-context.js ci
11
+ node .agileflow/scripts/obtain-context.js ci
12
12
  ```
13
13
 
14
14
  ---