@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
|
@@ -1,21 +1,49 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileType: utility
|
|
3
3
|
* @status: current
|
|
4
|
-
* @updated:
|
|
5
|
-
* @tags: [sprint, task-
|
|
6
|
-
* @related: [charter-loader.ts, start-reflection.ts, context-loader-events.ts]
|
|
4
|
+
* @updated: 2026-01-19
|
|
5
|
+
* @tags: [sprint, task-content, epic-015, session-context]
|
|
6
|
+
* @related: [charter-loader.ts, start-reflection.ts, context-loader-events.ts, task-extractor.ts]
|
|
7
7
|
* @priority: high
|
|
8
8
|
* @complexity: medium
|
|
9
9
|
* @dependencies: [fs-extra, path]
|
|
10
10
|
*/
|
|
11
11
|
/**
|
|
12
|
-
* Task state enumeration
|
|
12
|
+
* Task state enumeration (kept for backward compatibility)
|
|
13
|
+
* NOTE: As of EPIC-015 Sprint 2, status comes from the graph API.
|
|
14
|
+
* File parsing defaults all tasks to 'todo'. Merge with graph status at runtime.
|
|
13
15
|
* - todo: [ ] Not started
|
|
14
16
|
* - in_progress: [@] Currently being worked on
|
|
15
17
|
* - paused: [Z] Temporarily on hold (sleeping)
|
|
16
18
|
* - complete: [x] Finished
|
|
17
19
|
*/
|
|
18
20
|
export type TaskState = 'todo' | 'in_progress' | 'paused' | 'complete';
|
|
21
|
+
/**
|
|
22
|
+
* Task content extracted from sprint markdown (no status)
|
|
23
|
+
* Status comes from graph API and is merged at runtime.
|
|
24
|
+
*/
|
|
25
|
+
export interface TaskContent {
|
|
26
|
+
id: string;
|
|
27
|
+
title: string;
|
|
28
|
+
files?: string[];
|
|
29
|
+
effort?: string;
|
|
30
|
+
priority?: string;
|
|
31
|
+
relatedADRs?: string[];
|
|
32
|
+
relatedPatterns?: string[];
|
|
33
|
+
relatedGotchas?: string[];
|
|
34
|
+
acceptanceCriteria?: AcceptanceCriterion[];
|
|
35
|
+
dependsOn?: string[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Sprint content extracted from markdown (no status-based progress)
|
|
39
|
+
* Status/progress comes from graph API and is merged at runtime.
|
|
40
|
+
*/
|
|
41
|
+
export interface SprintContent {
|
|
42
|
+
name: string;
|
|
43
|
+
file: string;
|
|
44
|
+
tasks: TaskContent[];
|
|
45
|
+
dependencyWarnings?: string[];
|
|
46
|
+
}
|
|
19
47
|
/**
|
|
20
48
|
* Acceptance criterion type - auto-detected from description (EPIC-004 Sprint 3)
|
|
21
49
|
* - test: Unit tests, specs, test suites
|
|
@@ -37,7 +65,9 @@ export interface AcceptanceCriterion {
|
|
|
37
65
|
command?: string;
|
|
38
66
|
}
|
|
39
67
|
/**
|
|
40
|
-
* Individual task from sprint checklist
|
|
68
|
+
* Individual task from sprint checklist (backward compatible interface)
|
|
69
|
+
* NOTE: As of EPIC-015 Sprint 2, state defaults to 'todo' from file parsing.
|
|
70
|
+
* Actual status should be merged from graph API at runtime.
|
|
41
71
|
*/
|
|
42
72
|
export interface Task {
|
|
43
73
|
id: string;
|
|
@@ -54,7 +84,9 @@ export interface Task {
|
|
|
54
84
|
dependsOn?: string[];
|
|
55
85
|
}
|
|
56
86
|
/**
|
|
57
|
-
* Sprint checklist with task states and progress
|
|
87
|
+
* Sprint checklist with task states and progress (backward compatible interface)
|
|
88
|
+
* NOTE: As of EPIC-015 Sprint 2, progress values will be zeroed from file parsing.
|
|
89
|
+
* Actual progress should be calculated from graph API status at runtime.
|
|
58
90
|
*/
|
|
59
91
|
export interface SprintChecklist {
|
|
60
92
|
name: string;
|
|
@@ -75,11 +107,12 @@ export interface SprintChecklist {
|
|
|
75
107
|
* Find active sprint file
|
|
76
108
|
*
|
|
77
109
|
* Priority:
|
|
78
|
-
* 1.
|
|
79
|
-
* 2. Check CURRENT-SPRINT.md for sprint reference → return that file
|
|
80
|
-
* 3. Fall back to scanning SPRINT-*.md files for first incomplete one
|
|
110
|
+
* 1. Scan SPRINT-*.md files for first incomplete one
|
|
81
111
|
* - Respects frontmatter `status` field: active > not_started > (skip paused/complete)
|
|
82
112
|
*
|
|
113
|
+
* EPIC-015 Sprint 3: CURRENT-SPRINT.md is deprecated. Sprint state now comes from
|
|
114
|
+
* the graph API, not from a local file. This function only scans actual sprint files.
|
|
115
|
+
*
|
|
83
116
|
* Valid status values:
|
|
84
117
|
* - active: Currently being worked on
|
|
85
118
|
* - not_started: Planned but not yet started
|
|
@@ -93,25 +126,57 @@ export interface SprintChecklist {
|
|
|
93
126
|
*/
|
|
94
127
|
export declare function findActiveSprint(projectRoot?: string): Promise<string | null>;
|
|
95
128
|
/**
|
|
96
|
-
* Parse sprint markdown into task checklist
|
|
129
|
+
* Parse sprint markdown into task checklist (backward compatible)
|
|
130
|
+
*
|
|
131
|
+
* EPIC-015 Sprint 2: Status parsing removed. All tasks default to 'todo'.
|
|
132
|
+
* Status should be merged from graph API at runtime.
|
|
97
133
|
*
|
|
98
134
|
* Looks for:
|
|
99
135
|
* - Sprint name from title (# SPRINT: ...)
|
|
100
136
|
* - Tasks from ## Sprint Tasks or ### TASK-N sections
|
|
101
|
-
* -
|
|
102
|
-
* -
|
|
137
|
+
* - Task metadata (files, priority, effort, dependencies)
|
|
138
|
+
* - Related knowledge (ADRs, patterns, gotchas)
|
|
139
|
+
* - Acceptance criteria
|
|
103
140
|
*
|
|
104
141
|
* @param markdown - Sprint markdown content
|
|
105
142
|
* @param filePath - Path to sprint file
|
|
106
|
-
* @returns Parsed sprint checklist
|
|
143
|
+
* @returns Parsed sprint checklist (all tasks with state: 'todo')
|
|
107
144
|
*/
|
|
108
145
|
export declare function parseSprintChecklist(markdown: string, filePath: string): SprintChecklist;
|
|
109
146
|
/**
|
|
110
|
-
*
|
|
147
|
+
* Parse sprint markdown into content-only structure (no status)
|
|
148
|
+
*
|
|
149
|
+
* EPIC-015 Sprint 2: New content-focused parsing function.
|
|
150
|
+
* Extracts task content (ID, title, metadata, relationships) without status.
|
|
151
|
+
* Status should be fetched from graph API and merged separately.
|
|
152
|
+
*
|
|
153
|
+
* @param markdown - Sprint markdown content
|
|
154
|
+
* @param filePath - Path to sprint file
|
|
155
|
+
* @returns Parsed sprint content (no status information)
|
|
156
|
+
*/
|
|
157
|
+
export declare function parseSprintContent(markdown: string, filePath: string): SprintContent;
|
|
158
|
+
/**
|
|
159
|
+
* Load active sprint content (no status)
|
|
160
|
+
*
|
|
161
|
+
* EPIC-015 Sprint 2: New content-focused loading function.
|
|
162
|
+
* Finds active sprint file and parses content without status.
|
|
163
|
+
* Status should be fetched from graph API and merged separately.
|
|
164
|
+
*
|
|
165
|
+
* @param projectRoot - Project root directory (defaults to git root)
|
|
166
|
+
* @param sprintFilePath - Optional specific sprint file path
|
|
167
|
+
* @returns Parsed sprint content or null if no sprint found
|
|
168
|
+
*/
|
|
169
|
+
export declare function loadSprintContent(projectRoot?: string, sprintFilePath?: string): Promise<SprintContent | null>;
|
|
170
|
+
/**
|
|
171
|
+
* Load active sprint checklist (backward compatible)
|
|
172
|
+
*
|
|
173
|
+
* EPIC-015 Sprint 2: This function maintained for backward compatibility.
|
|
174
|
+
* All tasks will have state: 'todo'. Merge with graph API status at runtime.
|
|
111
175
|
*
|
|
112
176
|
* Finds active sprint file and parses into structured checklist
|
|
113
177
|
*
|
|
114
178
|
* @param projectRoot - Project root directory (defaults to git root)
|
|
179
|
+
* @param sprintFilePath - Optional specific sprint file path
|
|
115
180
|
* @returns Parsed sprint checklist or null if no sprint found
|
|
116
181
|
*/
|
|
117
182
|
export declare function loadSprintChecklist(projectRoot?: string, sprintFilePath?: string): Promise<SprintChecklist | null>;
|
|
@@ -146,6 +211,13 @@ export interface SprintProgressionInfo {
|
|
|
146
211
|
totalSprints: number;
|
|
147
212
|
completedSprints: number;
|
|
148
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* Find sprint file by ID
|
|
216
|
+
* @param sprintId - Sprint ID (e.g., 'e011_s01' or 'e016_s03')
|
|
217
|
+
* @param projectRoot - Project root directory
|
|
218
|
+
* @returns Path to sprint file or null if not found
|
|
219
|
+
*/
|
|
220
|
+
export declare function findSprintFileById(sprintId: string, projectRoot: string): Promise<string | null>;
|
|
149
221
|
/**
|
|
150
222
|
* Detect sprint progression and epic completion status
|
|
151
223
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sprint-loader.d.ts","sourceRoot":"","sources":["../../src/lib/sprint-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"sprint-loader.d.ts","sourceRoot":"","sources":["../../src/lib/sprint-loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,24 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileType: utility
|
|
3
3
|
* @status: current
|
|
4
|
-
* @updated:
|
|
5
|
-
* @tags: [sprint, task-
|
|
6
|
-
* @related: [charter-loader.ts, start-reflection.ts, context-loader-events.ts]
|
|
4
|
+
* @updated: 2026-01-19
|
|
5
|
+
* @tags: [sprint, task-content, epic-015, session-context]
|
|
6
|
+
* @related: [charter-loader.ts, start-reflection.ts, context-loader-events.ts, task-extractor.ts]
|
|
7
7
|
* @priority: high
|
|
8
8
|
* @complexity: medium
|
|
9
9
|
* @dependencies: [fs-extra, path]
|
|
10
10
|
*/
|
|
11
11
|
/**
|
|
12
|
-
* Sprint Loader (EPIC-001 TASK-5)
|
|
12
|
+
* Sprint Loader (EPIC-001 TASK-5, EPIC-015 Sprint 2)
|
|
13
13
|
*
|
|
14
14
|
* Loads active sprint from filesystem (docs/sprints/SPRINT-*.md)
|
|
15
|
-
* and parses
|
|
15
|
+
* and parses task content (ID, title, metadata).
|
|
16
|
+
*
|
|
17
|
+
* EPIC-015 Sprint 2 Changes:
|
|
18
|
+
* - Status now comes from graph API, not file parsing
|
|
19
|
+
* - Removed checkbox status parsing ([x], [ ], [@], [Z])
|
|
20
|
+
* - Added content-only interfaces (TaskContent, SprintContent)
|
|
21
|
+
* - Backward compatibility: existing functions default state to 'todo'
|
|
16
22
|
*
|
|
17
23
|
* Provides AI partners with:
|
|
18
|
-
* -
|
|
19
|
-
* -
|
|
20
|
-
* -
|
|
21
|
-
* -
|
|
24
|
+
* - Task identification (ID, title)
|
|
25
|
+
* - Task metadata (effort, priority, files, dependencies)
|
|
26
|
+
* - Related knowledge (ADRs, patterns, gotchas)
|
|
27
|
+
* - Acceptance criteria
|
|
22
28
|
*/
|
|
23
29
|
import fs from 'fs-extra';
|
|
24
30
|
import path from 'path';
|
|
@@ -61,15 +67,28 @@ function parseFrontmatter(content) {
|
|
|
61
67
|
}
|
|
62
68
|
return frontmatter;
|
|
63
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Warn about deprecated CURRENT-SPRINT.md file
|
|
72
|
+
* EPIC-015 Sprint 3: CURRENT-SPRINT.md is deprecated in favor of graph-authoritative state
|
|
73
|
+
*/
|
|
74
|
+
function warnDeprecatedCurrentSprint(sprintsDir) {
|
|
75
|
+
const currentSprintPath = path.join(sprintsDir, 'CURRENT-SPRINT.md');
|
|
76
|
+
if (fs.existsSync(currentSprintPath)) {
|
|
77
|
+
console.warn('\n⚠️ DEPRECATED: CURRENT-SPRINT.md detected');
|
|
78
|
+
console.warn(' This file is no longer used. Sprint state now comes from the graph.');
|
|
79
|
+
console.warn(' You can safely delete: docs/sprints/CURRENT-SPRINT.md\n');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
64
82
|
/**
|
|
65
83
|
* Find active sprint file
|
|
66
84
|
*
|
|
67
85
|
* Priority:
|
|
68
|
-
* 1.
|
|
69
|
-
* 2. Check CURRENT-SPRINT.md for sprint reference → return that file
|
|
70
|
-
* 3. Fall back to scanning SPRINT-*.md files for first incomplete one
|
|
86
|
+
* 1. Scan SPRINT-*.md files for first incomplete one
|
|
71
87
|
* - Respects frontmatter `status` field: active > not_started > (skip paused/complete)
|
|
72
88
|
*
|
|
89
|
+
* EPIC-015 Sprint 3: CURRENT-SPRINT.md is deprecated. Sprint state now comes from
|
|
90
|
+
* the graph API, not from a local file. This function only scans actual sprint files.
|
|
91
|
+
*
|
|
73
92
|
* Valid status values:
|
|
74
93
|
* - active: Currently being worked on
|
|
75
94
|
* - not_started: Planned but not yet started
|
|
@@ -88,52 +107,10 @@ export async function findActiveSprint(projectRoot) {
|
|
|
88
107
|
if (!fs.existsSync(sprintsDir)) {
|
|
89
108
|
return null;
|
|
90
109
|
}
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
// Check for "Between Sprints" or "No active sprint" status
|
|
96
|
-
if (currentContent.includes('Between Sprints') ||
|
|
97
|
-
currentContent.includes('No active sprint') ||
|
|
98
|
-
currentContent.match(/\*\*Status\*\*:\s*Between Sprints/i)) {
|
|
99
|
-
return null; // Explicitly between sprints
|
|
100
|
-
}
|
|
101
|
-
// Look for sprint reference: See: SPRINT-YYYY-MM-DD-name.md
|
|
102
|
-
const sprintRefMatch = currentContent.match(/See:\s*(SPRINT-[\w-]+\.md)/i);
|
|
103
|
-
if (sprintRefMatch) {
|
|
104
|
-
const referencedPath = path.join(sprintsDir, sprintRefMatch[1]);
|
|
105
|
-
if (fs.existsSync(referencedPath)) {
|
|
106
|
-
// Validate referenced sprint isn't complete before returning
|
|
107
|
-
const referencedContent = await fs.readFile(referencedPath, 'utf-8');
|
|
108
|
-
const refFrontmatter = parseFrontmatter(referencedContent);
|
|
109
|
-
const refStatus = refFrontmatter.status?.toLowerCase();
|
|
110
|
-
// If referenced sprint is complete, don't return it - fall through to scanning
|
|
111
|
-
if (refStatus !== 'complete') {
|
|
112
|
-
// Also check progress percentage
|
|
113
|
-
const refProgressMatch = referencedContent.match(/\*\*Progress:?\*\*:?\s*(\d+)%/);
|
|
114
|
-
const refProgress = refProgressMatch ? parseInt(refProgressMatch[1]) : 0;
|
|
115
|
-
if (refProgress < 100) {
|
|
116
|
-
return referencedPath;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
// Fall through to scan for next active sprint
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
// Look for **Last Completed**: SPRINT-XXX pattern (means between sprints)
|
|
123
|
-
if (currentContent.match(/\*\*Last Completed\*\*:/)) {
|
|
124
|
-
return null;
|
|
125
|
-
}
|
|
126
|
-
// NEW: Check if CURRENT-SPRINT.md contains actual sprint content
|
|
127
|
-
// (not just a reference to another file)
|
|
128
|
-
// This supports the pattern where CURRENT-SPRINT.md IS the sprint file
|
|
129
|
-
const hasSprintContent = currentContent.includes('## Sprint Tasks') ||
|
|
130
|
-
currentContent.includes('### TASK-') ||
|
|
131
|
-
currentContent.match(/\*\*Sprint Goal\*\*:/);
|
|
132
|
-
if (hasSprintContent) {
|
|
133
|
-
return currentSprintPath;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
// Fall back: Get all sprint files sorted by date (newest first)
|
|
110
|
+
// EPIC-015 Sprint 3: Warn about deprecated CURRENT-SPRINT.md
|
|
111
|
+
warnDeprecatedCurrentSprint(sprintsDir);
|
|
112
|
+
// Get all sprint files sorted by date (newest first)
|
|
113
|
+
// Skip CURRENT-SPRINT.md as it's deprecated
|
|
137
114
|
const sprintFiles = (await fs.readdir(sprintsDir))
|
|
138
115
|
.filter(f => f.startsWith('SPRINT-') && f.endsWith('.md') && f !== 'CURRENT-SPRINT.md')
|
|
139
116
|
.sort()
|
|
@@ -195,22 +172,8 @@ export async function findActiveSprint(projectRoot) {
|
|
|
195
172
|
return null;
|
|
196
173
|
}
|
|
197
174
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
*
|
|
201
|
-
* @param symbol - Checkbox content: ' ', '@', 'Z', or 'x'
|
|
202
|
-
* @returns Task state enum
|
|
203
|
-
*/
|
|
204
|
-
function parseTaskState(symbol) {
|
|
205
|
-
const trimmed = symbol.trim();
|
|
206
|
-
if (trimmed === 'x' || trimmed === 'X')
|
|
207
|
-
return 'complete';
|
|
208
|
-
if (trimmed === '@')
|
|
209
|
-
return 'in_progress';
|
|
210
|
-
if (trimmed === 'Z' || trimmed === 'z')
|
|
211
|
-
return 'paused';
|
|
212
|
-
return 'todo';
|
|
213
|
-
}
|
|
175
|
+
// NOTE: parseTaskState function removed in EPIC-015 Sprint 2
|
|
176
|
+
// Status now comes from graph API, not markdown checkbox parsing
|
|
214
177
|
/**
|
|
215
178
|
* Detect acceptance criterion type from description (EPIC-004 Sprint 3 TASK-1)
|
|
216
179
|
*
|
|
@@ -334,17 +297,21 @@ function parseAcceptanceCriteria(lines, startIndex) {
|
|
|
334
297
|
return criteria;
|
|
335
298
|
}
|
|
336
299
|
/**
|
|
337
|
-
* Parse sprint markdown into task checklist
|
|
300
|
+
* Parse sprint markdown into task checklist (backward compatible)
|
|
301
|
+
*
|
|
302
|
+
* EPIC-015 Sprint 2: Status parsing removed. All tasks default to 'todo'.
|
|
303
|
+
* Status should be merged from graph API at runtime.
|
|
338
304
|
*
|
|
339
305
|
* Looks for:
|
|
340
306
|
* - Sprint name from title (# SPRINT: ...)
|
|
341
307
|
* - Tasks from ## Sprint Tasks or ### TASK-N sections
|
|
342
|
-
* -
|
|
343
|
-
* -
|
|
308
|
+
* - Task metadata (files, priority, effort, dependencies)
|
|
309
|
+
* - Related knowledge (ADRs, patterns, gotchas)
|
|
310
|
+
* - Acceptance criteria
|
|
344
311
|
*
|
|
345
312
|
* @param markdown - Sprint markdown content
|
|
346
313
|
* @param filePath - Path to sprint file
|
|
347
|
-
* @returns Parsed sprint checklist
|
|
314
|
+
* @returns Parsed sprint checklist (all tasks with state: 'todo')
|
|
348
315
|
*/
|
|
349
316
|
export function parseSprintChecklist(markdown, filePath) {
|
|
350
317
|
const lines = markdown.split('\n');
|
|
@@ -363,20 +330,20 @@ export function parseSprintChecklist(markdown, filePath) {
|
|
|
363
330
|
if (parsingTask?.id && parsingTask?.title) {
|
|
364
331
|
tasks.push(parsingTask);
|
|
365
332
|
}
|
|
366
|
-
// Start new task
|
|
333
|
+
// Start new task - state defaults to 'todo' (status comes from graph API)
|
|
367
334
|
parsingTask = {
|
|
368
335
|
id: taskHeaderMatch[1],
|
|
369
336
|
title: taskHeaderMatch[2].trim(),
|
|
370
337
|
effort: taskHeaderMatch[3]?.trim(),
|
|
371
|
-
state: 'todo', //
|
|
338
|
+
state: 'todo', // EPIC-015: Status comes from graph API, not file
|
|
372
339
|
files: [],
|
|
373
340
|
};
|
|
374
341
|
continue;
|
|
375
342
|
}
|
|
376
|
-
//
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
343
|
+
// NOTE: Status line parsing removed in EPIC-015 Sprint 2
|
|
344
|
+
// Status now comes from graph API, not markdown checkboxes
|
|
345
|
+
// Skip **Status:** lines but don't parse them
|
|
346
|
+
if (line.match(/\*\*Status:\*\*/i)) {
|
|
380
347
|
continue;
|
|
381
348
|
}
|
|
382
349
|
// Match task priority: **Priority:** HIGH
|
|
@@ -488,18 +455,20 @@ export function parseSprintChecklist(markdown, filePath) {
|
|
|
488
455
|
if (parsingTask?.id && parsingTask?.title) {
|
|
489
456
|
tasks.push(parsingTask);
|
|
490
457
|
}
|
|
491
|
-
//
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
const
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
const
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
//
|
|
501
|
-
const
|
|
502
|
-
|
|
458
|
+
// EPIC-015 Sprint 2: Progress calculation removed from file parsing
|
|
459
|
+
// All tasks default to 'todo' - actual status comes from graph API
|
|
460
|
+
// Progress should be calculated at runtime after merging with graph status
|
|
461
|
+
const complete = 0; // Will be calculated from graph status
|
|
462
|
+
const inProgress = 0; // Will be calculated from graph status
|
|
463
|
+
const paused = 0; // Will be calculated from graph status
|
|
464
|
+
const todo = tasks.length; // All tasks default to todo
|
|
465
|
+
// EPIC-015 Sprint 2: Current task selection simplified
|
|
466
|
+
// Without status, we just pick the first task (or undefined if none)
|
|
467
|
+
// Actual current task should be determined from graph status at runtime
|
|
468
|
+
const currentTask = tasks.length > 0 ? tasks[0] : undefined;
|
|
469
|
+
// EPIC-015 Sprint 2: Recent completions empty from file parsing
|
|
470
|
+
// Should be populated from graph status at runtime
|
|
471
|
+
const recentCompletions = [];
|
|
503
472
|
// Validate dependencies (EPIC-004 Sprint 4)
|
|
504
473
|
const dependencyWarnings = [];
|
|
505
474
|
const taskIds = new Set(tasks.map(t => t.id));
|
|
@@ -565,12 +534,235 @@ export function parseSprintChecklist(markdown, filePath) {
|
|
|
565
534
|
dependencyWarnings: dependencyWarnings.length > 0 ? dependencyWarnings : undefined,
|
|
566
535
|
};
|
|
567
536
|
}
|
|
537
|
+
// ============================================================================
|
|
538
|
+
// Content-Only Functions (EPIC-015 Sprint 2)
|
|
539
|
+
// These functions extract content without status - status comes from graph API
|
|
540
|
+
// ============================================================================
|
|
541
|
+
/**
|
|
542
|
+
* Parse sprint markdown into content-only structure (no status)
|
|
543
|
+
*
|
|
544
|
+
* EPIC-015 Sprint 2: New content-focused parsing function.
|
|
545
|
+
* Extracts task content (ID, title, metadata, relationships) without status.
|
|
546
|
+
* Status should be fetched from graph API and merged separately.
|
|
547
|
+
*
|
|
548
|
+
* @param markdown - Sprint markdown content
|
|
549
|
+
* @param filePath - Path to sprint file
|
|
550
|
+
* @returns Parsed sprint content (no status information)
|
|
551
|
+
*/
|
|
552
|
+
export function parseSprintContent(markdown, filePath) {
|
|
553
|
+
const lines = markdown.split('\n');
|
|
554
|
+
const tasks = [];
|
|
555
|
+
// Extract sprint name from title
|
|
556
|
+
const titleMatch = markdown.match(/^#\s+SPRINT:\s*(.+?)$/m);
|
|
557
|
+
const sprintName = titleMatch ? titleMatch[1].trim() : path.basename(filePath, '.md');
|
|
558
|
+
let parsingTask = null;
|
|
559
|
+
for (let i = 0; i < lines.length; i++) {
|
|
560
|
+
const line = lines[i];
|
|
561
|
+
// Match task headers: ### TASK-N: Title OR ### e008_s02_t01: Title (ADR-052)
|
|
562
|
+
const taskHeaderMatch = line.match(/^###\s+((?:TASK-\d+|e\d+_s\d+_t\d+|adhoc_\d+_s\d+_t\d+)):\s*(.+?)(?:\s*\((.+?)\))?$/i);
|
|
563
|
+
if (taskHeaderMatch) {
|
|
564
|
+
// Save previous task if exists
|
|
565
|
+
if (parsingTask?.id && parsingTask?.title) {
|
|
566
|
+
tasks.push(parsingTask);
|
|
567
|
+
}
|
|
568
|
+
// Start new task (content only - no status)
|
|
569
|
+
parsingTask = {
|
|
570
|
+
id: taskHeaderMatch[1],
|
|
571
|
+
title: taskHeaderMatch[2].trim(),
|
|
572
|
+
effort: taskHeaderMatch[3]?.trim(),
|
|
573
|
+
files: [],
|
|
574
|
+
};
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
// Skip status lines - status comes from graph API
|
|
578
|
+
if (line.match(/\*\*Status:\*\*/i)) {
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
// Match task priority
|
|
582
|
+
const priorityMatch = line.match(/\*\*Priority:\*\*\s*(.+)/i);
|
|
583
|
+
if (priorityMatch && parsingTask) {
|
|
584
|
+
parsingTask.priority = priorityMatch[1].trim();
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
// Match task dependencies
|
|
588
|
+
const dependsMatch = line.match(/\*\*Depends:\*\*\s*(.+)/i);
|
|
589
|
+
if (dependsMatch && parsingTask) {
|
|
590
|
+
const depsString = dependsMatch[1].trim();
|
|
591
|
+
const deps = depsString.split(/[,\s]+/)
|
|
592
|
+
.map(d => d.trim())
|
|
593
|
+
.filter(d => /^(?:TASK-\d+|t\d+|e\d+_s\d+_t\d+|adhoc_\d+_s\d+_t\d+)$/i.test(d));
|
|
594
|
+
if (deps.length > 0) {
|
|
595
|
+
parsingTask.dependsOn = deps;
|
|
596
|
+
}
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
// Match files section
|
|
600
|
+
if (line.match(/^\*\*Files:\*\*/) && parsingTask) {
|
|
601
|
+
let j = i + 1;
|
|
602
|
+
while (j < lines.length && lines[j].match(/^-\s+(Create|Modify):/)) {
|
|
603
|
+
const fileMatch = lines[j].match(/^-\s+(?:Create|Modify):\s+`([^`]+)`/);
|
|
604
|
+
if (fileMatch) {
|
|
605
|
+
parsingTask.files = parsingTask.files || [];
|
|
606
|
+
parsingTask.files.push(fileMatch[1]);
|
|
607
|
+
}
|
|
608
|
+
j++;
|
|
609
|
+
}
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
// Match acceptance criteria section
|
|
613
|
+
if (line.match(/^\*\*Acceptance:\*\*/) && parsingTask) {
|
|
614
|
+
const criteria = parseAcceptanceCriteria(lines, i + 1);
|
|
615
|
+
if (criteria.length > 0) {
|
|
616
|
+
parsingTask.acceptanceCriteria = criteria;
|
|
617
|
+
}
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
// Extract ADR references
|
|
621
|
+
if (parsingTask) {
|
|
622
|
+
const adrMatches = line.matchAll(/ADR-(\d+)/gi);
|
|
623
|
+
for (const match of adrMatches) {
|
|
624
|
+
const adrId = `ADR-${match[1].padStart(3, '0')}`;
|
|
625
|
+
parsingTask.relatedADRs = parsingTask.relatedADRs || [];
|
|
626
|
+
if (!parsingTask.relatedADRs.includes(adrId)) {
|
|
627
|
+
parsingTask.relatedADRs.push(adrId);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
// Extract pattern references
|
|
631
|
+
const patternFromFileMatches = line.matchAll(/(?:use|apply|see|follow)\s+(?:the\s+)?(?:pattern|example)\s+(?:from|in)\s+[`"]?([a-zA-Z0-9_\-./]+\.[a-zA-Z]+)[`"]?/gi);
|
|
632
|
+
for (const match of patternFromFileMatches) {
|
|
633
|
+
const patternId = match[1];
|
|
634
|
+
parsingTask.relatedPatterns = parsingTask.relatedPatterns || [];
|
|
635
|
+
if (!parsingTask.relatedPatterns.includes(patternId)) {
|
|
636
|
+
parsingTask.relatedPatterns.push(patternId);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
const explicitPatternMatches = line.matchAll(/\b([a-z][a-z0-9]*(?:-[a-z0-9]+)*-pattern)\b/gi);
|
|
640
|
+
for (const match of explicitPatternMatches) {
|
|
641
|
+
const patternId = match[1].toLowerCase();
|
|
642
|
+
parsingTask.relatedPatterns = parsingTask.relatedPatterns || [];
|
|
643
|
+
if (!parsingTask.relatedPatterns.includes(patternId)) {
|
|
644
|
+
parsingTask.relatedPatterns.push(patternId);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
// Extract gotcha references
|
|
648
|
+
const explicitGotchaMatches = line.matchAll(/\b([a-z][a-z0-9]*(?:-[a-z0-9]+)*-gotcha)\b/gi);
|
|
649
|
+
for (const match of explicitGotchaMatches) {
|
|
650
|
+
const gotchaId = match[1].toLowerCase();
|
|
651
|
+
parsingTask.relatedGotchas = parsingTask.relatedGotchas || [];
|
|
652
|
+
if (!parsingTask.relatedGotchas.includes(gotchaId)) {
|
|
653
|
+
parsingTask.relatedGotchas.push(gotchaId);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
if (!line.match(/[a-z][a-z0-9]*(?:-[a-z0-9]+)*-gotcha/i)) {
|
|
657
|
+
const gotchaWarningMatches = line.matchAll(/(?:avoid|watch out for|beware of|gotcha:)\s+[`"]?([^`".\n]+)[`"]?/gi);
|
|
658
|
+
for (const match of gotchaWarningMatches) {
|
|
659
|
+
if (match[1].toLowerCase().includes('gotcha'))
|
|
660
|
+
continue;
|
|
661
|
+
const gotchaId = match[1].trim().toLowerCase().replace(/\s+/g, '-');
|
|
662
|
+
if (gotchaId.length <= 40) {
|
|
663
|
+
parsingTask.relatedGotchas = parsingTask.relatedGotchas || [];
|
|
664
|
+
if (!parsingTask.relatedGotchas.includes(gotchaId)) {
|
|
665
|
+
parsingTask.relatedGotchas.push(gotchaId);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
// Save last task
|
|
673
|
+
if (parsingTask?.id && parsingTask?.title) {
|
|
674
|
+
tasks.push(parsingTask);
|
|
675
|
+
}
|
|
676
|
+
// Validate dependencies
|
|
677
|
+
const dependencyWarnings = [];
|
|
678
|
+
const taskIds = new Set(tasks.map(t => t.id));
|
|
679
|
+
for (const task of tasks) {
|
|
680
|
+
if (task.dependsOn) {
|
|
681
|
+
for (const depId of task.dependsOn) {
|
|
682
|
+
if (!taskIds.has(depId)) {
|
|
683
|
+
dependencyWarnings.push(`${task.id} depends on non-existent task: ${depId}`);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
// Check for circular dependencies
|
|
689
|
+
const visited = new Set();
|
|
690
|
+
const inStack = new Set();
|
|
691
|
+
function detectCycle(taskId, pathArr) {
|
|
692
|
+
if (inStack.has(taskId)) {
|
|
693
|
+
const cycleStart = pathArr.indexOf(taskId);
|
|
694
|
+
return [...pathArr.slice(cycleStart), taskId];
|
|
695
|
+
}
|
|
696
|
+
if (visited.has(taskId))
|
|
697
|
+
return null;
|
|
698
|
+
visited.add(taskId);
|
|
699
|
+
inStack.add(taskId);
|
|
700
|
+
pathArr.push(taskId);
|
|
701
|
+
const task = tasks.find(t => t.id === taskId);
|
|
702
|
+
if (task?.dependsOn) {
|
|
703
|
+
for (const depId of task.dependsOn) {
|
|
704
|
+
if (taskIds.has(depId)) {
|
|
705
|
+
const cycle = detectCycle(depId, pathArr);
|
|
706
|
+
if (cycle)
|
|
707
|
+
return cycle;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
pathArr.pop();
|
|
712
|
+
inStack.delete(taskId);
|
|
713
|
+
return null;
|
|
714
|
+
}
|
|
715
|
+
for (const task of tasks) {
|
|
716
|
+
if (!visited.has(task.id)) {
|
|
717
|
+
const cycle = detectCycle(task.id, []);
|
|
718
|
+
if (cycle) {
|
|
719
|
+
dependencyWarnings.push(`Circular dependency: ${cycle.join(' -> ')}`);
|
|
720
|
+
break;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
return {
|
|
725
|
+
name: sprintName,
|
|
726
|
+
file: filePath,
|
|
727
|
+
tasks,
|
|
728
|
+
dependencyWarnings: dependencyWarnings.length > 0 ? dependencyWarnings : undefined,
|
|
729
|
+
};
|
|
730
|
+
}
|
|
568
731
|
/**
|
|
569
|
-
* Load active sprint
|
|
732
|
+
* Load active sprint content (no status)
|
|
733
|
+
*
|
|
734
|
+
* EPIC-015 Sprint 2: New content-focused loading function.
|
|
735
|
+
* Finds active sprint file and parses content without status.
|
|
736
|
+
* Status should be fetched from graph API and merged separately.
|
|
737
|
+
*
|
|
738
|
+
* @param projectRoot - Project root directory (defaults to git root)
|
|
739
|
+
* @param sprintFilePath - Optional specific sprint file path
|
|
740
|
+
* @returns Parsed sprint content or null if no sprint found
|
|
741
|
+
*/
|
|
742
|
+
export async function loadSprintContent(projectRoot, sprintFilePath) {
|
|
743
|
+
try {
|
|
744
|
+
const sprintFile = sprintFilePath || await findActiveSprint(projectRoot);
|
|
745
|
+
if (!sprintFile) {
|
|
746
|
+
return null;
|
|
747
|
+
}
|
|
748
|
+
const content = await fs.readFile(sprintFile, 'utf-8');
|
|
749
|
+
return parseSprintContent(content, sprintFile);
|
|
750
|
+
}
|
|
751
|
+
catch (error) {
|
|
752
|
+
console.error('Failed to load sprint content:', error.message);
|
|
753
|
+
return null;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Load active sprint checklist (backward compatible)
|
|
758
|
+
*
|
|
759
|
+
* EPIC-015 Sprint 2: This function maintained for backward compatibility.
|
|
760
|
+
* All tasks will have state: 'todo'. Merge with graph API status at runtime.
|
|
570
761
|
*
|
|
571
762
|
* Finds active sprint file and parses into structured checklist
|
|
572
763
|
*
|
|
573
764
|
* @param projectRoot - Project root directory (defaults to git root)
|
|
765
|
+
* @param sprintFilePath - Optional specific sprint file path
|
|
574
766
|
* @returns Parsed sprint checklist or null if no sprint found
|
|
575
767
|
*/
|
|
576
768
|
export async function loadSprintChecklist(projectRoot, sprintFilePath) {
|
|
@@ -760,8 +952,11 @@ function parseSprintTable(content) {
|
|
|
760
952
|
}
|
|
761
953
|
/**
|
|
762
954
|
* Find sprint file by ID
|
|
955
|
+
* @param sprintId - Sprint ID (e.g., 'e011_s01' or 'e016_s03')
|
|
956
|
+
* @param projectRoot - Project root directory
|
|
957
|
+
* @returns Path to sprint file or null if not found
|
|
763
958
|
*/
|
|
764
|
-
async function findSprintFileById(sprintId, projectRoot) {
|
|
959
|
+
export async function findSprintFileById(sprintId, projectRoot) {
|
|
765
960
|
const sprintsDir = path.join(projectRoot, 'docs', 'sprints');
|
|
766
961
|
if (!fs.existsSync(sprintsDir)) {
|
|
767
962
|
return null;
|