@ginkoai/cli 2.0.5 → 2.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.
- package/dist/commands/epic/index.d.ts +29 -0
- package/dist/commands/epic/index.d.ts.map +1 -0
- package/dist/commands/epic/index.js +94 -0
- package/dist/commands/epic/index.js.map +1 -0
- package/dist/commands/epic/status.d.ts +40 -0
- package/dist/commands/epic/status.d.ts.map +1 -0
- package/dist/commands/epic/status.js +244 -0
- package/dist/commands/epic/status.js.map +1 -0
- package/dist/commands/graph/api-client.d.ts +209 -0
- package/dist/commands/graph/api-client.d.ts.map +1 -1
- package/dist/commands/graph/api-client.js +125 -0
- package/dist/commands/graph/api-client.js.map +1 -1
- package/dist/commands/graph/load.d.ts.map +1 -1
- package/dist/commands/graph/load.js +40 -2
- package/dist/commands/graph/load.js.map +1 -1
- package/dist/commands/migrate/index.d.ts +27 -0
- package/dist/commands/migrate/index.d.ts.map +1 -0
- package/dist/commands/migrate/index.js +76 -0
- package/dist/commands/migrate/index.js.map +1 -0
- package/dist/commands/migrate/status-migration.d.ts +58 -0
- package/dist/commands/migrate/status-migration.d.ts.map +1 -0
- package/dist/commands/migrate/status-migration.js +323 -0
- package/dist/commands/migrate/status-migration.js.map +1 -0
- package/dist/commands/sprint/index.d.ts.map +1 -1
- package/dist/commands/sprint/index.js +4 -0
- package/dist/commands/sprint/index.js.map +1 -1
- package/dist/commands/sprint/status.d.ts +42 -0
- package/dist/commands/sprint/status.d.ts.map +1 -0
- package/dist/commands/sprint/status.js +278 -0
- package/dist/commands/sprint/status.js.map +1 -0
- package/dist/commands/start/start-reflection.d.ts +39 -0
- package/dist/commands/start/start-reflection.d.ts.map +1 -1
- package/dist/commands/start/start-reflection.js +311 -91
- package/dist/commands/start/start-reflection.js.map +1 -1
- package/dist/commands/sync/sprint-syncer.d.ts +19 -12
- package/dist/commands/sync/sprint-syncer.d.ts.map +1 -1
- package/dist/commands/sync/sprint-syncer.js +58 -140
- package/dist/commands/sync/sprint-syncer.js.map +1 -1
- package/dist/commands/sync/sync-command.d.ts.map +1 -1
- package/dist/commands/sync/sync-command.js +6 -18
- package/dist/commands/sync/sync-command.js.map +1 -1
- package/dist/commands/task/index.d.ts +25 -0
- package/dist/commands/task/index.d.ts.map +1 -0
- package/dist/commands/task/index.js +100 -0
- package/dist/commands/task/index.js.map +1 -0
- package/dist/commands/task/status.d.ts +43 -0
- package/dist/commands/task/status.d.ts.map +1 -0
- package/dist/commands/task/status.js +301 -0
- package/dist/commands/task/status.js.map +1 -0
- package/dist/index.js +12 -29
- package/dist/index.js.map +1 -1
- package/dist/lib/context-loader-events.d.ts +1 -0
- package/dist/lib/context-loader-events.d.ts.map +1 -1
- package/dist/lib/context-loader-events.js +28 -41
- package/dist/lib/context-loader-events.js.map +1 -1
- package/dist/lib/output-formatter.d.ts +12 -4
- package/dist/lib/output-formatter.d.ts.map +1 -1
- package/dist/lib/output-formatter.js +186 -14
- package/dist/lib/output-formatter.js.map +1 -1
- package/dist/lib/pending-updates.d.ts +148 -0
- package/dist/lib/pending-updates.d.ts.map +1 -0
- package/dist/lib/pending-updates.js +301 -0
- package/dist/lib/pending-updates.js.map +1 -0
- package/dist/lib/sprint-loader.d.ts +86 -14
- package/dist/lib/sprint-loader.d.ts.map +1 -1
- package/dist/lib/sprint-loader.js +293 -98
- package/dist/lib/sprint-loader.js.map +1 -1
- package/dist/lib/state-cache.d.ts +142 -0
- package/dist/lib/state-cache.d.ts.map +1 -0
- package/dist/lib/state-cache.js +259 -0
- package/dist/lib/state-cache.js.map +1 -0
- package/dist/lib/task-graph-sync.d.ts +105 -0
- package/dist/lib/task-graph-sync.d.ts.map +1 -0
- package/dist/lib/task-graph-sync.js +178 -0
- package/dist/lib/task-graph-sync.js.map +1 -0
- package/dist/lib/task-parser.d.ts +107 -0
- package/dist/lib/task-parser.d.ts.map +1 -0
- package/dist/lib/task-parser.js +384 -0
- package/dist/lib/task-parser.js.map +1 -0
- package/dist/templates/ai-instructions-template.d.ts.map +1 -1
- package/dist/templates/ai-instructions-template.js +7 -5
- package/dist/templates/ai-instructions-template.js.map +1 -1
- package/dist/templates/epic-template.md +0 -2
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +7 -5
- package/dist/types/config.js.map +1 -1
- package/dist/utils/synthesis.d.ts +4 -0
- package/dist/utils/synthesis.d.ts.map +1 -1
- package/dist/utils/synthesis.js +12 -18
- package/dist/utils/synthesis.js.map +1 -1
- package/package.json +1 -1
|
@@ -20,8 +20,8 @@ import { SessionLogManager } from '../../core/session-log-manager.js';
|
|
|
20
20
|
import { SessionSynthesizer } from '../../utils/synthesis.js';
|
|
21
21
|
import { loadContextStrategic, formatContextSummary } from '../../utils/context-loader.js';
|
|
22
22
|
import { initializeQueue } from '../../lib/event-queue.js';
|
|
23
|
-
import { formatContextSummary as formatEventContextSummary } from '../../lib/context-loader-events.js';
|
|
24
23
|
import { loadSprintChecklist, formatSprintChecklist, formatCurrentTaskDetails, detectSprintProgression } from '../../lib/sprint-loader.js';
|
|
24
|
+
import { loadStateCache, saveStateCache, checkCacheStaleness } from '../../lib/state-cache.js';
|
|
25
25
|
import { formatHumanOutput, formatVerboseOutput, formatAIContextJSONL, formatTableOutput, formatEpicComplete, formatSprintProgressionPrompt } from '../../lib/output-formatter.js';
|
|
26
26
|
import { getUserCurrentSprint, setUserCurrentSprint, createAssignmentFromFile, getSprintFileFromAssignment } from '../../lib/user-sprint.js';
|
|
27
27
|
import { GraphApiClient } from '../graph/api-client.js';
|
|
@@ -157,11 +157,222 @@ export class StartReflectionCommand extends ReflectionCommand {
|
|
|
157
157
|
source: 'graph', // Mark source for debugging
|
|
158
158
|
};
|
|
159
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* Load sprint state from graph API with cache fallback (EPIC-015 Sprint 2 Task 3)
|
|
162
|
+
*
|
|
163
|
+
* Data flow:
|
|
164
|
+
* 1. Try to fetch from graph API first
|
|
165
|
+
* 2. On success: save to cache for offline use
|
|
166
|
+
* 3. On failure: load from cache
|
|
167
|
+
*
|
|
168
|
+
* @param graphId - The graph namespace ID
|
|
169
|
+
* @returns Sprint data and source indicator ('graph' | 'cache' | null)
|
|
170
|
+
*/
|
|
171
|
+
async loadSprintStateFromGraph(graphId) {
|
|
172
|
+
const client = new GraphApiClient();
|
|
173
|
+
try {
|
|
174
|
+
// Try to fetch from graph API first
|
|
175
|
+
const graphResponse = await client.getActiveSprint(graphId);
|
|
176
|
+
// Convert API response to cache format
|
|
177
|
+
const activeSprintData = {
|
|
178
|
+
sprintId: graphResponse.sprint.id,
|
|
179
|
+
sprintName: graphResponse.sprint.name,
|
|
180
|
+
epicId: graphResponse.sprint.id.split('_s')[0] || 'unknown', // Extract epic from e011_s01
|
|
181
|
+
progress: {
|
|
182
|
+
completed: graphResponse.stats.completedTasks,
|
|
183
|
+
total: graphResponse.stats.totalTasks,
|
|
184
|
+
percentage: graphResponse.stats.progressPercentage,
|
|
185
|
+
},
|
|
186
|
+
currentTask: graphResponse.nextTask ? {
|
|
187
|
+
taskId: graphResponse.nextTask.id,
|
|
188
|
+
taskName: graphResponse.nextTask.title,
|
|
189
|
+
status: this.mapGraphStatusToTaskStatus(graphResponse.nextTask.status),
|
|
190
|
+
} : undefined,
|
|
191
|
+
nextTask: graphResponse.nextTask ? {
|
|
192
|
+
taskId: graphResponse.nextTask.id,
|
|
193
|
+
taskName: graphResponse.nextTask.title,
|
|
194
|
+
} : undefined,
|
|
195
|
+
};
|
|
196
|
+
// Save to cache for offline use
|
|
197
|
+
await saveStateCache(activeSprintData, graphId);
|
|
198
|
+
return {
|
|
199
|
+
data: activeSprintData,
|
|
200
|
+
source: 'graph',
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
// Graph unavailable - try to load from cache
|
|
205
|
+
const cache = await loadStateCache();
|
|
206
|
+
if (cache && cache.graph_id === graphId) {
|
|
207
|
+
const staleness = checkCacheStaleness(cache);
|
|
208
|
+
return {
|
|
209
|
+
data: cache.active_sprint,
|
|
210
|
+
source: 'cache',
|
|
211
|
+
staleness,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
// No cache available
|
|
215
|
+
return {
|
|
216
|
+
data: null,
|
|
217
|
+
source: 'cache',
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Load sprint data from cache only (for offline fallback)
|
|
223
|
+
*/
|
|
224
|
+
async loadFromCacheOnly(graphId) {
|
|
225
|
+
const cache = await loadStateCache();
|
|
226
|
+
if (cache && cache.graph_id === graphId) {
|
|
227
|
+
const staleness = checkCacheStaleness(cache);
|
|
228
|
+
return {
|
|
229
|
+
data: cache.active_sprint,
|
|
230
|
+
staleness,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
return { data: null };
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Convert cached ActiveSprintData to SprintChecklist format
|
|
237
|
+
*/
|
|
238
|
+
convertCachedDataToChecklist(cachedData) {
|
|
239
|
+
// Build minimal checklist from cached data
|
|
240
|
+
const tasks = [];
|
|
241
|
+
// If we have a current task, add it
|
|
242
|
+
if (cachedData.currentTask) {
|
|
243
|
+
tasks.push({
|
|
244
|
+
id: cachedData.currentTask.taskId,
|
|
245
|
+
title: cachedData.currentTask.taskName,
|
|
246
|
+
state: this.mapStatusToTaskState(cachedData.currentTask.status || 'todo'),
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
const completed = cachedData.progress?.completed || 0;
|
|
250
|
+
const total = cachedData.progress?.total || tasks.length;
|
|
251
|
+
return {
|
|
252
|
+
name: cachedData.sprintName || cachedData.sprintId,
|
|
253
|
+
file: '', // No file when loading from cache
|
|
254
|
+
tasks,
|
|
255
|
+
progress: {
|
|
256
|
+
complete: completed,
|
|
257
|
+
inProgress: cachedData.currentTask ? 1 : 0,
|
|
258
|
+
paused: 0,
|
|
259
|
+
todo: Math.max(0, total - completed - (cachedData.currentTask ? 1 : 0)),
|
|
260
|
+
total,
|
|
261
|
+
},
|
|
262
|
+
currentTask: cachedData.currentTask ? {
|
|
263
|
+
id: cachedData.currentTask.taskId,
|
|
264
|
+
title: cachedData.currentTask.taskName,
|
|
265
|
+
state: this.mapStatusToTaskState(cachedData.currentTask.status || 'todo'),
|
|
266
|
+
} : undefined,
|
|
267
|
+
recentCompletions: [],
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Map graph API status string to TaskStatus enum
|
|
272
|
+
*/
|
|
273
|
+
mapGraphStatusToTaskStatus(status) {
|
|
274
|
+
switch (status?.toLowerCase()) {
|
|
275
|
+
case 'complete':
|
|
276
|
+
case 'completed':
|
|
277
|
+
return 'completed';
|
|
278
|
+
case 'in_progress':
|
|
279
|
+
case 'in-progress':
|
|
280
|
+
return 'in_progress';
|
|
281
|
+
default:
|
|
282
|
+
return 'pending';
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Map graph status to TaskState enum for SprintChecklist
|
|
287
|
+
*/
|
|
288
|
+
mapStatusToTaskState(status) {
|
|
289
|
+
switch (status?.toLowerCase()) {
|
|
290
|
+
case 'complete':
|
|
291
|
+
case 'completed':
|
|
292
|
+
return 'complete';
|
|
293
|
+
case 'in_progress':
|
|
294
|
+
case 'in-progress':
|
|
295
|
+
return 'in_progress';
|
|
296
|
+
case 'paused':
|
|
297
|
+
case 'sleeping':
|
|
298
|
+
return 'paused';
|
|
299
|
+
case 'blocked':
|
|
300
|
+
return 'in_progress'; // Treat blocked as in_progress for display
|
|
301
|
+
default:
|
|
302
|
+
return 'todo';
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Merge graph status with local file content (EPIC-015 Sprint 2 Task 3)
|
|
307
|
+
*
|
|
308
|
+
* Graph API is authoritative for STATUS (progress, task states).
|
|
309
|
+
* Local file provides CONTENT (descriptions, acceptance criteria, ADRs).
|
|
310
|
+
*
|
|
311
|
+
* @param graphData - Status data from graph API (or cache)
|
|
312
|
+
* @param fileContent - Content from local sprint file
|
|
313
|
+
* @returns Merged SprintChecklist with graph status + file content
|
|
314
|
+
*/
|
|
315
|
+
mergeGraphStatusWithContent(graphData, fileContent, graphTasks) {
|
|
316
|
+
// Build task list: merge graph status with file content
|
|
317
|
+
const tasks = fileContent.tasks.map((fileTask) => {
|
|
318
|
+
// Find matching task in graph data
|
|
319
|
+
const graphTask = graphTasks?.find(gt => gt.id === fileTask.id);
|
|
320
|
+
const status = graphTask?.status || 'not_started';
|
|
321
|
+
return {
|
|
322
|
+
id: fileTask.id,
|
|
323
|
+
title: fileTask.title,
|
|
324
|
+
state: this.mapStatusToTaskState(status),
|
|
325
|
+
files: fileTask.files || graphTask?.files || [],
|
|
326
|
+
effort: fileTask.effort,
|
|
327
|
+
priority: fileTask.priority || graphTask?.priority,
|
|
328
|
+
relatedADRs: fileTask.relatedADRs,
|
|
329
|
+
relatedPatterns: fileTask.relatedPatterns,
|
|
330
|
+
relatedGotchas: fileTask.relatedGotchas,
|
|
331
|
+
acceptanceCriteria: fileTask.acceptanceCriteria,
|
|
332
|
+
dependsOn: fileTask.dependsOn,
|
|
333
|
+
};
|
|
334
|
+
});
|
|
335
|
+
// Calculate progress from graph data
|
|
336
|
+
const progress = {
|
|
337
|
+
complete: graphData.progress.completed,
|
|
338
|
+
inProgress: tasks.filter(t => t.state === 'in_progress').length,
|
|
339
|
+
paused: tasks.filter(t => t.state === 'paused').length,
|
|
340
|
+
todo: graphData.progress.total - graphData.progress.completed - tasks.filter(t => t.state === 'in_progress').length,
|
|
341
|
+
total: graphData.progress.total,
|
|
342
|
+
};
|
|
343
|
+
// Determine current task (first in_progress or first todo)
|
|
344
|
+
const currentTask = tasks.find(t => t.state === 'in_progress') ||
|
|
345
|
+
tasks.find(t => t.state === 'todo');
|
|
346
|
+
// Recent completions (completed tasks from the end of list)
|
|
347
|
+
const recentCompletions = tasks
|
|
348
|
+
.filter(t => t.state === 'complete')
|
|
349
|
+
.slice(-3);
|
|
350
|
+
return {
|
|
351
|
+
name: graphData.sprintName,
|
|
352
|
+
file: fileContent.file,
|
|
353
|
+
progress,
|
|
354
|
+
tasks,
|
|
355
|
+
currentTask,
|
|
356
|
+
recentCompletions,
|
|
357
|
+
dependencyWarnings: fileContent.dependencyWarnings,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
160
360
|
/**
|
|
161
361
|
* Override execute to process handoff and display session info
|
|
162
362
|
*/
|
|
163
363
|
async execute(intent, options = {}) {
|
|
164
|
-
|
|
364
|
+
// Disable spinner in non-TTY mode (e.g., Claude Code) to prevent output pollution
|
|
365
|
+
// The table output will be the only visible output
|
|
366
|
+
const isTTY = process.stdout.isTTY === true;
|
|
367
|
+
const spinner = ora({
|
|
368
|
+
text: 'Initializing session...',
|
|
369
|
+
isEnabled: isTTY,
|
|
370
|
+
isSilent: !isTTY, // Completely silence output in non-TTY mode
|
|
371
|
+
});
|
|
372
|
+
// Only start spinner animation in TTY mode
|
|
373
|
+
if (isTTY) {
|
|
374
|
+
spinner.start();
|
|
375
|
+
}
|
|
165
376
|
try {
|
|
166
377
|
// 1. Parse intent
|
|
167
378
|
const parsedIntent = this.parseIntent(intent);
|
|
@@ -174,11 +385,10 @@ export class StartReflectionCommand extends ReflectionCommand {
|
|
|
174
385
|
maxBatchSize: 20, // 20 events max
|
|
175
386
|
silent: true // Suppress logs for clean table output
|
|
176
387
|
});
|
|
177
|
-
|
|
388
|
+
// Don't print - table will be the only output
|
|
178
389
|
}
|
|
179
390
|
catch (error) {
|
|
180
|
-
// Non-critical - continue without sync
|
|
181
|
-
spinner.warn(chalk.yellow('Event sync queue unavailable (offline mode)'));
|
|
391
|
+
// Non-critical - continue without sync (silent)
|
|
182
392
|
}
|
|
183
393
|
// 3. Gather context (including handoff)
|
|
184
394
|
const context = await this.gatherContext(parsedIntent);
|
|
@@ -190,24 +400,27 @@ export class StartReflectionCommand extends ReflectionCommand {
|
|
|
190
400
|
const userSlug = userEmail.replace('@', '-at-').replace(/\./g, '-');
|
|
191
401
|
const sessionDir = path.join(ginkoDir, 'sessions', userSlug);
|
|
192
402
|
const projectRoot = await getProjectRoot();
|
|
193
|
-
// Load sprint checklist for session context
|
|
194
|
-
//
|
|
195
|
-
//
|
|
403
|
+
// Load sprint checklist for session context (EPIC-015 Sprint 2 Task 3)
|
|
404
|
+
// NEW DATA FLOW: Graph API is authoritative for STATUS, file provides CONTENT
|
|
405
|
+
// 1. Fetch status from graph API (with cache fallback)
|
|
406
|
+
// 2. Load content from local sprint file
|
|
407
|
+
// 3. Merge: graph status + file content
|
|
196
408
|
let sprintChecklist = null;
|
|
197
409
|
let sprintSource = 'local';
|
|
410
|
+
let isOffline = false;
|
|
411
|
+
let cacheAge;
|
|
412
|
+
let cacheStaleness;
|
|
198
413
|
// First, check if user has a specific sprint assignment
|
|
414
|
+
// EPIC-012: Per-user sprint tracking enables multiple users on different sprints
|
|
199
415
|
const userSprint = await getUserCurrentSprint();
|
|
200
416
|
let userSprintLoaded = false;
|
|
417
|
+
let sprintFilePath;
|
|
201
418
|
if (userSprint) {
|
|
202
419
|
try {
|
|
203
420
|
const userSprintFile = await getSprintFileFromAssignment(userSprint);
|
|
204
421
|
if (await fs.pathExists(userSprintFile)) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
sprintChecklist = userSprintChecklist;
|
|
208
|
-
sprintSource = 'user';
|
|
209
|
-
userSprintLoaded = true;
|
|
210
|
-
}
|
|
422
|
+
sprintFilePath = userSprintFile;
|
|
423
|
+
userSprintLoaded = true;
|
|
211
424
|
}
|
|
212
425
|
}
|
|
213
426
|
catch {
|
|
@@ -215,72 +428,74 @@ export class StartReflectionCommand extends ReflectionCommand {
|
|
|
215
428
|
// We'll fall back to global sprint
|
|
216
429
|
}
|
|
217
430
|
}
|
|
218
|
-
//
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
431
|
+
// Load sprint with graph-first status (EPIC-015 Sprint 2 Task 3)
|
|
432
|
+
const graphId = await isGraphInitialized() ? await getGraphId() : null;
|
|
433
|
+
const isGraphReady = await isAuthenticated() && graphId;
|
|
434
|
+
if (isGraphReady && graphId) {
|
|
435
|
+
// Graph-only approach: fetch state directly from graph API (EPIC-015 Sprint 3)
|
|
436
|
+
// No local file loading - graph is authoritative for STATE
|
|
437
|
+
spinner.text = 'Fetching sprint from graph...';
|
|
438
|
+
const client = new GraphApiClient();
|
|
439
|
+
try {
|
|
440
|
+
const graphResponse = await client.getActiveSprint(graphId);
|
|
441
|
+
sprintChecklist = this.convertGraphSprintToChecklist(graphResponse);
|
|
442
|
+
sprintSource = 'graph';
|
|
443
|
+
// Save to cache for offline fallback
|
|
444
|
+
const activeSprintData = {
|
|
445
|
+
sprintId: graphResponse.sprint.id,
|
|
446
|
+
sprintName: graphResponse.sprint.name,
|
|
447
|
+
epicId: graphResponse.sprint.id.split('_s')[0] || 'unknown',
|
|
448
|
+
progress: {
|
|
449
|
+
completed: graphResponse.stats.completedTasks,
|
|
450
|
+
total: graphResponse.stats.totalTasks,
|
|
451
|
+
percentage: graphResponse.stats.progressPercentage,
|
|
452
|
+
},
|
|
453
|
+
currentTask: graphResponse.nextTask ? {
|
|
454
|
+
taskId: graphResponse.nextTask.id,
|
|
455
|
+
taskName: graphResponse.nextTask.title,
|
|
456
|
+
status: this.mapGraphStatusToTaskStatus(graphResponse.nextTask.status),
|
|
457
|
+
} : undefined,
|
|
458
|
+
nextTask: graphResponse.nextTask ? {
|
|
459
|
+
taskId: graphResponse.nextTask.id,
|
|
460
|
+
taskName: graphResponse.nextTask.title,
|
|
461
|
+
} : undefined,
|
|
462
|
+
};
|
|
463
|
+
await saveStateCache(activeSprintData, graphId);
|
|
241
464
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
sprintChecklist = localSprint;
|
|
256
|
-
sprintSource = 'local';
|
|
465
|
+
catch (error) {
|
|
466
|
+
// Graph unavailable - try cache fallback
|
|
467
|
+
const { data: cachedData, staleness } = await this.loadFromCacheOnly(graphId);
|
|
468
|
+
if (cachedData) {
|
|
469
|
+
// Use cached data
|
|
470
|
+
sprintChecklist = this.convertCachedDataToChecklist(cachedData);
|
|
471
|
+
sprintSource = 'cache';
|
|
472
|
+
isOffline = true;
|
|
473
|
+
cacheAge = staleness?.ageHuman;
|
|
474
|
+
cacheStaleness = staleness?.level;
|
|
475
|
+
if (staleness?.showWarning) {
|
|
476
|
+
spinner.text = `Using cached sprint (${staleness.ageHuman})`;
|
|
477
|
+
}
|
|
257
478
|
}
|
|
258
479
|
else {
|
|
259
|
-
//
|
|
260
|
-
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const graphProgress = graphSprint.progress?.complete || 0;
|
|
265
|
-
if (localTotal > graphTotal || localProgress > graphProgress) {
|
|
266
|
-
sprintChecklist = localSprint;
|
|
480
|
+
// No cache - fall back to local file (offline fallback only)
|
|
481
|
+
spinner.text = 'Graph unavailable, using local sprint file';
|
|
482
|
+
const localChecklist = await loadSprintChecklist(projectRoot, sprintFilePath);
|
|
483
|
+
if (localChecklist) {
|
|
484
|
+
sprintChecklist = localChecklist;
|
|
267
485
|
sprintSource = 'local';
|
|
268
|
-
|
|
269
|
-
else {
|
|
270
|
-
sprintChecklist = graphSprint;
|
|
271
|
-
sprintSource = 'graph';
|
|
486
|
+
isOffline = true;
|
|
272
487
|
}
|
|
273
488
|
}
|
|
274
489
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
sprintChecklist =
|
|
281
|
-
sprintSource = '
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
// Graph not initialized - use local file only
|
|
493
|
+
const localChecklist = await loadSprintChecklist(projectRoot, sprintFilePath);
|
|
494
|
+
if (localChecklist) {
|
|
495
|
+
sprintChecklist = localChecklist;
|
|
496
|
+
sprintSource = userSprintLoaded ? 'user' : 'local';
|
|
282
497
|
}
|
|
283
|
-
}
|
|
498
|
+
}
|
|
284
499
|
// Load session log content before archiving
|
|
285
500
|
const previousSessionLog = await SessionLogManager.loadSessionLog(sessionDir);
|
|
286
501
|
const hasLog = previousSessionLog.length > 100; // Non-empty log
|
|
@@ -290,9 +505,9 @@ export class StartReflectionCommand extends ReflectionCommand {
|
|
|
290
505
|
const synthesis = await synthesizer.synthesize();
|
|
291
506
|
// 6. Archive previous session log (ALWAYS, not conditionally)
|
|
292
507
|
if (hasLog) {
|
|
293
|
-
spinner.text = 'Archiving previous session
|
|
294
|
-
|
|
295
|
-
|
|
508
|
+
spinner.text = 'Archiving previous session...';
|
|
509
|
+
await SessionLogManager.archiveLog(sessionDir);
|
|
510
|
+
// Silent - table will be the only output
|
|
296
511
|
}
|
|
297
512
|
// 7. Determine work mode from context and update context manager
|
|
298
513
|
const workMode = this.determineWorkMode(context, options);
|
|
@@ -317,14 +532,14 @@ export class StartReflectionCommand extends ReflectionCommand {
|
|
|
317
532
|
teamEventLimit: 10,
|
|
318
533
|
documentDepth: 2,
|
|
319
534
|
teamDays: 7,
|
|
535
|
+
silent: true, // Suppress loading messages - table will be the only output
|
|
320
536
|
}));
|
|
321
537
|
// Ensure eventContext is defined
|
|
322
538
|
if (!eventContext) {
|
|
323
539
|
throw new Error('Event context loading failed');
|
|
324
540
|
}
|
|
325
|
-
//
|
|
326
|
-
spinner.
|
|
327
|
-
console.log(chalk.dim(formatEventContextSummary(eventContext)));
|
|
541
|
+
// Silent - context summary will be shown in table
|
|
542
|
+
spinner.text = 'Processing events...';
|
|
328
543
|
// Generate synthesis from loaded events (replaces file-based synthesis)
|
|
329
544
|
eventSynthesis = await SessionSynthesizer.synthesizeFromEvents(eventContext, projectRoot);
|
|
330
545
|
// Convert to strategy context for compatibility
|
|
@@ -397,12 +612,11 @@ export class StartReflectionCommand extends ReflectionCommand {
|
|
|
397
612
|
spinner.text = 'Creating fresh session log...';
|
|
398
613
|
const flowState = activeSynthesis?.flowState?.energy?.toLowerCase();
|
|
399
614
|
await this.initializeSessionLog(context, options, flowState);
|
|
400
|
-
if
|
|
401
|
-
spinner.info('Session logging enabled (use --no-log to disable)');
|
|
402
|
-
}
|
|
615
|
+
// Session logging status shown in table if needed
|
|
403
616
|
// 11. Build AI context for dual output system (TASK-11)
|
|
404
617
|
// Now async due to graph API enrichment (EPIC-002 Sprint 3 completion)
|
|
405
|
-
|
|
618
|
+
// EPIC-015 Sprint 2: Include offline status for display
|
|
619
|
+
const aiContext = await this.buildAIContext(context, activeSynthesis, strategyContext, eventContext, sprintChecklist, isFirstTimeMember, { isOffline, cacheAge, cacheStaleness });
|
|
406
620
|
// Store AI context for MCP/external access
|
|
407
621
|
await this.storeAIContext(aiContext, sessionDir);
|
|
408
622
|
// 12. Check sprint progression and epic completion (EPIC-012 Sprint 1)
|
|
@@ -427,8 +641,8 @@ export class StartReflectionCommand extends ReflectionCommand {
|
|
|
427
641
|
}
|
|
428
642
|
}
|
|
429
643
|
}
|
|
430
|
-
// 13.
|
|
431
|
-
spinner.
|
|
644
|
+
// 13. Stop spinner before any output
|
|
645
|
+
spinner.stop();
|
|
432
646
|
// EPIC-008 Sprint 2: Check team context staleness (silent - no output)
|
|
433
647
|
await this.checkTeamStaleness();
|
|
434
648
|
// EPIC-004: Push real-time cursor update on session start
|
|
@@ -444,7 +658,7 @@ export class StartReflectionCommand extends ReflectionCommand {
|
|
|
444
658
|
// Insights update is non-critical - don't block session start
|
|
445
659
|
});
|
|
446
660
|
// 14. Display output LAST (after all async operations complete)
|
|
447
|
-
// Table view
|
|
661
|
+
// Table view is the FINAL output - nothing should print after it
|
|
448
662
|
if (options.verbose) {
|
|
449
663
|
// Verbose mode: Full session info (~80 lines)
|
|
450
664
|
await this.displaySessionInfo(context, contextLevel, activeSynthesis, strategyContext, eventContext, sprintChecklist);
|
|
@@ -453,10 +667,11 @@ export class StartReflectionCommand extends ReflectionCommand {
|
|
|
453
667
|
console.log(chalk.dim(formatContextSummary(strategyContext)));
|
|
454
668
|
}
|
|
455
669
|
else {
|
|
456
|
-
// Default mode:
|
|
670
|
+
// Default mode: Compact table (use --full for task list)
|
|
457
671
|
this.displayConciseOutput(aiContext, {
|
|
458
672
|
compact: options.compact,
|
|
459
|
-
table: options.table
|
|
673
|
+
table: options.table,
|
|
674
|
+
full: options.full
|
|
460
675
|
});
|
|
461
676
|
}
|
|
462
677
|
}
|
|
@@ -1255,7 +1470,7 @@ Example output structure:
|
|
|
1255
1470
|
* Creates a rich, structured context object that AI partners can parse.
|
|
1256
1471
|
* This is the "AI UX" side of the dual output system.
|
|
1257
1472
|
*/
|
|
1258
|
-
async buildAIContext(context, synthesis, strategyContext, eventContext, sprintChecklist, isFirstTimeMember = false) {
|
|
1473
|
+
async buildAIContext(context, synthesis, strategyContext, eventContext, sprintChecklist, isFirstTimeMember = false, offlineStatus) {
|
|
1259
1474
|
const workMode = context.workMode || 'Think & Build';
|
|
1260
1475
|
// Build sprint object
|
|
1261
1476
|
let sprint;
|
|
@@ -1338,6 +1553,10 @@ Example output structure:
|
|
|
1338
1553
|
flowState: synthesis?.flowState?.energy || 'neutral',
|
|
1339
1554
|
workMode: workMode,
|
|
1340
1555
|
isFirstTimeMember,
|
|
1556
|
+
// EPIC-015 Sprint 2: Offline status indicators
|
|
1557
|
+
isOffline: offlineStatus?.isOffline,
|
|
1558
|
+
cacheAge: offlineStatus?.cacheAge,
|
|
1559
|
+
cacheStaleness: offlineStatus?.cacheStaleness,
|
|
1341
1560
|
},
|
|
1342
1561
|
charter: eventContext?.strategicContext?.charter,
|
|
1343
1562
|
teamActivity: eventContext?.strategicContext?.teamActivity ? {
|
|
@@ -1387,13 +1606,14 @@ Example output structure:
|
|
|
1387
1606
|
*/
|
|
1388
1607
|
displayConciseOutput(aiContext, options = {}) {
|
|
1389
1608
|
console.log('');
|
|
1390
|
-
//
|
|
1391
|
-
// --compact
|
|
1392
|
-
// --no-table (table === false)
|
|
1609
|
+
// Default: Full table with task list
|
|
1610
|
+
// --compact: Previous concise format without borders
|
|
1611
|
+
// --no-table (table === false): Plain text format for piping
|
|
1393
1612
|
if (options.compact || options.table === false) {
|
|
1394
1613
|
console.log(formatHumanOutput(aiContext, { workMode: aiContext.session.workMode }));
|
|
1395
1614
|
}
|
|
1396
1615
|
else {
|
|
1616
|
+
// Default: full table with task list
|
|
1397
1617
|
console.log(formatTableOutput(aiContext));
|
|
1398
1618
|
}
|
|
1399
1619
|
console.log('');
|