@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
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileType: utility
|
|
3
|
+
* @status: current
|
|
4
|
+
* @updated: 2026-01-19
|
|
5
|
+
* @tags: [cache, offline, state, EPIC-015]
|
|
6
|
+
* @related: [staleness-detector.ts, user-sprint.ts, start-reflection.ts]
|
|
7
|
+
* @priority: high
|
|
8
|
+
* @complexity: medium
|
|
9
|
+
* @dependencies: [fs-extra, path]
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Data about the active sprint for display
|
|
13
|
+
*/
|
|
14
|
+
export interface ActiveSprintData {
|
|
15
|
+
sprintId: string;
|
|
16
|
+
sprintName: string;
|
|
17
|
+
epicId: string;
|
|
18
|
+
epicName?: string;
|
|
19
|
+
progress: {
|
|
20
|
+
completed: number;
|
|
21
|
+
total: number;
|
|
22
|
+
percentage: number;
|
|
23
|
+
};
|
|
24
|
+
currentTask?: {
|
|
25
|
+
taskId: string;
|
|
26
|
+
taskName: string;
|
|
27
|
+
status: 'pending' | 'in_progress' | 'completed';
|
|
28
|
+
};
|
|
29
|
+
nextTask?: {
|
|
30
|
+
taskId: string;
|
|
31
|
+
taskName: string;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Cached state structure
|
|
36
|
+
*/
|
|
37
|
+
export interface StateCache {
|
|
38
|
+
version: 1;
|
|
39
|
+
fetched_at: string;
|
|
40
|
+
graph_id: string;
|
|
41
|
+
active_sprint: ActiveSprintData;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Result of checking cache staleness
|
|
45
|
+
*/
|
|
46
|
+
export interface CacheStalenessResult {
|
|
47
|
+
level: 'fresh' | 'stale' | 'expired';
|
|
48
|
+
age: number;
|
|
49
|
+
ageHuman: string;
|
|
50
|
+
isFresh: boolean;
|
|
51
|
+
showWarning: boolean;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Load cached state from disk
|
|
55
|
+
*
|
|
56
|
+
* @returns StateCache if valid cache exists, null otherwise
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* const cache = await loadStateCache();
|
|
61
|
+
* if (cache) {
|
|
62
|
+
* console.log(`Sprint: ${cache.active_sprint.sprintName}`);
|
|
63
|
+
* }
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export declare function loadStateCache(): Promise<StateCache | null>;
|
|
67
|
+
/**
|
|
68
|
+
* Save state data to cache
|
|
69
|
+
* Uses atomic write (temp file + rename) to prevent partial writes
|
|
70
|
+
*
|
|
71
|
+
* @param data - Active sprint data to cache
|
|
72
|
+
* @param graphId - The graph ID this data belongs to
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```typescript
|
|
76
|
+
* await saveStateCache(sprintData, 'graph-123');
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export declare function saveStateCache(data: ActiveSprintData, graphId: string): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Clear the state cache
|
|
82
|
+
* Called when cache should be invalidated (e.g., logout, graph switch)
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```typescript
|
|
86
|
+
* await clearStateCache();
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export declare function clearStateCache(): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Check staleness of cached state
|
|
92
|
+
*
|
|
93
|
+
* Staleness levels:
|
|
94
|
+
* - fresh: < 5 minutes old
|
|
95
|
+
* - stale: 5 minutes to 24 hours old
|
|
96
|
+
* - expired: > 24 hours old
|
|
97
|
+
*
|
|
98
|
+
* @param cache - The cached state to check
|
|
99
|
+
* @returns Staleness result with level, age, and display info
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* const cache = await loadStateCache();
|
|
104
|
+
* if (cache) {
|
|
105
|
+
* const staleness = checkCacheStaleness(cache);
|
|
106
|
+
* if (staleness.showWarning) {
|
|
107
|
+
* console.log(`Cache is ${staleness.level}: ${staleness.ageHuman}`);
|
|
108
|
+
* }
|
|
109
|
+
* }
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
export declare function checkCacheStaleness(cache: StateCache): CacheStalenessResult;
|
|
113
|
+
/**
|
|
114
|
+
* Format cache age for human-readable display
|
|
115
|
+
*
|
|
116
|
+
* @param fetchedAt - ISO timestamp string
|
|
117
|
+
* @returns Human-readable age string (e.g., "15 min ago", "2 hours ago")
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```typescript
|
|
121
|
+
* const age = formatCacheAge('2026-01-19T10:00:00.000Z');
|
|
122
|
+
* // Returns something like "15 min ago"
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
export declare function formatCacheAge(fetchedAt: string): string;
|
|
126
|
+
/**
|
|
127
|
+
* Get the full path to the cache file
|
|
128
|
+
*/
|
|
129
|
+
declare function getCachePath(): Promise<string>;
|
|
130
|
+
/**
|
|
131
|
+
* Validate that cache data has correct structure and version
|
|
132
|
+
*/
|
|
133
|
+
declare function isValidCache(data: unknown): data is StateCache;
|
|
134
|
+
export declare const _internal: {
|
|
135
|
+
getCachePath: typeof getCachePath;
|
|
136
|
+
isValidCache: typeof isValidCache;
|
|
137
|
+
FRESH_THRESHOLD_MS: number;
|
|
138
|
+
EXPIRED_THRESHOLD_MS: number;
|
|
139
|
+
CACHE_VERSION: number;
|
|
140
|
+
};
|
|
141
|
+
export {};
|
|
142
|
+
//# sourceMappingURL=state-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-cache.d.ts","sourceRoot":"","sources":["../../src/lib/state-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAsBH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE;QACR,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,WAAW,CAAC,EAAE;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,SAAS,GAAG,aAAa,GAAG,WAAW,CAAC;KACjD,CAAC;IACF,QAAQ,CAAC,EAAE;QACT,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,CAAC,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,gBAAgB,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,CAAC;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;CACtB;AAiBD;;;;;;;;;;;;GAYG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAoBjE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CA4Bf;AAED;;;;;;;;GAQG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAUrD;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,oBAAoB,CAsB3E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAwBxD;AAMD;;GAEG;AACH,iBAAe,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,CAG7C;AAED;;GAEG;AACH,iBAAS,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,UAAU,CAsCvD;AAMD,eAAO,MAAM,SAAS;;;;;;CAMrB,CAAC"}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileType: utility
|
|
3
|
+
* @status: current
|
|
4
|
+
* @updated: 2026-01-19
|
|
5
|
+
* @tags: [cache, offline, state, EPIC-015]
|
|
6
|
+
* @related: [staleness-detector.ts, user-sprint.ts, start-reflection.ts]
|
|
7
|
+
* @priority: high
|
|
8
|
+
* @complexity: medium
|
|
9
|
+
* @dependencies: [fs-extra, path]
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Local State Cache (EPIC-015 Sprint 2)
|
|
13
|
+
*
|
|
14
|
+
* Provides local caching for API state data to enable:
|
|
15
|
+
* - Offline operation when API is unavailable
|
|
16
|
+
* - Faster startup by avoiding API calls for fresh data
|
|
17
|
+
* - Graceful degradation with staleness indicators
|
|
18
|
+
*
|
|
19
|
+
* Cache location: .ginko/state-cache.json
|
|
20
|
+
* Uses atomic writes (temp file + rename) to prevent partial writes.
|
|
21
|
+
*/
|
|
22
|
+
import fs from 'fs-extra';
|
|
23
|
+
import path from 'path';
|
|
24
|
+
import { getGinkoDir } from '../utils/helpers.js';
|
|
25
|
+
// =============================================================================
|
|
26
|
+
// Constants
|
|
27
|
+
// =============================================================================
|
|
28
|
+
const CACHE_FILE = 'state-cache.json';
|
|
29
|
+
const CACHE_VERSION = 1;
|
|
30
|
+
// Staleness thresholds in milliseconds
|
|
31
|
+
const FRESH_THRESHOLD_MS = 5 * 60 * 1000; // 5 minutes
|
|
32
|
+
const EXPIRED_THRESHOLD_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
33
|
+
// =============================================================================
|
|
34
|
+
// Main API
|
|
35
|
+
// =============================================================================
|
|
36
|
+
/**
|
|
37
|
+
* Load cached state from disk
|
|
38
|
+
*
|
|
39
|
+
* @returns StateCache if valid cache exists, null otherwise
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* const cache = await loadStateCache();
|
|
44
|
+
* if (cache) {
|
|
45
|
+
* console.log(`Sprint: ${cache.active_sprint.sprintName}`);
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export async function loadStateCache() {
|
|
50
|
+
try {
|
|
51
|
+
const cachePath = await getCachePath();
|
|
52
|
+
if (!await fs.pathExists(cachePath)) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const data = await fs.readJSON(cachePath);
|
|
56
|
+
// Validate cache version and required fields
|
|
57
|
+
if (!isValidCache(data)) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
return data;
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
// File doesn't exist, is invalid JSON, or other read error
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Save state data to cache
|
|
69
|
+
* Uses atomic write (temp file + rename) to prevent partial writes
|
|
70
|
+
*
|
|
71
|
+
* @param data - Active sprint data to cache
|
|
72
|
+
* @param graphId - The graph ID this data belongs to
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```typescript
|
|
76
|
+
* await saveStateCache(sprintData, 'graph-123');
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export async function saveStateCache(data, graphId) {
|
|
80
|
+
const cachePath = await getCachePath();
|
|
81
|
+
const cacheDir = path.dirname(cachePath);
|
|
82
|
+
const tempPath = `${cachePath}.tmp.${Date.now()}`;
|
|
83
|
+
// Ensure .ginko directory exists
|
|
84
|
+
await fs.ensureDir(cacheDir);
|
|
85
|
+
const cache = {
|
|
86
|
+
version: CACHE_VERSION,
|
|
87
|
+
fetched_at: new Date().toISOString(),
|
|
88
|
+
graph_id: graphId,
|
|
89
|
+
active_sprint: data,
|
|
90
|
+
};
|
|
91
|
+
// Atomic write: write to temp file, then rename
|
|
92
|
+
try {
|
|
93
|
+
await fs.writeJSON(tempPath, cache, { spaces: 2 });
|
|
94
|
+
await fs.rename(tempPath, cachePath);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
// Clean up temp file if rename failed
|
|
98
|
+
try {
|
|
99
|
+
await fs.remove(tempPath);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Ignore cleanup errors
|
|
103
|
+
}
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Clear the state cache
|
|
109
|
+
* Called when cache should be invalidated (e.g., logout, graph switch)
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```typescript
|
|
113
|
+
* await clearStateCache();
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
export async function clearStateCache() {
|
|
117
|
+
try {
|
|
118
|
+
const cachePath = await getCachePath();
|
|
119
|
+
if (await fs.pathExists(cachePath)) {
|
|
120
|
+
await fs.remove(cachePath);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
// Ignore errors when clearing cache
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Check staleness of cached state
|
|
129
|
+
*
|
|
130
|
+
* Staleness levels:
|
|
131
|
+
* - fresh: < 5 minutes old
|
|
132
|
+
* - stale: 5 minutes to 24 hours old
|
|
133
|
+
* - expired: > 24 hours old
|
|
134
|
+
*
|
|
135
|
+
* @param cache - The cached state to check
|
|
136
|
+
* @returns Staleness result with level, age, and display info
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```typescript
|
|
140
|
+
* const cache = await loadStateCache();
|
|
141
|
+
* if (cache) {
|
|
142
|
+
* const staleness = checkCacheStaleness(cache);
|
|
143
|
+
* if (staleness.showWarning) {
|
|
144
|
+
* console.log(`Cache is ${staleness.level}: ${staleness.ageHuman}`);
|
|
145
|
+
* }
|
|
146
|
+
* }
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
export function checkCacheStaleness(cache) {
|
|
150
|
+
const fetchedAt = new Date(cache.fetched_at);
|
|
151
|
+
const now = new Date();
|
|
152
|
+
const ageMs = now.getTime() - fetchedAt.getTime();
|
|
153
|
+
let level;
|
|
154
|
+
if (ageMs < FRESH_THRESHOLD_MS) {
|
|
155
|
+
level = 'fresh';
|
|
156
|
+
}
|
|
157
|
+
else if (ageMs < EXPIRED_THRESHOLD_MS) {
|
|
158
|
+
level = 'stale';
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
level = 'expired';
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
level,
|
|
165
|
+
age: ageMs,
|
|
166
|
+
ageHuman: formatCacheAge(cache.fetched_at),
|
|
167
|
+
isFresh: level === 'fresh',
|
|
168
|
+
showWarning: level !== 'fresh',
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Format cache age for human-readable display
|
|
173
|
+
*
|
|
174
|
+
* @param fetchedAt - ISO timestamp string
|
|
175
|
+
* @returns Human-readable age string (e.g., "15 min ago", "2 hours ago")
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* ```typescript
|
|
179
|
+
* const age = formatCacheAge('2026-01-19T10:00:00.000Z');
|
|
180
|
+
* // Returns something like "15 min ago"
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
export function formatCacheAge(fetchedAt) {
|
|
184
|
+
const date = new Date(fetchedAt);
|
|
185
|
+
const now = new Date();
|
|
186
|
+
const diffMs = now.getTime() - date.getTime();
|
|
187
|
+
const diffSecs = Math.floor(diffMs / 1000);
|
|
188
|
+
const diffMins = Math.floor(diffMs / (1000 * 60));
|
|
189
|
+
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
190
|
+
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
191
|
+
if (diffSecs < 60) {
|
|
192
|
+
return 'just now';
|
|
193
|
+
}
|
|
194
|
+
if (diffMins < 60) {
|
|
195
|
+
return `${diffMins} min ago`;
|
|
196
|
+
}
|
|
197
|
+
if (diffHours < 24) {
|
|
198
|
+
return `${diffHours} hour${diffHours === 1 ? '' : 's'} ago`;
|
|
199
|
+
}
|
|
200
|
+
if (diffDays < 7) {
|
|
201
|
+
return `${diffDays} day${diffDays === 1 ? '' : 's'} ago`;
|
|
202
|
+
}
|
|
203
|
+
return date.toLocaleDateString();
|
|
204
|
+
}
|
|
205
|
+
// =============================================================================
|
|
206
|
+
// Helper Functions
|
|
207
|
+
// =============================================================================
|
|
208
|
+
/**
|
|
209
|
+
* Get the full path to the cache file
|
|
210
|
+
*/
|
|
211
|
+
async function getCachePath() {
|
|
212
|
+
const ginkoDir = await getGinkoDir();
|
|
213
|
+
return path.join(ginkoDir, CACHE_FILE);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Validate that cache data has correct structure and version
|
|
217
|
+
*/
|
|
218
|
+
function isValidCache(data) {
|
|
219
|
+
if (!data || typeof data !== 'object') {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
const cache = data;
|
|
223
|
+
// Check version
|
|
224
|
+
if (cache.version !== CACHE_VERSION) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
// Check required fields
|
|
228
|
+
if (!cache.fetched_at || typeof cache.fetched_at !== 'string') {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
if (!cache.graph_id || typeof cache.graph_id !== 'string') {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
if (!cache.active_sprint || typeof cache.active_sprint !== 'object') {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
// Validate active_sprint structure
|
|
238
|
+
const sprint = cache.active_sprint;
|
|
239
|
+
if (!sprint.sprintId || !sprint.sprintName || !sprint.epicId) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
if (!sprint.progress || typeof sprint.progress.completed !== 'number' ||
|
|
243
|
+
typeof sprint.progress.total !== 'number' ||
|
|
244
|
+
typeof sprint.progress.percentage !== 'number') {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
// =============================================================================
|
|
250
|
+
// Exports for Testing
|
|
251
|
+
// =============================================================================
|
|
252
|
+
export const _internal = {
|
|
253
|
+
getCachePath,
|
|
254
|
+
isValidCache,
|
|
255
|
+
FRESH_THRESHOLD_MS,
|
|
256
|
+
EXPIRED_THRESHOLD_MS,
|
|
257
|
+
CACHE_VERSION,
|
|
258
|
+
};
|
|
259
|
+
//# sourceMappingURL=state-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-cache.js","sourceRoot":"","sources":["../../src/lib/state-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileType: utility
|
|
3
|
+
* @status: current
|
|
4
|
+
* @updated: 2026-01-19
|
|
5
|
+
* @tags: [task-sync, graph, neo4j, epic-015, sprint-0a]
|
|
6
|
+
* @related: [task-parser.ts, ../commands/graph/api-client.ts]
|
|
7
|
+
* @priority: high
|
|
8
|
+
* @complexity: medium
|
|
9
|
+
* @dependencies: [api-client]
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Task Graph Sync (EPIC-015 Sprint 0a Tasks 2-3)
|
|
13
|
+
*
|
|
14
|
+
* Syncs parsed tasks to Neo4j graph via the dashboard API.
|
|
15
|
+
* Creates Task nodes and BELONGS_TO relationships.
|
|
16
|
+
*
|
|
17
|
+
* Key principle (ADR-060): Content from Git, State from Graph.
|
|
18
|
+
* - On CREATE: Uses initial_status from markdown
|
|
19
|
+
* - On UPDATE: Preserves existing status (graph-authoritative)
|
|
20
|
+
*/
|
|
21
|
+
import { GraphApiClient } from '../commands/graph/api-client.js';
|
|
22
|
+
import { ParsedTask, SprintParseResult } from './task-parser.js';
|
|
23
|
+
/**
|
|
24
|
+
* Response from task sync API
|
|
25
|
+
*/
|
|
26
|
+
export interface TaskSyncResponse {
|
|
27
|
+
success: boolean;
|
|
28
|
+
created: number;
|
|
29
|
+
updated: number;
|
|
30
|
+
relationships: number;
|
|
31
|
+
tasks: string[];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Options for task sync
|
|
35
|
+
*/
|
|
36
|
+
export interface TaskSyncOptions {
|
|
37
|
+
/** Create BELONGS_TO relationships (default: true) */
|
|
38
|
+
createRelationships?: boolean;
|
|
39
|
+
/** Batch size for API calls (default: 50) */
|
|
40
|
+
batchSize?: number;
|
|
41
|
+
/** Progress callback */
|
|
42
|
+
onProgress?: (synced: number, total: number) => void;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Result of syncing multiple sprints
|
|
46
|
+
*/
|
|
47
|
+
export interface BatchSyncResult {
|
|
48
|
+
success: boolean;
|
|
49
|
+
totalTasks: number;
|
|
50
|
+
created: number;
|
|
51
|
+
updated: number;
|
|
52
|
+
relationships: number;
|
|
53
|
+
errors: string[];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Sync tasks to graph via API
|
|
57
|
+
*
|
|
58
|
+
* @param tasks - Array of parsed tasks
|
|
59
|
+
* @param graphId - Graph namespace ID
|
|
60
|
+
* @param client - GraphApiClient instance
|
|
61
|
+
* @param options - Sync options
|
|
62
|
+
* @returns TaskSyncResponse
|
|
63
|
+
*/
|
|
64
|
+
export declare function syncTasksToGraph(tasks: ParsedTask[], graphId: string, client: GraphApiClient, options?: TaskSyncOptions): Promise<TaskSyncResponse>;
|
|
65
|
+
/**
|
|
66
|
+
* Sync a single sprint's tasks to graph
|
|
67
|
+
*
|
|
68
|
+
* @param sprintResult - Parsed sprint with tasks
|
|
69
|
+
* @param client - GraphApiClient instance (optional, will create if not provided)
|
|
70
|
+
* @param options - Sync options
|
|
71
|
+
* @returns TaskSyncResponse
|
|
72
|
+
*/
|
|
73
|
+
export declare function syncSprintTasksToGraph(sprintResult: SprintParseResult, client?: GraphApiClient, options?: TaskSyncOptions): Promise<TaskSyncResponse>;
|
|
74
|
+
/**
|
|
75
|
+
* Sync multiple sprints' tasks to graph
|
|
76
|
+
*
|
|
77
|
+
* @param sprintResults - Array of parsed sprints with tasks
|
|
78
|
+
* @param options - Sync options
|
|
79
|
+
* @returns BatchSyncResult
|
|
80
|
+
*/
|
|
81
|
+
export declare function syncAllSprintTasksToGraph(sprintResults: SprintParseResult[], options?: TaskSyncOptions): Promise<BatchSyncResult>;
|
|
82
|
+
/**
|
|
83
|
+
* Get task sync status from graph
|
|
84
|
+
*
|
|
85
|
+
* @param graphId - Graph namespace ID
|
|
86
|
+
* @param sprintId - Optional sprint ID filter
|
|
87
|
+
* @param epicId - Optional epic ID filter
|
|
88
|
+
* @param client - GraphApiClient instance
|
|
89
|
+
* @returns Array of task status objects
|
|
90
|
+
*/
|
|
91
|
+
export declare function getTasksFromGraph(graphId: string, client: GraphApiClient, filters?: {
|
|
92
|
+
sprintId?: string;
|
|
93
|
+
epicId?: string;
|
|
94
|
+
}): Promise<Array<{
|
|
95
|
+
id: string;
|
|
96
|
+
title: string;
|
|
97
|
+
status: string;
|
|
98
|
+
priority: string;
|
|
99
|
+
sprint_id: string;
|
|
100
|
+
epic_id: string;
|
|
101
|
+
estimate: string | null;
|
|
102
|
+
assignee: string | null;
|
|
103
|
+
synced_at: string | null;
|
|
104
|
+
}>>;
|
|
105
|
+
//# sourceMappingURL=task-graph-sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-graph-sync.d.ts","sourceRoot":"","sources":["../../src/lib/task-graph-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;;;;;;;GASG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEjE,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,sDAAsD;IACtD,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wBAAwB;IACxB,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACtD;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,UAAU,EAAE,EACnB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,cAAc,EACtB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,gBAAgB,CAAC,CA+C3B;AAED;;;;;;;GAOG;AACH,wBAAsB,sBAAsB,CAC1C,YAAY,EAAE,iBAAiB,EAC/B,MAAM,CAAC,EAAE,cAAc,EACvB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,gBAAgB,CAAC,CAgB3B;AAED;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAC7C,aAAa,EAAE,iBAAiB,EAAE,EAClC,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,eAAe,CAAC,CAqE1B;AAED;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,cAAc,EACtB,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/C,OAAO,CAAC,KAAK,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,CAAC,CAAC,CAEF"}
|