ai-sdlc 0.3.0-alpha.1 → 0.3.0-alpha.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -0
- package/dist/agents/implementation.d.ts.map +1 -1
- package/dist/agents/implementation.js +3 -7
- package/dist/agents/implementation.js.map +1 -1
- package/dist/agents/review.d.ts +9 -0
- package/dist/agents/review.d.ts.map +1 -1
- package/dist/agents/review.js +44 -4
- package/dist/agents/review.js.map +1 -1
- package/dist/cli/commands.d.ts +3 -0
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +17 -1
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/dependency-resolver.d.ts +49 -0
- package/dist/cli/dependency-resolver.d.ts.map +1 -0
- package/dist/cli/dependency-resolver.js +133 -0
- package/dist/cli/dependency-resolver.js.map +1 -0
- package/dist/cli/epic-processor.d.ts +16 -0
- package/dist/cli/epic-processor.d.ts.map +1 -0
- package/dist/cli/epic-processor.js +362 -0
- package/dist/cli/epic-processor.js.map +1 -0
- package/dist/cli/formatting.d.ts +15 -0
- package/dist/cli/formatting.d.ts.map +1 -1
- package/dist/cli/formatting.js +19 -0
- package/dist/cli/formatting.js.map +1 -1
- package/dist/cli/progress-dashboard.d.ts +58 -0
- package/dist/cli/progress-dashboard.d.ts.map +1 -0
- package/dist/cli/progress-dashboard.js +216 -0
- package/dist/cli/progress-dashboard.js.map +1 -0
- package/dist/cli/table-renderer.d.ts.map +1 -1
- package/dist/cli/table-renderer.js +5 -1
- package/dist/cli/table-renderer.js.map +1 -1
- package/dist/core/config.d.ts +5 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +70 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/git-utils.d.ts +19 -0
- package/dist/core/git-utils.d.ts.map +1 -1
- package/dist/core/git-utils.js +58 -0
- package/dist/core/git-utils.js.map +1 -1
- package/dist/core/kanban.d.ts +125 -1
- package/dist/core/kanban.d.ts.map +1 -1
- package/dist/core/kanban.js +363 -4
- package/dist/core/kanban.js.map +1 -1
- package/dist/core/story.d.ts.map +1 -1
- package/dist/core/story.js +4 -0
- package/dist/core/story.js.map +1 -1
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +137 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +23 -0
- package/dist/types/index.js.map +1 -1
- package/package.json +1 -1
package/dist/core/git-utils.js
CHANGED
|
@@ -114,6 +114,64 @@ export function isLocalBehindRemote(workingDir) {
|
|
|
114
114
|
const [behind] = output.split('\t').map(Number);
|
|
115
115
|
return behind > 0;
|
|
116
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Detect the base branch for the repository (main or master)
|
|
119
|
+
*
|
|
120
|
+
* @param workingDir - Working directory to check
|
|
121
|
+
* @returns 'main' or 'master' depending on which exists
|
|
122
|
+
* @throws Error if neither main nor master branch exists
|
|
123
|
+
*/
|
|
124
|
+
export function getBaseBranch(workingDir) {
|
|
125
|
+
// Check for 'main' first
|
|
126
|
+
const mainResult = spawnSync('git', ['rev-parse', '--verify', 'main'], {
|
|
127
|
+
cwd: workingDir,
|
|
128
|
+
encoding: 'utf-8',
|
|
129
|
+
shell: false,
|
|
130
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
131
|
+
});
|
|
132
|
+
if (mainResult.status === 0) {
|
|
133
|
+
return 'main';
|
|
134
|
+
}
|
|
135
|
+
// Check for 'master' as fallback
|
|
136
|
+
const masterResult = spawnSync('git', ['rev-parse', '--verify', 'master'], {
|
|
137
|
+
cwd: workingDir,
|
|
138
|
+
encoding: 'utf-8',
|
|
139
|
+
shell: false,
|
|
140
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
141
|
+
});
|
|
142
|
+
if (masterResult.status === 0) {
|
|
143
|
+
return 'master';
|
|
144
|
+
}
|
|
145
|
+
throw new Error('No base branch found. Expected "main" or "master" branch to exist.');
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Get the merge-base (common ancestor) between a base branch and HEAD
|
|
149
|
+
*
|
|
150
|
+
* This is useful for comparing all changes on a feature branch against the base branch,
|
|
151
|
+
* rather than just comparing to the immediate parent commit (HEAD~1).
|
|
152
|
+
*
|
|
153
|
+
* @param workingDir - Working directory to check
|
|
154
|
+
* @param baseBranch - Base branch name (e.g., 'main' or 'master')
|
|
155
|
+
* @returns Commit SHA of the merge-base, or null if it cannot be determined
|
|
156
|
+
*/
|
|
157
|
+
export function getMergeBase(workingDir, baseBranch) {
|
|
158
|
+
try {
|
|
159
|
+
const result = spawnSync('git', ['merge-base', baseBranch, 'HEAD'], {
|
|
160
|
+
cwd: workingDir,
|
|
161
|
+
encoding: 'utf-8',
|
|
162
|
+
shell: false,
|
|
163
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
164
|
+
});
|
|
165
|
+
if (result.status !== 0) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
const output = result.stdout?.toString().trim() || '';
|
|
169
|
+
return output || null;
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
117
175
|
export function validateGitState(workingDir, options = {}) {
|
|
118
176
|
const errors = [];
|
|
119
177
|
const warnings = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git-utils.js","sourceRoot":"","sources":["../../src/core/git-utils.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"git-utils.js","sourceRoot":"","sources":["../../src/core/git-utils.ts"],"names":[],"mappings":"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"}
|
package/dist/core/kanban.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Story, StateAssessment, KanbanFolder, StoryStatus } from '../types/index.js';
|
|
1
|
+
import { Story, StateAssessment, KanbanFolder, StoryStatus, GroupingDimension, GroupingSummary } from '../types/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* Find all stories in the stories/ folder structure
|
|
4
4
|
* Globs stories/*\u200B/story.md and returns all story objects
|
|
@@ -46,4 +46,128 @@ export declare function kanbanExists(sdlcRoot: string): boolean;
|
|
|
46
46
|
* Get board statistics
|
|
47
47
|
*/
|
|
48
48
|
export declare function getBoardStats(sdlcRoot: string): Record<KanbanFolder, number>;
|
|
49
|
+
/**
|
|
50
|
+
* Check if a label matches a glob pattern.
|
|
51
|
+
* Supports wildcard (*) for pattern matching with proper regex escaping.
|
|
52
|
+
*
|
|
53
|
+
* @param label - The label to test
|
|
54
|
+
* @param pattern - The glob pattern (e.g., 'epic-*', '*-test')
|
|
55
|
+
* @returns true if label matches pattern
|
|
56
|
+
* @throws Error if pattern exceeds maximum length
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* labelMatchesPattern('epic-ticketing', 'epic-*') // true
|
|
60
|
+
* labelMatchesPattern('sprint-2024-q1', 'epic-*') // false
|
|
61
|
+
* labelMatchesPattern('test.label', 'test.label') // true (special chars escaped)
|
|
62
|
+
*/
|
|
63
|
+
export declare function labelMatchesPattern(label: string, pattern: string): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Find stories by exact label match.
|
|
66
|
+
* Returns stories sorted by priority ascending.
|
|
67
|
+
*
|
|
68
|
+
* @param sdlcRoot - Path to .ai-sdlc folder
|
|
69
|
+
* @param label - Exact label to match
|
|
70
|
+
* @returns Array of stories with the specified label
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* findStoriesByLabel(sdlcRoot, 'epic-ticketing')
|
|
74
|
+
*/
|
|
75
|
+
export declare function findStoriesByLabel(sdlcRoot: string, label: string): Story[];
|
|
76
|
+
/**
|
|
77
|
+
* Find stories by multiple labels with AND/OR logic.
|
|
78
|
+
* Returns stories sorted by priority ascending.
|
|
79
|
+
*
|
|
80
|
+
* @param sdlcRoot - Path to .ai-sdlc folder
|
|
81
|
+
* @param labels - Array of labels to match
|
|
82
|
+
* @param mode - 'all' requires all labels (AND), 'any' requires at least one (OR)
|
|
83
|
+
* @returns Array of matching stories
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* // Stories with BOTH epic-ticketing AND team-backend
|
|
87
|
+
* findStoriesByLabels(sdlcRoot, ['epic-ticketing', 'team-backend'], 'all')
|
|
88
|
+
*
|
|
89
|
+
* // Stories with EITHER epic-ticketing OR epic-auth
|
|
90
|
+
* findStoriesByLabels(sdlcRoot, ['epic-ticketing', 'epic-auth'], 'any')
|
|
91
|
+
*/
|
|
92
|
+
export declare function findStoriesByLabels(sdlcRoot: string, labels: string[], mode: 'all' | 'any'): Story[];
|
|
93
|
+
/**
|
|
94
|
+
* Find stories by glob pattern matching on labels.
|
|
95
|
+
* Returns deduplicated stories sorted by priority ascending.
|
|
96
|
+
*
|
|
97
|
+
* @param sdlcRoot - Path to .ai-sdlc folder
|
|
98
|
+
* @param pattern - Glob pattern (e.g., 'epic-*', '*-test', 'team-*-backend')
|
|
99
|
+
* @returns Array of stories with at least one matching label
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* findStoriesByPattern(sdlcRoot, 'epic-*') // All stories with epic-* labels
|
|
103
|
+
* findStoriesByPattern(sdlcRoot, '*-test') // All stories with *-test labels
|
|
104
|
+
*/
|
|
105
|
+
export declare function findStoriesByPattern(sdlcRoot: string, pattern: string): Story[];
|
|
106
|
+
/**
|
|
107
|
+
* Get all unique labels across all stories.
|
|
108
|
+
* Returns labels sorted alphabetically.
|
|
109
|
+
*
|
|
110
|
+
* @param sdlcRoot - Path to .ai-sdlc folder
|
|
111
|
+
* @returns Sorted array of unique labels
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* getUniqueLabels(sdlcRoot) // ['epic-auth', 'epic-ticketing', 'team-backend', ...]
|
|
115
|
+
*/
|
|
116
|
+
export declare function getUniqueLabels(sdlcRoot: string): string[];
|
|
117
|
+
/**
|
|
118
|
+
* Get grouping summaries for a specific dimension.
|
|
119
|
+
* Returns groupings sorted by story count descending.
|
|
120
|
+
*
|
|
121
|
+
* @param sdlcRoot - Path to .ai-sdlc folder
|
|
122
|
+
* @param dimension - Grouping dimension ('thematic', 'temporal', 'structural')
|
|
123
|
+
* @returns Array of grouping summaries with story counts and status breakdowns
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* getGroupings(sdlcRoot, 'thematic')
|
|
127
|
+
* // [
|
|
128
|
+
* // { id: 'ticketing', label: 'epic-ticketing', dimension: 'thematic',
|
|
129
|
+
* // storyCount: 5, statusBreakdown: { backlog: 2, ready: 1, ... } },
|
|
130
|
+
* // ...
|
|
131
|
+
* // ]
|
|
132
|
+
*/
|
|
133
|
+
export declare function getGroupings(sdlcRoot: string, dimension: GroupingDimension): GroupingSummary[];
|
|
134
|
+
/**
|
|
135
|
+
* Find stories by epic label (convenience wrapper).
|
|
136
|
+
* Queries for stories with 'epic-{epicId}' label.
|
|
137
|
+
*
|
|
138
|
+
* @param sdlcRoot - Path to .ai-sdlc folder
|
|
139
|
+
* @param epicId - Epic identifier (without 'epic-' prefix)
|
|
140
|
+
* @returns Array of stories with the specified epic label
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* findStoriesByEpic(sdlcRoot, 'ticketing-integration')
|
|
144
|
+
* // Returns stories with label 'epic-ticketing-integration'
|
|
145
|
+
*/
|
|
146
|
+
export declare function findStoriesByEpic(sdlcRoot: string, epicId: string): Story[];
|
|
147
|
+
/**
|
|
148
|
+
* Find stories by sprint label (convenience wrapper).
|
|
149
|
+
* Queries for stories with 'sprint-{sprintId}' label.
|
|
150
|
+
*
|
|
151
|
+
* @param sdlcRoot - Path to .ai-sdlc folder
|
|
152
|
+
* @param sprintId - Sprint identifier (without 'sprint-' prefix)
|
|
153
|
+
* @returns Array of stories with the specified sprint label
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* findStoriesBySprint(sdlcRoot, '2024-q1')
|
|
157
|
+
* // Returns stories with label 'sprint-2024-q1'
|
|
158
|
+
*/
|
|
159
|
+
export declare function findStoriesBySprint(sdlcRoot: string, sprintId: string): Story[];
|
|
160
|
+
/**
|
|
161
|
+
* Find stories by team label (convenience wrapper).
|
|
162
|
+
* Queries for stories with 'team-{teamId}' label.
|
|
163
|
+
*
|
|
164
|
+
* @param sdlcRoot - Path to .ai-sdlc folder
|
|
165
|
+
* @param teamId - Team identifier (without 'team-' prefix)
|
|
166
|
+
* @returns Array of stories with the specified team label
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* findStoriesByTeam(sdlcRoot, 'backend')
|
|
170
|
+
* // Returns stories with label 'team-backend'
|
|
171
|
+
*/
|
|
172
|
+
export declare function findStoriesByTeam(sdlcRoot: string, teamId: string): Story[];
|
|
49
173
|
//# sourceMappingURL=kanban.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kanban.d.ts","sourceRoot":"","sources":["../../src/core/kanban.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAAE,eAAe,EAA0B,YAAY,EAA+D,WAAW,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"kanban.d.ts","sourceRoot":"","sources":["../../src/core/kanban.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAAE,eAAe,EAA0B,YAAY,EAA+D,WAAW,EAAE,iBAAiB,EAAE,eAAe,EAAqB,MAAM,mBAAmB,CAAC;AA+FlO;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,EAAE,CAiCxD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,KAAK,EAAE,CAalF;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,KAAK,EAAE,CAclF;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC,YAAY,EAAE,KAAK,EAAE,CAAC,CAiC1E;AAKD;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAG5E;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAO7D;AAeD;;GAEG;AACH,wBAAsB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CA6M5E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAMvD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAItD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAa5E;AAOD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAsB3E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,EAAE,CAa3E;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EAAE,EAChB,IAAI,EAAE,KAAK,GAAG,KAAK,GAClB,KAAK,EAAE,CA0BT;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,KAAK,EAAE,CA+B/E;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAW1D;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,iBAAiB,GAAG,eAAe,EAAE,CAoE9F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,KAAK,EAAE,CAE3E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,KAAK,EAAE,CAE/E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,KAAK,EAAE,CAE3E"}
|
package/dist/core/kanban.js
CHANGED
|
@@ -1,10 +1,90 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { glob } from 'glob';
|
|
4
|
-
import { KANBAN_FOLDERS, ReviewDecision, STORIES_FOLDER, STORY_FILENAME } from '../types/index.js';
|
|
4
|
+
import { KANBAN_FOLDERS, ReviewDecision, STORIES_FOLDER, STORY_FILENAME, DEFAULT_GROUPINGS } from '../types/index.js';
|
|
5
5
|
import { parseStory, isAtMaxRetries, canRetryRefinement, getLatestReviewAttempt, moveToBlocked, getEffectiveMaxRetries, sanitizeReasonText } from './story.js';
|
|
6
6
|
import { loadConfig } from './config.js';
|
|
7
7
|
import { determineTargetPhase } from '../agents/rework.js';
|
|
8
|
+
import { GitWorktreeService } from './worktree.js';
|
|
9
|
+
/**
|
|
10
|
+
* Load stories from active worktrees.
|
|
11
|
+
* Returns a map of story ID to story object for all stories found in worktrees.
|
|
12
|
+
* Handles missing files, parse errors, and deleted worktrees gracefully with logging.
|
|
13
|
+
*
|
|
14
|
+
* @param sdlcRoot - Path to .ai-sdlc folder
|
|
15
|
+
* @returns Map of story ID to story object from worktrees
|
|
16
|
+
*/
|
|
17
|
+
function loadStoriesFromWorktrees(sdlcRoot) {
|
|
18
|
+
const worktreeMap = new Map();
|
|
19
|
+
try {
|
|
20
|
+
// Determine worktree base path and project root
|
|
21
|
+
const projectRoot = path.dirname(sdlcRoot);
|
|
22
|
+
const worktreeBasePath = path.join(sdlcRoot, 'worktrees');
|
|
23
|
+
// Get list of active worktrees
|
|
24
|
+
const worktreeService = new GitWorktreeService(projectRoot, worktreeBasePath);
|
|
25
|
+
const worktrees = worktreeService.list();
|
|
26
|
+
for (const worktree of worktrees) {
|
|
27
|
+
// Skip worktrees that don't exist on filesystem
|
|
28
|
+
if (!worktree.exists) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
// Skip worktrees without a story ID
|
|
32
|
+
if (!worktree.storyId) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
// Construct expected story path in worktree
|
|
36
|
+
// Pattern: {worktreePath}/stories/{storyId}/story.md
|
|
37
|
+
const storyPath = path.join(worktree.path, STORIES_FOLDER, worktree.storyId, STORY_FILENAME);
|
|
38
|
+
// Check if story file exists
|
|
39
|
+
if (!fs.existsSync(storyPath)) {
|
|
40
|
+
console.warn(`Worktree story file missing for ${worktree.storyId}: ${storyPath}`);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
// Parse the story from worktree
|
|
45
|
+
const canonicalPath = fs.realpathSync(storyPath);
|
|
46
|
+
const story = parseStory(canonicalPath);
|
|
47
|
+
worktreeMap.set(worktree.storyId, { ...story, path: canonicalPath });
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
// Log parse error and continue
|
|
51
|
+
const errorMsg = err instanceof Error ? err.message : 'Unknown error';
|
|
52
|
+
console.error(`Failed to parse worktree story ${worktree.storyId} at ${storyPath}: ${errorMsg}`);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
// If worktree listing fails, log error but return empty map (graceful fallback)
|
|
59
|
+
const errorMsg = err instanceof Error ? err.message : 'Unknown error';
|
|
60
|
+
console.error(`Failed to load worktree stories: ${errorMsg}`);
|
|
61
|
+
}
|
|
62
|
+
return worktreeMap;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Merge main repository stories with worktree stories.
|
|
66
|
+
* Worktree versions take precedence over main repo versions for the same story ID.
|
|
67
|
+
*
|
|
68
|
+
* @param mainStories - Stories from main repository
|
|
69
|
+
* @param worktreeStories - Map of story ID to story from worktrees
|
|
70
|
+
* @returns Merged array of stories with worktree versions prioritized
|
|
71
|
+
*/
|
|
72
|
+
function mergeStories(mainStories, worktreeStories) {
|
|
73
|
+
const merged = [];
|
|
74
|
+
for (const mainStory of mainStories) {
|
|
75
|
+
const storyId = mainStory.frontmatter.id;
|
|
76
|
+
// Check if worktree version exists
|
|
77
|
+
if (worktreeStories.has(storyId)) {
|
|
78
|
+
// Use worktree version (more up-to-date)
|
|
79
|
+
merged.push(worktreeStories.get(storyId));
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// Use main repo version
|
|
83
|
+
merged.push(mainStory);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return merged;
|
|
87
|
+
}
|
|
8
88
|
/**
|
|
9
89
|
* Find all stories in the stories/ folder structure
|
|
10
90
|
* Globs stories/*\u200B/story.md and returns all story objects
|
|
@@ -15,7 +95,7 @@ export function findAllStories(sdlcRoot) {
|
|
|
15
95
|
if (!fs.existsSync(storiesFolder)) {
|
|
16
96
|
return [];
|
|
17
97
|
}
|
|
18
|
-
const
|
|
98
|
+
const mainStories = [];
|
|
19
99
|
const pattern = path.join(storiesFolder, '*', STORY_FILENAME);
|
|
20
100
|
try {
|
|
21
101
|
const storyPaths = glob.sync(pattern);
|
|
@@ -23,7 +103,7 @@ export function findAllStories(sdlcRoot) {
|
|
|
23
103
|
try {
|
|
24
104
|
const canonicalPath = fs.realpathSync(storyPath);
|
|
25
105
|
const story = parseStory(canonicalPath);
|
|
26
|
-
|
|
106
|
+
mainStories.push({ ...story, path: canonicalPath });
|
|
27
107
|
}
|
|
28
108
|
catch (err) {
|
|
29
109
|
// Skip malformed stories or symlinks that can't be resolved
|
|
@@ -35,7 +115,10 @@ export function findAllStories(sdlcRoot) {
|
|
|
35
115
|
// If glob fails, return empty array
|
|
36
116
|
return [];
|
|
37
117
|
}
|
|
38
|
-
|
|
118
|
+
// Load stories from active worktrees
|
|
119
|
+
const worktreeStories = loadStoriesFromWorktrees(sdlcRoot);
|
|
120
|
+
// Merge: worktree version takes precedence
|
|
121
|
+
return mergeStories(mainStories, worktreeStories);
|
|
39
122
|
}
|
|
40
123
|
/**
|
|
41
124
|
* Find stories by frontmatter status
|
|
@@ -370,4 +453,280 @@ export function getBoardStats(sdlcRoot) {
|
|
|
370
453
|
}
|
|
371
454
|
return stats;
|
|
372
455
|
}
|
|
456
|
+
/**
|
|
457
|
+
* Maximum allowed pattern length to prevent ReDoS attacks
|
|
458
|
+
*/
|
|
459
|
+
const MAX_PATTERN_LENGTH = 100;
|
|
460
|
+
/**
|
|
461
|
+
* Check if a label matches a glob pattern.
|
|
462
|
+
* Supports wildcard (*) for pattern matching with proper regex escaping.
|
|
463
|
+
*
|
|
464
|
+
* @param label - The label to test
|
|
465
|
+
* @param pattern - The glob pattern (e.g., 'epic-*', '*-test')
|
|
466
|
+
* @returns true if label matches pattern
|
|
467
|
+
* @throws Error if pattern exceeds maximum length
|
|
468
|
+
*
|
|
469
|
+
* @example
|
|
470
|
+
* labelMatchesPattern('epic-ticketing', 'epic-*') // true
|
|
471
|
+
* labelMatchesPattern('sprint-2024-q1', 'epic-*') // false
|
|
472
|
+
* labelMatchesPattern('test.label', 'test.label') // true (special chars escaped)
|
|
473
|
+
*/
|
|
474
|
+
export function labelMatchesPattern(label, pattern) {
|
|
475
|
+
// Security: Prevent ReDoS by limiting pattern length
|
|
476
|
+
if (pattern.length > MAX_PATTERN_LENGTH) {
|
|
477
|
+
throw new Error(`Pattern exceeds maximum length of ${MAX_PATTERN_LENGTH} characters`);
|
|
478
|
+
}
|
|
479
|
+
// Handle empty pattern edge case
|
|
480
|
+
if (pattern === '') {
|
|
481
|
+
return label === '';
|
|
482
|
+
}
|
|
483
|
+
// Escape special regex characters except *
|
|
484
|
+
// Characters that need escaping: . + ? ^ $ { } ( ) | [ ] \
|
|
485
|
+
const escapedPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
|
|
486
|
+
// Convert glob wildcard (*) to regex (.*)
|
|
487
|
+
const regexPattern = escapedPattern.replace(/\*/g, '.*');
|
|
488
|
+
// Create anchored regex (exact match from start to end)
|
|
489
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
490
|
+
return regex.test(label);
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Find stories by exact label match.
|
|
494
|
+
* Returns stories sorted by priority ascending.
|
|
495
|
+
*
|
|
496
|
+
* @param sdlcRoot - Path to .ai-sdlc folder
|
|
497
|
+
* @param label - Exact label to match
|
|
498
|
+
* @returns Array of stories with the specified label
|
|
499
|
+
*
|
|
500
|
+
* @example
|
|
501
|
+
* findStoriesByLabel(sdlcRoot, 'epic-ticketing')
|
|
502
|
+
*/
|
|
503
|
+
export function findStoriesByLabel(sdlcRoot, label) {
|
|
504
|
+
const allStories = findAllStories(sdlcRoot);
|
|
505
|
+
return allStories
|
|
506
|
+
.filter(story => story.frontmatter.labels.includes(label))
|
|
507
|
+
.sort((a, b) => {
|
|
508
|
+
// Sort by priority ascending
|
|
509
|
+
if (a.frontmatter.priority !== b.frontmatter.priority) {
|
|
510
|
+
return a.frontmatter.priority - b.frontmatter.priority;
|
|
511
|
+
}
|
|
512
|
+
// Tiebreaker: sort by creation date
|
|
513
|
+
return a.frontmatter.created.localeCompare(b.frontmatter.created);
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Find stories by multiple labels with AND/OR logic.
|
|
518
|
+
* Returns stories sorted by priority ascending.
|
|
519
|
+
*
|
|
520
|
+
* @param sdlcRoot - Path to .ai-sdlc folder
|
|
521
|
+
* @param labels - Array of labels to match
|
|
522
|
+
* @param mode - 'all' requires all labels (AND), 'any' requires at least one (OR)
|
|
523
|
+
* @returns Array of matching stories
|
|
524
|
+
*
|
|
525
|
+
* @example
|
|
526
|
+
* // Stories with BOTH epic-ticketing AND team-backend
|
|
527
|
+
* findStoriesByLabels(sdlcRoot, ['epic-ticketing', 'team-backend'], 'all')
|
|
528
|
+
*
|
|
529
|
+
* // Stories with EITHER epic-ticketing OR epic-auth
|
|
530
|
+
* findStoriesByLabels(sdlcRoot, ['epic-ticketing', 'epic-auth'], 'any')
|
|
531
|
+
*/
|
|
532
|
+
export function findStoriesByLabels(sdlcRoot, labels, mode) {
|
|
533
|
+
// Return empty array for empty labels input
|
|
534
|
+
if (labels.length === 0) {
|
|
535
|
+
return [];
|
|
536
|
+
}
|
|
537
|
+
const allStories = findAllStories(sdlcRoot);
|
|
538
|
+
const filtered = allStories.filter(story => {
|
|
539
|
+
if (mode === 'all') {
|
|
540
|
+
// All labels must be present (AND logic)
|
|
541
|
+
return labels.every(label => story.frontmatter.labels.includes(label));
|
|
542
|
+
}
|
|
543
|
+
else {
|
|
544
|
+
// At least one label must be present (OR logic)
|
|
545
|
+
return labels.some(label => story.frontmatter.labels.includes(label));
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
return filtered.sort((a, b) => {
|
|
549
|
+
// Sort by priority ascending
|
|
550
|
+
if (a.frontmatter.priority !== b.frontmatter.priority) {
|
|
551
|
+
return a.frontmatter.priority - b.frontmatter.priority;
|
|
552
|
+
}
|
|
553
|
+
// Tiebreaker: sort by creation date
|
|
554
|
+
return a.frontmatter.created.localeCompare(b.frontmatter.created);
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Find stories by glob pattern matching on labels.
|
|
559
|
+
* Returns deduplicated stories sorted by priority ascending.
|
|
560
|
+
*
|
|
561
|
+
* @param sdlcRoot - Path to .ai-sdlc folder
|
|
562
|
+
* @param pattern - Glob pattern (e.g., 'epic-*', '*-test', 'team-*-backend')
|
|
563
|
+
* @returns Array of stories with at least one matching label
|
|
564
|
+
*
|
|
565
|
+
* @example
|
|
566
|
+
* findStoriesByPattern(sdlcRoot, 'epic-*') // All stories with epic-* labels
|
|
567
|
+
* findStoriesByPattern(sdlcRoot, '*-test') // All stories with *-test labels
|
|
568
|
+
*/
|
|
569
|
+
export function findStoriesByPattern(sdlcRoot, pattern) {
|
|
570
|
+
// Return empty array for empty pattern
|
|
571
|
+
if (pattern === '') {
|
|
572
|
+
return [];
|
|
573
|
+
}
|
|
574
|
+
const allStories = findAllStories(sdlcRoot);
|
|
575
|
+
const filtered = allStories.filter(story => {
|
|
576
|
+
// Check if any label matches the pattern
|
|
577
|
+
return story.frontmatter.labels.some(label => labelMatchesPattern(label, pattern));
|
|
578
|
+
});
|
|
579
|
+
// Deduplicate by story ID (in case multiple labels match)
|
|
580
|
+
const seen = new Set();
|
|
581
|
+
const deduplicated = filtered.filter(story => {
|
|
582
|
+
if (seen.has(story.frontmatter.id)) {
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
seen.add(story.frontmatter.id);
|
|
586
|
+
return true;
|
|
587
|
+
});
|
|
588
|
+
return deduplicated.sort((a, b) => {
|
|
589
|
+
// Sort by priority ascending
|
|
590
|
+
if (a.frontmatter.priority !== b.frontmatter.priority) {
|
|
591
|
+
return a.frontmatter.priority - b.frontmatter.priority;
|
|
592
|
+
}
|
|
593
|
+
// Tiebreaker: sort by creation date
|
|
594
|
+
return a.frontmatter.created.localeCompare(b.frontmatter.created);
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Get all unique labels across all stories.
|
|
599
|
+
* Returns labels sorted alphabetically.
|
|
600
|
+
*
|
|
601
|
+
* @param sdlcRoot - Path to .ai-sdlc folder
|
|
602
|
+
* @returns Sorted array of unique labels
|
|
603
|
+
*
|
|
604
|
+
* @example
|
|
605
|
+
* getUniqueLabels(sdlcRoot) // ['epic-auth', 'epic-ticketing', 'team-backend', ...]
|
|
606
|
+
*/
|
|
607
|
+
export function getUniqueLabels(sdlcRoot) {
|
|
608
|
+
const allStories = findAllStories(sdlcRoot);
|
|
609
|
+
const labelsSet = new Set();
|
|
610
|
+
for (const story of allStories) {
|
|
611
|
+
for (const label of story.frontmatter.labels) {
|
|
612
|
+
labelsSet.add(label);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return Array.from(labelsSet).sort();
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Get grouping summaries for a specific dimension.
|
|
619
|
+
* Returns groupings sorted by story count descending.
|
|
620
|
+
*
|
|
621
|
+
* @param sdlcRoot - Path to .ai-sdlc folder
|
|
622
|
+
* @param dimension - Grouping dimension ('thematic', 'temporal', 'structural')
|
|
623
|
+
* @returns Array of grouping summaries with story counts and status breakdowns
|
|
624
|
+
*
|
|
625
|
+
* @example
|
|
626
|
+
* getGroupings(sdlcRoot, 'thematic')
|
|
627
|
+
* // [
|
|
628
|
+
* // { id: 'ticketing', label: 'epic-ticketing', dimension: 'thematic',
|
|
629
|
+
* // storyCount: 5, statusBreakdown: { backlog: 2, ready: 1, ... } },
|
|
630
|
+
* // ...
|
|
631
|
+
* // ]
|
|
632
|
+
*/
|
|
633
|
+
export function getGroupings(sdlcRoot, dimension) {
|
|
634
|
+
// Find the default configuration for this dimension
|
|
635
|
+
const config = DEFAULT_GROUPINGS.find(g => g.dimension === dimension);
|
|
636
|
+
if (!config) {
|
|
637
|
+
return [];
|
|
638
|
+
}
|
|
639
|
+
const allStories = findAllStories(sdlcRoot);
|
|
640
|
+
const prefix = config.prefix;
|
|
641
|
+
// Collect all labels matching this dimension's prefix
|
|
642
|
+
const groupingMap = new Map();
|
|
643
|
+
for (const story of allStories) {
|
|
644
|
+
const matchingLabels = story.frontmatter.labels.filter(label => label.startsWith(prefix));
|
|
645
|
+
// Check for cardinality violations (multiple labels when cardinality is 'single')
|
|
646
|
+
if (config.cardinality === 'single' && matchingLabels.length > 1) {
|
|
647
|
+
console.warn(`Story ${story.frontmatter.id} has multiple ${dimension} labels (${matchingLabels.join(', ')}) ` +
|
|
648
|
+
`but cardinality is 'single'. Consider using only one ${prefix}* label per story.`);
|
|
649
|
+
}
|
|
650
|
+
for (const label of matchingLabels) {
|
|
651
|
+
if (!groupingMap.has(label)) {
|
|
652
|
+
groupingMap.set(label, {
|
|
653
|
+
label,
|
|
654
|
+
stories: [],
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
groupingMap.get(label).stories.push(story);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
// Convert to GroupingSummary array
|
|
661
|
+
const summaries = [];
|
|
662
|
+
for (const [label, data] of groupingMap.entries()) {
|
|
663
|
+
// Extract ID by removing prefix
|
|
664
|
+
const id = label.substring(prefix.length);
|
|
665
|
+
// Calculate status breakdown
|
|
666
|
+
const statusBreakdown = {
|
|
667
|
+
backlog: 0,
|
|
668
|
+
ready: 0,
|
|
669
|
+
'in-progress': 0,
|
|
670
|
+
done: 0,
|
|
671
|
+
blocked: 0,
|
|
672
|
+
};
|
|
673
|
+
for (const story of data.stories) {
|
|
674
|
+
statusBreakdown[story.frontmatter.status]++;
|
|
675
|
+
}
|
|
676
|
+
summaries.push({
|
|
677
|
+
id,
|
|
678
|
+
label,
|
|
679
|
+
dimension,
|
|
680
|
+
storyCount: data.stories.length,
|
|
681
|
+
statusBreakdown,
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
// Sort by story count descending
|
|
685
|
+
return summaries.sort((a, b) => b.storyCount - a.storyCount);
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Find stories by epic label (convenience wrapper).
|
|
689
|
+
* Queries for stories with 'epic-{epicId}' label.
|
|
690
|
+
*
|
|
691
|
+
* @param sdlcRoot - Path to .ai-sdlc folder
|
|
692
|
+
* @param epicId - Epic identifier (without 'epic-' prefix)
|
|
693
|
+
* @returns Array of stories with the specified epic label
|
|
694
|
+
*
|
|
695
|
+
* @example
|
|
696
|
+
* findStoriesByEpic(sdlcRoot, 'ticketing-integration')
|
|
697
|
+
* // Returns stories with label 'epic-ticketing-integration'
|
|
698
|
+
*/
|
|
699
|
+
export function findStoriesByEpic(sdlcRoot, epicId) {
|
|
700
|
+
return findStoriesByPattern(sdlcRoot, `epic-${epicId}`);
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Find stories by sprint label (convenience wrapper).
|
|
704
|
+
* Queries for stories with 'sprint-{sprintId}' label.
|
|
705
|
+
*
|
|
706
|
+
* @param sdlcRoot - Path to .ai-sdlc folder
|
|
707
|
+
* @param sprintId - Sprint identifier (without 'sprint-' prefix)
|
|
708
|
+
* @returns Array of stories with the specified sprint label
|
|
709
|
+
*
|
|
710
|
+
* @example
|
|
711
|
+
* findStoriesBySprint(sdlcRoot, '2024-q1')
|
|
712
|
+
* // Returns stories with label 'sprint-2024-q1'
|
|
713
|
+
*/
|
|
714
|
+
export function findStoriesBySprint(sdlcRoot, sprintId) {
|
|
715
|
+
return findStoriesByPattern(sdlcRoot, `sprint-${sprintId}`);
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Find stories by team label (convenience wrapper).
|
|
719
|
+
* Queries for stories with 'team-{teamId}' label.
|
|
720
|
+
*
|
|
721
|
+
* @param sdlcRoot - Path to .ai-sdlc folder
|
|
722
|
+
* @param teamId - Team identifier (without 'team-' prefix)
|
|
723
|
+
* @returns Array of stories with the specified team label
|
|
724
|
+
*
|
|
725
|
+
* @example
|
|
726
|
+
* findStoriesByTeam(sdlcRoot, 'backend')
|
|
727
|
+
* // Returns stories with label 'team-backend'
|
|
728
|
+
*/
|
|
729
|
+
export function findStoriesByTeam(sdlcRoot, teamId) {
|
|
730
|
+
return findStoriesByPattern(sdlcRoot, `team-${teamId}`);
|
|
731
|
+
}
|
|
373
732
|
//# sourceMappingURL=kanban.js.map
|