agileflow 2.50.0 → 2.55.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/README.md +82 -460
- package/package.json +18 -3
- package/scripts/agileflow-configure.js +134 -63
- package/scripts/agileflow-welcome.js +161 -31
- package/scripts/generators/agent-registry.js +2 -2
- package/scripts/generators/command-registry.js +6 -6
- package/scripts/generators/index.js +2 -6
- package/scripts/generators/inject-babysit.js +9 -2
- package/scripts/generators/inject-help.js +3 -1
- package/scripts/generators/inject-readme.js +7 -3
- package/scripts/generators/skill-registry.js +5 -5
- package/scripts/get-env.js +13 -12
- package/scripts/obtain-context.js +396 -185
- package/scripts/session-coordinator.sh +232 -0
- package/scripts/session-manager.js +512 -0
- package/src/core/agents/orchestrator.md +275 -0
- package/src/core/commands/adr.md +38 -16
- package/src/core/commands/agent.md +39 -22
- package/src/core/commands/assign.md +17 -0
- package/src/core/commands/auto.md +60 -46
- package/src/core/commands/babysit.md +302 -637
- package/src/core/commands/baseline.md +20 -0
- package/src/core/commands/blockers.md +33 -48
- package/src/core/commands/board.md +19 -0
- package/src/core/commands/changelog.md +20 -0
- package/src/core/commands/ci.md +17 -0
- package/src/core/commands/context.md +43 -40
- package/src/core/commands/debt.md +76 -45
- package/src/core/commands/deploy.md +20 -0
- package/src/core/commands/deps.md +40 -46
- package/src/core/commands/diagnose.md +24 -18
- package/src/core/commands/docs.md +18 -0
- package/src/core/commands/epic.md +31 -0
- package/src/core/commands/feedback.md +33 -21
- package/src/core/commands/handoff.md +29 -0
- package/src/core/commands/help.md +16 -7
- package/src/core/commands/impact.md +31 -61
- package/src/core/commands/metrics.md +17 -35
- package/src/core/commands/packages.md +21 -0
- package/src/core/commands/pr.md +15 -0
- package/src/core/commands/readme-sync.md +42 -9
- package/src/core/commands/research.md +58 -11
- package/src/core/commands/retro.md +42 -50
- package/src/core/commands/review.md +22 -27
- package/src/core/commands/session/end.md +53 -297
- package/src/core/commands/session/history.md +38 -257
- package/src/core/commands/session/init.md +44 -446
- package/src/core/commands/session/new.md +152 -0
- package/src/core/commands/session/resume.md +51 -447
- package/src/core/commands/session/status.md +32 -244
- package/src/core/commands/sprint.md +33 -0
- package/src/core/commands/status.md +18 -0
- package/src/core/commands/story-validate.md +32 -0
- package/src/core/commands/story.md +21 -6
- package/src/core/commands/template.md +18 -0
- package/src/core/commands/tests.md +22 -0
- package/src/core/commands/update.md +72 -58
- package/src/core/commands/validate-expertise.md +25 -37
- package/src/core/commands/velocity.md +33 -74
- package/src/core/commands/verify.md +16 -0
- package/src/core/experts/documentation/expertise.yaml +16 -2
- package/src/core/skills/agileflow-retro-facilitator/SKILL.md +57 -219
- package/src/core/skills/agileflow-retro-facilitator/cookbook/4ls.md +86 -0
- package/src/core/skills/agileflow-retro-facilitator/cookbook/glad-sad-mad.md +79 -0
- package/src/core/skills/agileflow-retro-facilitator/cookbook/start-stop-continue.md +142 -0
- package/src/core/skills/agileflow-retro-facilitator/prompts/action-items.md +83 -0
- package/src/core/skills/writing-skills/SKILL.md +352 -0
- package/src/core/skills/writing-skills/testing-skills-with-subagents.md +232 -0
- package/tools/cli/agileflow-cli.js +4 -2
- package/tools/cli/commands/config.js +20 -13
- package/tools/cli/commands/doctor.js +25 -9
- package/tools/cli/commands/list.js +10 -6
- package/tools/cli/commands/setup.js +54 -3
- package/tools/cli/commands/status.js +6 -8
- package/tools/cli/commands/uninstall.js +5 -5
- package/tools/cli/commands/update.js +51 -7
- package/tools/cli/installers/core/installer.js +8 -4
- package/tools/cli/installers/ide/_base-ide.js +3 -1
- package/tools/cli/installers/ide/claude-code.js +3 -7
- package/tools/cli/installers/ide/codex.js +440 -0
- package/tools/cli/installers/ide/manager.js +2 -6
- package/tools/cli/lib/content-injector.js +3 -3
- package/tools/cli/lib/docs-setup.js +3 -2
- package/tools/cli/lib/npm-utils.js +3 -3
- package/tools/cli/lib/ui.js +7 -7
- package/tools/cli/lib/version-checker.js +3 -3
- package/tools/postinstall.js +2 -3
|
@@ -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) {
|
|
@@ -23,7 +28,11 @@ if (commandName) {
|
|
|
23
28
|
if (fs.existsSync(sessionStatePath)) {
|
|
24
29
|
try {
|
|
25
30
|
const state = JSON.parse(fs.readFileSync(sessionStatePath, 'utf8'));
|
|
26
|
-
state.active_command = {
|
|
31
|
+
state.active_command = {
|
|
32
|
+
name: commandName,
|
|
33
|
+
activated_at: new Date().toISOString(),
|
|
34
|
+
state: {},
|
|
35
|
+
};
|
|
27
36
|
fs.writeFileSync(sessionStatePath, JSON.stringify(state, null, 2) + '\n');
|
|
28
37
|
} catch (e) {
|
|
29
38
|
// Silently continue if session state can't be updated
|
|
@@ -41,6 +50,11 @@ const C = {
|
|
|
41
50
|
green: '\x1b[32m',
|
|
42
51
|
red: '\x1b[31m',
|
|
43
52
|
magenta: '\x1b[35m',
|
|
53
|
+
blue: '\x1b[34m',
|
|
54
|
+
brightCyan: '\x1b[96m',
|
|
55
|
+
brightYellow: '\x1b[93m',
|
|
56
|
+
brightGreen: '\x1b[92m',
|
|
57
|
+
brand: '\x1b[38;2;232;104;58m', // AgileFlow brand orange
|
|
44
58
|
};
|
|
45
59
|
|
|
46
60
|
function safeRead(filePath) {
|
|
@@ -75,219 +89,416 @@ function safeExec(cmd) {
|
|
|
75
89
|
}
|
|
76
90
|
}
|
|
77
91
|
|
|
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
92
|
// ============================================
|
|
87
|
-
//
|
|
93
|
+
// GENERATE SUMMARY (calculated first for positioning)
|
|
88
94
|
// ============================================
|
|
89
95
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
}
|
|
96
|
+
function generateSummary() {
|
|
97
|
+
// Box drawing characters
|
|
98
|
+
const box = {
|
|
99
|
+
tl: '╭',
|
|
100
|
+
tr: '╮',
|
|
101
|
+
bl: '╰',
|
|
102
|
+
br: '╯',
|
|
103
|
+
h: '─',
|
|
104
|
+
v: '│',
|
|
105
|
+
lT: '├',
|
|
106
|
+
rT: '┤',
|
|
107
|
+
tT: '┬',
|
|
108
|
+
bT: '┴',
|
|
109
|
+
cross: '┼',
|
|
110
|
+
};
|
|
110
111
|
|
|
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
|
-
});
|
|
112
|
+
const W = 58; // Total inner width (matches welcome script)
|
|
113
|
+
const L = 20; // Left column width
|
|
114
|
+
const R = W - L - 3; // Right column width (35 chars)
|
|
115
|
+
|
|
116
|
+
// Pad string to length, accounting for ANSI codes
|
|
117
|
+
function pad(str, len) {
|
|
118
|
+
const stripped = str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
119
|
+
const diff = len - stripped.length;
|
|
120
|
+
if (diff <= 0) return str;
|
|
121
|
+
return str + ' '.repeat(diff);
|
|
124
122
|
}
|
|
125
123
|
|
|
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
|
-
});
|
|
124
|
+
// Truncate string to max length, respecting ANSI codes
|
|
125
|
+
function truncate(str, maxLen, suffix = '..') {
|
|
126
|
+
const stripped = str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
127
|
+
if (stripped.length <= maxLen) return str;
|
|
135
128
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
129
|
+
const targetLen = maxLen - suffix.length;
|
|
130
|
+
let visibleCount = 0;
|
|
131
|
+
let cutIndex = 0;
|
|
132
|
+
let inEscape = false;
|
|
133
|
+
|
|
134
|
+
for (let i = 0; i < str.length; i++) {
|
|
135
|
+
if (str[i] === '\x1b') {
|
|
136
|
+
inEscape = true;
|
|
137
|
+
} else if (inEscape && str[i] === 'm') {
|
|
138
|
+
inEscape = false;
|
|
139
|
+
} else if (!inEscape) {
|
|
140
|
+
visibleCount++;
|
|
141
|
+
if (visibleCount >= targetLen) {
|
|
142
|
+
cutIndex = i + 1;
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
152
145
|
}
|
|
153
146
|
}
|
|
154
|
-
|
|
147
|
+
return str.substring(0, cutIndex) + suffix;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Create a row with auto-truncation
|
|
151
|
+
function row(left, right, leftColor = '', rightColor = '') {
|
|
152
|
+
const leftStr = `${leftColor}${left}${leftColor ? C.reset : ''}`;
|
|
153
|
+
const rightTrunc = truncate(right, R);
|
|
154
|
+
const rightStr = `${rightColor}${rightTrunc}${rightColor ? C.reset : ''}`;
|
|
155
|
+
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`;
|
|
156
|
+
}
|
|
155
157
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
158
|
+
const divider = () =>
|
|
159
|
+
`${C.dim}${box.lT}${box.h.repeat(L + 2)}${box.cross}${box.h.repeat(R + 2)}${box.rT}${C.reset}\n`;
|
|
160
|
+
const headerTopBorder = `${C.dim}${box.tl}${box.h.repeat(W + 2)}${box.tr}${C.reset}\n`;
|
|
161
|
+
const headerDivider = `${C.dim}${box.lT}${box.h.repeat(L + 2)}${box.tT}${box.h.repeat(R + 2)}${box.rT}${C.reset}\n`;
|
|
162
|
+
const bottomBorder = `${C.dim}${box.bl}${box.h.repeat(L + 2)}${box.bT}${box.h.repeat(R + 2)}${box.br}${C.reset}\n`;
|
|
163
|
+
|
|
164
|
+
// Gather data
|
|
165
|
+
const branch = safeExec('git branch --show-current') || 'unknown';
|
|
166
|
+
const lastCommitShort = safeExec('git log -1 --format="%h"') || '?';
|
|
167
|
+
const lastCommitMsg = safeExec('git log -1 --format="%s"') || 'no commits';
|
|
168
|
+
const statusLines = (safeExec('git status --short') || '').split('\n').filter(Boolean);
|
|
169
|
+
const statusJson = safeReadJSON('docs/09-agents/status.json');
|
|
170
|
+
const sessionState = safeReadJSON('docs/09-agents/session-state.json');
|
|
171
|
+
const researchFiles = safeLs('docs/10-research')
|
|
172
|
+
.filter(f => f.endsWith('.md') && f !== 'README.md')
|
|
173
|
+
.sort()
|
|
174
|
+
.reverse();
|
|
175
|
+
const epicFiles = safeLs('docs/05-epics').filter(f => f.endsWith('.md') && f !== 'README.md');
|
|
176
|
+
|
|
177
|
+
// Count stories by status
|
|
178
|
+
const byStatus = {};
|
|
179
|
+
const readyStories = [];
|
|
180
|
+
if (statusJson && statusJson.stories) {
|
|
181
|
+
Object.entries(statusJson.stories).forEach(([id, story]) => {
|
|
182
|
+
const s = story.status || 'unknown';
|
|
183
|
+
byStatus[s] = (byStatus[s] || 0) + 1;
|
|
184
|
+
if (s === 'ready') readyStories.push(id);
|
|
161
185
|
});
|
|
162
186
|
}
|
|
163
|
-
|
|
164
|
-
|
|
187
|
+
|
|
188
|
+
// Session info
|
|
189
|
+
let sessionDuration = null;
|
|
190
|
+
let currentStory = null;
|
|
191
|
+
if (sessionState && sessionState.current_session && sessionState.current_session.started_at) {
|
|
192
|
+
const started = new Date(sessionState.current_session.started_at);
|
|
193
|
+
sessionDuration = Math.round((Date.now() - started.getTime()) / 60000);
|
|
194
|
+
currentStory = sessionState.current_session.current_story;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Build table
|
|
198
|
+
let summary = '\n';
|
|
199
|
+
summary += headerTopBorder;
|
|
200
|
+
|
|
201
|
+
// Header row (full width, no column divider)
|
|
202
|
+
const title = commandName ? `Context [${commandName}]` : 'Context Summary';
|
|
203
|
+
const branchColor = branch === 'main' ? C.green : branch.startsWith('fix') ? C.red : C.cyan;
|
|
204
|
+
const maxBranchLen = 20;
|
|
205
|
+
const branchDisplay =
|
|
206
|
+
branch.length > maxBranchLen ? branch.substring(0, maxBranchLen - 2) + '..' : branch;
|
|
207
|
+
const header = `${C.brand}${C.bold}${title}${C.reset} ${branchColor}${branchDisplay}${C.reset} ${C.dim}(${lastCommitShort})${C.reset}`;
|
|
208
|
+
summary += `${C.dim}${box.v}${C.reset} ${pad(header, W)} ${C.dim}${box.v}${C.reset}\n`;
|
|
209
|
+
|
|
210
|
+
summary += headerDivider;
|
|
211
|
+
|
|
212
|
+
// Story counts with colorful labels
|
|
213
|
+
summary += row(
|
|
214
|
+
'In Progress',
|
|
215
|
+
byStatus['in-progress'] ? `${byStatus['in-progress']}` : '0',
|
|
216
|
+
C.yellow,
|
|
217
|
+
byStatus['in-progress'] ? C.brightYellow : C.dim
|
|
218
|
+
);
|
|
219
|
+
summary += row(
|
|
220
|
+
'Blocked',
|
|
221
|
+
byStatus['blocked'] ? `${byStatus['blocked']}` : '0',
|
|
222
|
+
C.red,
|
|
223
|
+
byStatus['blocked'] ? C.red : C.dim
|
|
224
|
+
);
|
|
225
|
+
summary += row(
|
|
226
|
+
'Ready',
|
|
227
|
+
byStatus['ready'] ? `${byStatus['ready']}` : '0',
|
|
228
|
+
C.cyan,
|
|
229
|
+
byStatus['ready'] ? C.brightCyan : C.dim
|
|
230
|
+
);
|
|
231
|
+
const completedColor = `${C.bold}${C.green}`;
|
|
232
|
+
summary += row(
|
|
233
|
+
'Completed',
|
|
234
|
+
byStatus['done'] ? `${byStatus['done']}` : '0',
|
|
235
|
+
completedColor,
|
|
236
|
+
byStatus['done'] ? completedColor : C.dim
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
summary += divider();
|
|
240
|
+
|
|
241
|
+
// Git status
|
|
242
|
+
const uncommittedStatus =
|
|
243
|
+
statusLines.length > 0 ? `${statusLines.length} uncommitted` : '✓ clean';
|
|
244
|
+
summary += row('Git', uncommittedStatus, C.blue, statusLines.length > 0 ? C.yellow : C.green);
|
|
245
|
+
|
|
246
|
+
// Session
|
|
247
|
+
const sessionText = sessionDuration !== null ? `${sessionDuration} min active` : 'no session';
|
|
248
|
+
summary += row('Session', sessionText, C.blue, sessionDuration !== null ? C.brightGreen : C.dim);
|
|
249
|
+
|
|
250
|
+
// Current story
|
|
251
|
+
const storyText = currentStory ? currentStory : 'none';
|
|
252
|
+
summary += row('Working on', storyText, C.blue, currentStory ? C.brightYellow : C.dim);
|
|
253
|
+
|
|
254
|
+
// Ready stories (if any)
|
|
255
|
+
if (readyStories.length > 0) {
|
|
256
|
+
summary += row('⭐ Up Next', readyStories.slice(0, 3).join(', '), C.brightCyan, C.cyan);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
summary += divider();
|
|
260
|
+
|
|
261
|
+
// Key files
|
|
262
|
+
const keyFileChecks = [
|
|
263
|
+
{ path: 'CLAUDE.md', label: 'CLAUDE' },
|
|
264
|
+
{ path: 'README.md', label: 'README' },
|
|
265
|
+
{ path: 'docs/04-architecture/README.md', label: 'arch' },
|
|
266
|
+
{ path: 'docs/02-practices/README.md', label: 'practices' },
|
|
267
|
+
];
|
|
268
|
+
const keyFileStatus = keyFileChecks
|
|
269
|
+
.map(f => {
|
|
270
|
+
const exists = fs.existsSync(f.path);
|
|
271
|
+
return exists ? `${C.brightGreen}✓${C.reset}${f.label}` : `${C.dim}○${f.label}${C.reset}`;
|
|
272
|
+
})
|
|
273
|
+
.join(' ');
|
|
274
|
+
summary += row('Key files', keyFileStatus, C.magenta, '');
|
|
275
|
+
|
|
276
|
+
// Research
|
|
277
|
+
const researchText = researchFiles.length > 0 ? `${researchFiles.length} notes` : 'none';
|
|
278
|
+
summary += row('Research', researchText, C.magenta, researchFiles.length > 0 ? C.cyan : C.dim);
|
|
279
|
+
|
|
280
|
+
// Epics
|
|
281
|
+
const epicText = epicFiles.length > 0 ? `${epicFiles.length} epics` : 'none';
|
|
282
|
+
summary += row('Epics', epicText, C.magenta, epicFiles.length > 0 ? C.cyan : C.dim);
|
|
283
|
+
|
|
284
|
+
summary += divider();
|
|
285
|
+
|
|
286
|
+
// Last commit
|
|
287
|
+
summary += row(
|
|
288
|
+
'Last commit',
|
|
289
|
+
`${C.yellow}${lastCommitShort}${C.reset} ${lastCommitMsg}`,
|
|
290
|
+
C.dim,
|
|
291
|
+
''
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
summary += bottomBorder;
|
|
295
|
+
|
|
296
|
+
summary += `${C.dim}Full context continues below (Claude sees all)...${C.reset}\n\n`;
|
|
297
|
+
|
|
298
|
+
return summary;
|
|
165
299
|
}
|
|
166
300
|
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
301
|
+
// ============================================
|
|
302
|
+
// GENERATE FULL CONTENT
|
|
303
|
+
// ============================================
|
|
304
|
+
|
|
305
|
+
function generateFullContent() {
|
|
306
|
+
let content = '';
|
|
307
|
+
|
|
308
|
+
const title = commandName ? `AgileFlow Context [${commandName}]` : 'AgileFlow Context';
|
|
309
|
+
content += `${C.magenta}${C.bold}${title}${C.reset}\n`;
|
|
310
|
+
content += `${C.dim}Generated: ${new Date().toISOString()}${C.reset}\n`;
|
|
311
|
+
|
|
312
|
+
// 1. GIT STATUS
|
|
313
|
+
content += `\n${C.cyan}${C.bold}═══ Git Status ═══${C.reset}\n`;
|
|
314
|
+
const branch = safeExec('git branch --show-current') || 'unknown';
|
|
315
|
+
const status = safeExec('git status --short') || '';
|
|
316
|
+
const statusLines = status.split('\n').filter(Boolean);
|
|
317
|
+
const lastCommit = safeExec('git log -1 --format="%h %s"') || 'no commits';
|
|
318
|
+
|
|
319
|
+
content += `Branch: ${C.green}${branch}${C.reset}\n`;
|
|
320
|
+
content += `Last commit: ${C.dim}${lastCommit}${C.reset}\n`;
|
|
321
|
+
if (statusLines.length > 0) {
|
|
322
|
+
content += `Uncommitted: ${C.yellow}${statusLines.length} file(s)${C.reset}\n`;
|
|
323
|
+
statusLines.slice(0, 10).forEach(line => (content += ` ${C.dim}${line}${C.reset}\n`));
|
|
324
|
+
if (statusLines.length > 10)
|
|
325
|
+
content += ` ${C.dim}... and ${statusLines.length - 10} more${C.reset}\n`;
|
|
326
|
+
} else {
|
|
327
|
+
content += `Uncommitted: ${C.green}clean${C.reset}\n`;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// 2. STATUS.JSON - Full Content
|
|
331
|
+
content += `\n${C.cyan}${C.bold}═══ Status.json (Full Content) ═══${C.reset}\n`;
|
|
332
|
+
const statusJsonPath = 'docs/09-agents/status.json';
|
|
333
|
+
const statusJson = safeReadJSON(statusJsonPath);
|
|
334
|
+
|
|
335
|
+
if (statusJson) {
|
|
336
|
+
content += `${C.dim}${'─'.repeat(50)}${C.reset}\n`;
|
|
337
|
+
content +=
|
|
338
|
+
JSON.stringify(statusJson, null, 2)
|
|
339
|
+
.split('\n')
|
|
340
|
+
.map(l => ` ${l}`)
|
|
341
|
+
.join('\n') + '\n';
|
|
342
|
+
content += `${C.dim}${'─'.repeat(50)}${C.reset}\n`;
|
|
343
|
+
} else {
|
|
344
|
+
content += `${C.dim}No status.json found${C.reset}\n`;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// 3. SESSION STATE
|
|
348
|
+
content += `\n${C.cyan}${C.bold}═══ Session State ═══${C.reset}\n`;
|
|
349
|
+
const sessionState = safeReadJSON('docs/09-agents/session-state.json');
|
|
350
|
+
if (sessionState) {
|
|
351
|
+
const current = sessionState.current_session;
|
|
352
|
+
if (current && current.started_at) {
|
|
353
|
+
const started = new Date(current.started_at);
|
|
354
|
+
const duration = Math.round((Date.now() - started.getTime()) / 60000);
|
|
355
|
+
content += `Active session: ${C.green}${duration} min${C.reset}\n`;
|
|
356
|
+
if (current.current_story) {
|
|
357
|
+
content += `Working on: ${C.yellow}${current.current_story}${C.reset}\n`;
|
|
358
|
+
}
|
|
359
|
+
} else {
|
|
360
|
+
content += `${C.dim}No active session${C.reset}\n`;
|
|
361
|
+
}
|
|
362
|
+
if (sessionState.active_command) {
|
|
363
|
+
content += `Active command: ${C.cyan}${sessionState.active_command.name}${C.reset}\n`;
|
|
178
364
|
}
|
|
179
365
|
} else {
|
|
180
|
-
|
|
366
|
+
content += `${C.dim}No session-state.json found${C.reset}\n`;
|
|
181
367
|
}
|
|
182
368
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
369
|
+
// 4. DOCS STRUCTURE
|
|
370
|
+
content += `\n${C.cyan}${C.bold}═══ Documentation ═══${C.reset}\n`;
|
|
371
|
+
const docsDir = 'docs';
|
|
372
|
+
const docFolders = safeLs(docsDir).filter(f => {
|
|
373
|
+
try {
|
|
374
|
+
return fs.statSync(path.join(docsDir, f)).isDirectory();
|
|
375
|
+
} catch {
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
if (docFolders.length > 0) {
|
|
381
|
+
docFolders.forEach(folder => {
|
|
382
|
+
const folderPath = path.join(docsDir, folder);
|
|
383
|
+
const files = safeLs(folderPath);
|
|
384
|
+
const mdFiles = files.filter(f => f.endsWith('.md'));
|
|
385
|
+
const jsonFiles = files.filter(f => f.endsWith('.json') || f.endsWith('.jsonl'));
|
|
386
|
+
const info = [];
|
|
387
|
+
if (mdFiles.length > 0) info.push(`${mdFiles.length} md`);
|
|
388
|
+
if (jsonFiles.length > 0) info.push(`${jsonFiles.length} json`);
|
|
389
|
+
content += ` ${C.dim}${folder}/${C.reset} ${info.length > 0 ? `(${info.join(', ')})` : ''}\n`;
|
|
390
|
+
});
|
|
187
391
|
}
|
|
188
392
|
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
|
|
393
|
+
// 5. RESEARCH NOTES - List + Full content of most recent
|
|
394
|
+
content += `\n${C.cyan}${C.bold}═══ Research Notes ═══${C.reset}\n`;
|
|
395
|
+
const researchDir = 'docs/10-research';
|
|
396
|
+
const researchFiles = safeLs(researchDir).filter(f => f.endsWith('.md') && f !== 'README.md');
|
|
397
|
+
if (researchFiles.length > 0) {
|
|
398
|
+
researchFiles.sort().reverse();
|
|
399
|
+
content += `${C.dim}───${C.reset} Available Research Notes\n`;
|
|
400
|
+
researchFiles.forEach(file => (content += ` ${C.dim}${file}${C.reset}\n`));
|
|
401
|
+
|
|
402
|
+
const mostRecentFile = researchFiles[0];
|
|
403
|
+
const mostRecentPath = path.join(researchDir, mostRecentFile);
|
|
404
|
+
const mostRecentContent = safeRead(mostRecentPath);
|
|
405
|
+
|
|
406
|
+
if (mostRecentContent) {
|
|
407
|
+
content += `\n${C.green}📄 Most Recent: ${mostRecentFile}${C.reset}\n`;
|
|
408
|
+
content += `${C.dim}${'─'.repeat(60)}${C.reset}\n`;
|
|
409
|
+
content += mostRecentContent + '\n';
|
|
410
|
+
content += `${C.dim}${'─'.repeat(60)}${C.reset}\n`;
|
|
411
|
+
}
|
|
412
|
+
} else {
|
|
413
|
+
content += `${C.dim}No research notes${C.reset}\n`;
|
|
192
414
|
}
|
|
193
|
-
} else {
|
|
194
|
-
console.log(`${C.dim}No session-state.json found${C.reset}`);
|
|
195
|
-
}
|
|
196
415
|
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
const
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
416
|
+
// 6. BUS MESSAGES
|
|
417
|
+
content += `\n${C.cyan}${C.bold}═══ Recent Agent Messages ═══${C.reset}\n`;
|
|
418
|
+
const busPath = 'docs/09-agents/bus/log.jsonl';
|
|
419
|
+
const busContent = safeRead(busPath);
|
|
420
|
+
if (busContent) {
|
|
421
|
+
const lines = busContent.trim().split('\n').filter(Boolean);
|
|
422
|
+
const recent = lines.slice(-5);
|
|
423
|
+
if (recent.length > 0) {
|
|
424
|
+
recent.forEach(line => {
|
|
425
|
+
try {
|
|
426
|
+
const msg = JSON.parse(line);
|
|
427
|
+
const time = msg.timestamp ? new Date(msg.timestamp).toLocaleTimeString() : '?';
|
|
428
|
+
content += ` ${C.dim}[${time}]${C.reset} ${msg.from || '?'}: ${msg.type || msg.message || '?'}\n`;
|
|
429
|
+
} catch {
|
|
430
|
+
content += ` ${C.dim}${line.substring(0, 80)}...${C.reset}\n`;
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
} else {
|
|
434
|
+
content += `${C.dim}No messages${C.reset}\n`;
|
|
435
|
+
}
|
|
436
|
+
} else {
|
|
437
|
+
content += `${C.dim}No bus log found${C.reset}\n`;
|
|
205
438
|
}
|
|
206
|
-
});
|
|
207
439
|
|
|
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'));
|
|
440
|
+
// 7. KEY FILES - Full content
|
|
441
|
+
content += `\n${C.cyan}${C.bold}═══ Key Context Files (Full Content) ═══${C.reset}\n`;
|
|
214
442
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
443
|
+
const keyFilesToRead = [
|
|
444
|
+
{ path: 'CLAUDE.md', label: 'CLAUDE.md (Project Instructions)' },
|
|
445
|
+
{ path: 'README.md', label: 'README.md (Project Overview)' },
|
|
446
|
+
{ path: 'docs/04-architecture/README.md', label: 'Architecture Index' },
|
|
447
|
+
{ path: 'docs/02-practices/README.md', label: 'Practices Index' },
|
|
448
|
+
{ path: 'docs/08-project/roadmap.md', label: 'Roadmap' },
|
|
449
|
+
];
|
|
218
450
|
|
|
219
|
-
|
|
451
|
+
keyFilesToRead.forEach(({ path: filePath, label }) => {
|
|
452
|
+
const fileContent = safeRead(filePath);
|
|
453
|
+
if (fileContent) {
|
|
454
|
+
content += `\n${C.green}✓ ${label}${C.reset} ${C.dim}(${filePath})${C.reset}\n`;
|
|
455
|
+
content += `${C.dim}${'─'.repeat(60)}${C.reset}\n`;
|
|
456
|
+
content += fileContent + '\n';
|
|
457
|
+
content += `${C.dim}${'─'.repeat(60)}${C.reset}\n`;
|
|
458
|
+
} else {
|
|
459
|
+
content += `${C.dim}○ ${label} (not found)${C.reset}\n`;
|
|
460
|
+
}
|
|
220
461
|
});
|
|
221
|
-
}
|
|
222
462
|
|
|
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
|
-
}
|
|
463
|
+
const settingsExists = fs.existsSync('.claude/settings.json');
|
|
464
|
+
content += `\n ${settingsExists ? `${C.green}✓${C.reset}` : `${C.dim}○${C.reset}`} .claude/settings.json\n`;
|
|
239
465
|
|
|
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
|
-
});
|
|
466
|
+
// 8. EPICS FOLDER
|
|
467
|
+
content += `\n${C.cyan}${C.bold}═══ Epic Files ═══${C.reset}\n`;
|
|
468
|
+
const epicFiles = safeLs('docs/05-epics').filter(f => f.endsWith('.md') && f !== 'README.md');
|
|
469
|
+
if (epicFiles.length > 0) {
|
|
470
|
+
epicFiles.forEach(file => (content += ` ${C.dim}${file}${C.reset}\n`));
|
|
257
471
|
} else {
|
|
258
|
-
|
|
472
|
+
content += `${C.dim}No epic files${C.reset}\n`;
|
|
259
473
|
}
|
|
260
|
-
|
|
261
|
-
|
|
474
|
+
|
|
475
|
+
// FOOTER
|
|
476
|
+
content += `\n${C.dim}─────────────────────────────────────────${C.reset}\n`;
|
|
477
|
+
content += `${C.dim}Context gathered in single execution. Claude has full context.${C.reset}\n`;
|
|
478
|
+
|
|
479
|
+
return content;
|
|
262
480
|
}
|
|
263
481
|
|
|
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
|
-
});
|
|
482
|
+
// ============================================
|
|
483
|
+
// MAIN: Output with smart summary positioning
|
|
484
|
+
// ============================================
|
|
485
|
+
|
|
486
|
+
const summary = generateSummary();
|
|
487
|
+
const fullContent = generateFullContent();
|
|
488
|
+
|
|
489
|
+
const summaryLength = summary.length;
|
|
490
|
+
const cutoffPoint = DISPLAY_LIMIT - summaryLength;
|
|
491
|
+
|
|
492
|
+
if (fullContent.length <= cutoffPoint) {
|
|
493
|
+
// Full content fits before summary - just output everything
|
|
494
|
+
console.log(fullContent);
|
|
495
|
+
console.log(summary);
|
|
287
496
|
} else {
|
|
288
|
-
|
|
289
|
-
|
|
497
|
+
// Split: content before cutoff + summary + content after cutoff
|
|
498
|
+
const contentBefore = fullContent.substring(0, cutoffPoint);
|
|
499
|
+
const contentAfter = fullContent.substring(cutoffPoint);
|
|
290
500
|
|
|
291
|
-
|
|
292
|
-
console.log(
|
|
293
|
-
console.log(
|
|
501
|
+
console.log(contentBefore);
|
|
502
|
+
console.log(summary);
|
|
503
|
+
console.log(contentAfter);
|
|
504
|
+
}
|