@ginkoai/cli 2.0.6 → 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 +292 -70
- 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 +11 -29
- package/dist/index.js.map +1 -1
- package/dist/lib/context-loader-events.d.ts.map +1 -1
- package/dist/lib/context-loader-events.js +7 -26
- package/dist/lib/context-loader-events.js.map +1 -1
- package/dist/lib/output-formatter.d.ts +8 -2
- package/dist/lib/output-formatter.d.ts.map +1 -1
- package/dist/lib/output-formatter.js +98 -18
- 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
|
@@ -21,6 +21,7 @@ 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
23
|
import { loadSprintChecklist, formatSprintChecklist, formatCurrentTaskDetails, detectSprintProgression } from '../../lib/sprint-loader.js';
|
|
24
|
+
import { loadStateCache, saveStateCache, checkCacheStaleness } from '../../lib/state-cache.js';
|
|
24
25
|
import { formatHumanOutput, formatVerboseOutput, formatAIContextJSONL, formatTableOutput, formatEpicComplete, formatSprintProgressionPrompt } from '../../lib/output-formatter.js';
|
|
25
26
|
import { getUserCurrentSprint, setUserCurrentSprint, createAssignmentFromFile, getSprintFileFromAssignment } from '../../lib/user-sprint.js';
|
|
26
27
|
import { GraphApiClient } from '../graph/api-client.js';
|
|
@@ -156,11 +157,222 @@ export class StartReflectionCommand extends ReflectionCommand {
|
|
|
156
157
|
source: 'graph', // Mark source for debugging
|
|
157
158
|
};
|
|
158
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
|
+
}
|
|
159
360
|
/**
|
|
160
361
|
* Override execute to process handoff and display session info
|
|
161
362
|
*/
|
|
162
363
|
async execute(intent, options = {}) {
|
|
163
|
-
|
|
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
|
+
}
|
|
164
376
|
try {
|
|
165
377
|
// 1. Parse intent
|
|
166
378
|
const parsedIntent = this.parseIntent(intent);
|
|
@@ -188,24 +400,27 @@ export class StartReflectionCommand extends ReflectionCommand {
|
|
|
188
400
|
const userSlug = userEmail.replace('@', '-at-').replace(/\./g, '-');
|
|
189
401
|
const sessionDir = path.join(ginkoDir, 'sessions', userSlug);
|
|
190
402
|
const projectRoot = await getProjectRoot();
|
|
191
|
-
// Load sprint checklist for session context
|
|
192
|
-
//
|
|
193
|
-
//
|
|
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
|
|
194
408
|
let sprintChecklist = null;
|
|
195
409
|
let sprintSource = 'local';
|
|
410
|
+
let isOffline = false;
|
|
411
|
+
let cacheAge;
|
|
412
|
+
let cacheStaleness;
|
|
196
413
|
// First, check if user has a specific sprint assignment
|
|
414
|
+
// EPIC-012: Per-user sprint tracking enables multiple users on different sprints
|
|
197
415
|
const userSprint = await getUserCurrentSprint();
|
|
198
416
|
let userSprintLoaded = false;
|
|
417
|
+
let sprintFilePath;
|
|
199
418
|
if (userSprint) {
|
|
200
419
|
try {
|
|
201
420
|
const userSprintFile = await getSprintFileFromAssignment(userSprint);
|
|
202
421
|
if (await fs.pathExists(userSprintFile)) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
sprintChecklist = userSprintChecklist;
|
|
206
|
-
sprintSource = 'user';
|
|
207
|
-
userSprintLoaded = true;
|
|
208
|
-
}
|
|
422
|
+
sprintFilePath = userSprintFile;
|
|
423
|
+
userSprintLoaded = true;
|
|
209
424
|
}
|
|
210
425
|
}
|
|
211
426
|
catch {
|
|
@@ -213,72 +428,74 @@ export class StartReflectionCommand extends ReflectionCommand {
|
|
|
213
428
|
// We'll fall back to global sprint
|
|
214
429
|
}
|
|
215
430
|
}
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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);
|
|
239
464
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
sprintChecklist = localSprint;
|
|
254
|
-
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
|
+
}
|
|
255
478
|
}
|
|
256
479
|
else {
|
|
257
|
-
//
|
|
258
|
-
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
const graphProgress = graphSprint.progress?.complete || 0;
|
|
263
|
-
if (localTotal > graphTotal || localProgress > graphProgress) {
|
|
264
|
-
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;
|
|
265
485
|
sprintSource = 'local';
|
|
266
|
-
|
|
267
|
-
else {
|
|
268
|
-
sprintChecklist = graphSprint;
|
|
269
|
-
sprintSource = 'graph';
|
|
486
|
+
isOffline = true;
|
|
270
487
|
}
|
|
271
488
|
}
|
|
272
489
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
sprintChecklist =
|
|
279
|
-
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';
|
|
280
497
|
}
|
|
281
|
-
}
|
|
498
|
+
}
|
|
282
499
|
// Load session log content before archiving
|
|
283
500
|
const previousSessionLog = await SessionLogManager.loadSessionLog(sessionDir);
|
|
284
501
|
const hasLog = previousSessionLog.length > 100; // Non-empty log
|
|
@@ -398,7 +615,8 @@ export class StartReflectionCommand extends ReflectionCommand {
|
|
|
398
615
|
// Session logging status shown in table if needed
|
|
399
616
|
// 11. Build AI context for dual output system (TASK-11)
|
|
400
617
|
// Now async due to graph API enrichment (EPIC-002 Sprint 3 completion)
|
|
401
|
-
|
|
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 });
|
|
402
620
|
// Store AI context for MCP/external access
|
|
403
621
|
await this.storeAIContext(aiContext, sessionDir);
|
|
404
622
|
// 12. Check sprint progression and epic completion (EPIC-012 Sprint 1)
|
|
@@ -1252,7 +1470,7 @@ Example output structure:
|
|
|
1252
1470
|
* Creates a rich, structured context object that AI partners can parse.
|
|
1253
1471
|
* This is the "AI UX" side of the dual output system.
|
|
1254
1472
|
*/
|
|
1255
|
-
async buildAIContext(context, synthesis, strategyContext, eventContext, sprintChecklist, isFirstTimeMember = false) {
|
|
1473
|
+
async buildAIContext(context, synthesis, strategyContext, eventContext, sprintChecklist, isFirstTimeMember = false, offlineStatus) {
|
|
1256
1474
|
const workMode = context.workMode || 'Think & Build';
|
|
1257
1475
|
// Build sprint object
|
|
1258
1476
|
let sprint;
|
|
@@ -1335,6 +1553,10 @@ Example output structure:
|
|
|
1335
1553
|
flowState: synthesis?.flowState?.energy || 'neutral',
|
|
1336
1554
|
workMode: workMode,
|
|
1337
1555
|
isFirstTimeMember,
|
|
1556
|
+
// EPIC-015 Sprint 2: Offline status indicators
|
|
1557
|
+
isOffline: offlineStatus?.isOffline,
|
|
1558
|
+
cacheAge: offlineStatus?.cacheAge,
|
|
1559
|
+
cacheStaleness: offlineStatus?.cacheStaleness,
|
|
1338
1560
|
},
|
|
1339
1561
|
charter: eventContext?.strategicContext?.charter,
|
|
1340
1562
|
teamActivity: eventContext?.strategicContext?.teamActivity ? {
|