specweave 0.33.2 → 0.33.3
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/CLAUDE.md +56 -0
- package/dist/plugins/specweave-ado/lib/per-us-sync.d.ts +120 -0
- package/dist/plugins/specweave-ado/lib/per-us-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-ado/lib/per-us-sync.js +276 -0
- package/dist/plugins/specweave-ado/lib/per-us-sync.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-client-v2.d.ts +4 -1
- package/dist/plugins/specweave-github/lib/github-client-v2.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-client-v2.js +13 -3
- package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
- package/dist/plugins/specweave-github/lib/per-us-sync.d.ts +97 -0
- package/dist/plugins/specweave-github/lib/per-us-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/per-us-sync.js +274 -0
- package/dist/plugins/specweave-github/lib/per-us-sync.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/per-us-sync.d.ts +113 -0
- package/dist/plugins/specweave-jira/lib/per-us-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/per-us-sync.js +254 -0
- package/dist/plugins/specweave-jira/lib/per-us-sync.js.map +1 -0
- package/dist/src/core/config/config-manager.d.ts.map +1 -1
- package/dist/src/core/config/config-manager.js +58 -0
- package/dist/src/core/config/config-manager.js.map +1 -1
- package/dist/src/core/config/types.d.ts +80 -0
- package/dist/src/core/config/types.d.ts.map +1 -1
- package/dist/src/core/config/types.js.map +1 -1
- package/dist/src/core/living-docs/cross-project-sync.d.ts +87 -15
- package/dist/src/core/living-docs/cross-project-sync.d.ts.map +1 -1
- package/dist/src/core/living-docs/cross-project-sync.js +147 -28
- package/dist/src/core/living-docs/cross-project-sync.js.map +1 -1
- package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
- package/dist/src/core/living-docs/living-docs-sync.js +26 -22
- package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
- package/dist/src/core/living-docs/types.d.ts +24 -3
- package/dist/src/core/living-docs/types.d.ts.map +1 -1
- package/dist/src/core/types/config.d.ts +79 -0
- package/dist/src/core/types/config.d.ts.map +1 -1
- package/dist/src/core/types/config.js.map +1 -1
- package/dist/src/sync/sync-coordinator.d.ts +20 -0
- package/dist/src/sync/sync-coordinator.d.ts.map +1 -1
- package/dist/src/sync/sync-coordinator.js +258 -33
- package/dist/src/sync/sync-coordinator.js.map +1 -1
- package/dist/src/utils/project-resolver.d.ts +156 -0
- package/dist/src/utils/project-resolver.d.ts.map +1 -0
- package/dist/src/utils/project-resolver.js +587 -0
- package/dist/src/utils/project-resolver.js.map +1 -0
- package/package.json +1 -1
- package/plugins/specweave/hooks/hooks.json +10 -0
- package/plugins/specweave/hooks/user-prompt-submit.sh +105 -3
- package/plugins/specweave/hooks/v2/guards/per-us-project-validator.sh +281 -0
- package/plugins/specweave/hooks/v2/handlers/living-specs-handler.sh +29 -0
- package/plugins/specweave-ado/lib/per-us-sync.js +247 -0
- package/plugins/specweave-ado/lib/per-us-sync.ts +410 -0
- package/plugins/specweave-github/lib/github-client-v2.js +10 -3
- package/plugins/specweave-github/lib/github-client-v2.ts +15 -3
- package/plugins/specweave-github/lib/per-us-sync.js +241 -0
- package/plugins/specweave-github/lib/per-us-sync.ts +375 -0
- package/plugins/specweave-jira/lib/per-us-sync.js +224 -0
- package/plugins/specweave-jira/lib/per-us-sync.ts +366 -0
|
@@ -5,8 +5,11 @@
|
|
|
5
5
|
* Groups user stories by their target project and syncs each group
|
|
6
6
|
* to the appropriate project folder in living docs.
|
|
7
7
|
*
|
|
8
|
+
* Supports both 1-level (project) and 2-level (project/board) structures.
|
|
9
|
+
*
|
|
8
10
|
* @module core/living-docs/cross-project-sync
|
|
9
11
|
* @since v0.33.0
|
|
12
|
+
* @updated v0.34.0 - Added 2-level structure support (project/board)
|
|
10
13
|
*/
|
|
11
14
|
import type { UserStoryData } from './types.js';
|
|
12
15
|
import { Logger } from '../../utils/logger.js';
|
|
@@ -45,50 +48,119 @@ export interface CrossProjectSyncOptions {
|
|
|
45
48
|
export declare class CrossProjectSync {
|
|
46
49
|
private projectRoot;
|
|
47
50
|
private logger;
|
|
51
|
+
private structureConfig;
|
|
48
52
|
constructor(projectRoot: string, options?: {
|
|
49
53
|
logger?: Logger;
|
|
50
54
|
});
|
|
51
55
|
/**
|
|
52
|
-
*
|
|
56
|
+
* Get structure level configuration (cached)
|
|
57
|
+
*/
|
|
58
|
+
private getStructureConfig;
|
|
59
|
+
/**
|
|
60
|
+
* Check if 2-level structure is detected
|
|
61
|
+
*/
|
|
62
|
+
is2LevelStructure(): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Get full target path for a user story
|
|
65
|
+
*
|
|
66
|
+
* For 1-level: returns project
|
|
67
|
+
* For 2-level: returns project/board
|
|
68
|
+
*
|
|
69
|
+
* @param us - User story
|
|
70
|
+
* @param defaultProject - Default project if US doesn't specify
|
|
71
|
+
* @param defaultBoard - Default board if US doesn't specify (2-level only)
|
|
72
|
+
* @returns Full path like "project" or "project/board"
|
|
73
|
+
*/
|
|
74
|
+
getUSTargetPath(us: UserStoryData, defaultProject: string, defaultBoard?: string): string;
|
|
75
|
+
/**
|
|
76
|
+
* Group user stories by their target path (project or project/board)
|
|
53
77
|
*
|
|
54
78
|
* @param userStories - User stories to group
|
|
55
79
|
* @param defaultProject - Default project for USs without explicit project
|
|
56
|
-
* @
|
|
80
|
+
* @param defaultBoard - Default board for USs without explicit board (2-level only)
|
|
81
|
+
* @returns Map of target path to user stories
|
|
82
|
+
*/
|
|
83
|
+
groupByProject(userStories: UserStoryData[], defaultProject: string, defaultBoard?: string): Map<string, UserStoryData[]>;
|
|
84
|
+
/**
|
|
85
|
+
* Group user stories by project only (ignoring board)
|
|
86
|
+
* Useful for project-level operations like external sync
|
|
57
87
|
*/
|
|
58
|
-
|
|
88
|
+
groupByProjectOnly(userStories: UserStoryData[], defaultProject: string): Map<string, UserStoryData[]>;
|
|
59
89
|
/**
|
|
60
|
-
* Check if an increment is cross-project (spans multiple projects)
|
|
90
|
+
* Check if an increment is cross-project (spans multiple projects/boards)
|
|
91
|
+
*
|
|
92
|
+
* For 1-level: checks if USs target different projects
|
|
93
|
+
* For 2-level: checks if USs target different project/board combinations
|
|
61
94
|
*/
|
|
62
|
-
isCrossProject(userStories: UserStoryData[], defaultProject: string): boolean;
|
|
95
|
+
isCrossProject(userStories: UserStoryData[], defaultProject: string, defaultBoard?: string): boolean;
|
|
63
96
|
/**
|
|
64
|
-
*
|
|
97
|
+
* Check if an increment spans multiple projects (ignoring boards)
|
|
98
|
+
* Useful for external sync which groups at project level
|
|
99
|
+
*/
|
|
100
|
+
spansMultipleProjects(userStories: UserStoryData[], defaultProject: string): boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Get the specs folder path for a target path
|
|
103
|
+
* Handles both 1-level (project) and 2-level (project/board)
|
|
104
|
+
*
|
|
105
|
+
* @param targetPath - Either "project" or "project/board"
|
|
106
|
+
*/
|
|
107
|
+
getSpecsPath(targetPath: string): string;
|
|
108
|
+
/**
|
|
109
|
+
* Get the specs folder path for a project (legacy, 1-level only)
|
|
110
|
+
* @deprecated Use getSpecsPath() instead for 2-level support
|
|
65
111
|
*/
|
|
66
112
|
getProjectSpecsPath(projectId: string): string;
|
|
67
113
|
/**
|
|
68
|
-
* Get the feature folder path for a
|
|
114
|
+
* Get the feature folder path for a target path and feature
|
|
115
|
+
* Handles both 1-level and 2-level structures
|
|
116
|
+
*
|
|
117
|
+
* @param targetPath - Either "project" or "project/board"
|
|
118
|
+
* @param featureId - Feature ID (e.g., "FS-125")
|
|
119
|
+
*/
|
|
120
|
+
getFeaturePath(targetPath: string, featureId: string): string;
|
|
121
|
+
/**
|
|
122
|
+
* Ensure specs folder exists for a target path
|
|
123
|
+
* Handles both 1-level and 2-level structures
|
|
69
124
|
*/
|
|
70
|
-
|
|
125
|
+
ensureSpecsFolder(targetPath: string): Promise<string>;
|
|
71
126
|
/**
|
|
72
|
-
* Ensure project specs folder exists
|
|
127
|
+
* Ensure project specs folder exists (legacy, 1-level only)
|
|
128
|
+
* @deprecated Use ensureSpecsFolder() instead
|
|
73
129
|
*/
|
|
74
130
|
ensureProjectFolder(projectId: string): Promise<string>;
|
|
75
131
|
/**
|
|
76
|
-
* Ensure feature folder exists within a
|
|
132
|
+
* Ensure feature folder exists within a target path
|
|
133
|
+
* Handles both 1-level and 2-level structures
|
|
77
134
|
*/
|
|
78
|
-
ensureFeatureFolder(
|
|
135
|
+
ensureFeatureFolder(targetPath: string, featureId: string): Promise<string>;
|
|
79
136
|
/**
|
|
80
137
|
* Generate cross-references section for FEATURE.md
|
|
81
138
|
*
|
|
82
139
|
* @param featureId - The feature ID (e.g., "FS-125")
|
|
83
|
-
* @param
|
|
84
|
-
* @param
|
|
140
|
+
* @param allTargetPaths - All target paths this feature spans (can be "project" or "project/board")
|
|
141
|
+
* @param currentTargetPath - The current target path being generated
|
|
85
142
|
* @returns Markdown content for "Related Projects" section
|
|
86
143
|
*/
|
|
87
|
-
generateCrossReferences(featureId: string,
|
|
144
|
+
generateCrossReferences(featureId: string, allTargetPaths: string[], currentTargetPath: string): string;
|
|
88
145
|
/**
|
|
89
146
|
* Generate cross-reference frontmatter for US files
|
|
147
|
+
*
|
|
148
|
+
* @param allTargetPaths - All target paths (can be "project" or "project/board")
|
|
149
|
+
* @param currentTargetPath - Current target path
|
|
150
|
+
*/
|
|
151
|
+
generateRelatedProjectsFrontmatter(allTargetPaths: string[], currentTargetPath: string): string;
|
|
152
|
+
/**
|
|
153
|
+
* Extract project ID from target path
|
|
154
|
+
* "project" -> "project"
|
|
155
|
+
* "project/board" -> "project"
|
|
156
|
+
*/
|
|
157
|
+
extractProjectId(targetPath: string): string;
|
|
158
|
+
/**
|
|
159
|
+
* Extract board ID from target path (for 2-level structures)
|
|
160
|
+
* "project" -> undefined
|
|
161
|
+
* "project/board" -> "board"
|
|
90
162
|
*/
|
|
91
|
-
|
|
163
|
+
extractBoardId(targetPath: string): string | undefined;
|
|
92
164
|
/**
|
|
93
165
|
* Log cross-project sync summary
|
|
94
166
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cross-project-sync.d.ts","sourceRoot":"","sources":["../../../../src/core/living-docs/cross-project-sync.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"cross-project-sync.d.ts","sourceRoot":"","sources":["../../../../src/core/living-docs/cross-project-sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,28 +5,83 @@
|
|
|
5
5
|
* Groups user stories by their target project and syncs each group
|
|
6
6
|
* to the appropriate project folder in living docs.
|
|
7
7
|
*
|
|
8
|
+
* Supports both 1-level (project) and 2-level (project/board) structures.
|
|
9
|
+
*
|
|
8
10
|
* @module core/living-docs/cross-project-sync
|
|
9
11
|
* @since v0.33.0
|
|
12
|
+
* @updated v0.34.0 - Added 2-level structure support (project/board)
|
|
10
13
|
*/
|
|
11
14
|
import * as path from 'node:path';
|
|
12
15
|
import { consoleLogger } from '../../utils/logger.js';
|
|
13
16
|
import { ensureDir } from '../../utils/fs-native.js';
|
|
17
|
+
import { detectStructureLevel } from '../../utils/structure-level-detector.js';
|
|
14
18
|
/**
|
|
15
19
|
* Cross-Project Sync Orchestrator
|
|
16
20
|
*/
|
|
17
21
|
export class CrossProjectSync {
|
|
18
22
|
constructor(projectRoot, options = {}) {
|
|
23
|
+
this.structureConfig = null;
|
|
19
24
|
this.projectRoot = projectRoot;
|
|
20
25
|
this.logger = options.logger ?? consoleLogger;
|
|
21
26
|
}
|
|
22
27
|
/**
|
|
23
|
-
*
|
|
28
|
+
* Get structure level configuration (cached)
|
|
29
|
+
*/
|
|
30
|
+
getStructureConfig() {
|
|
31
|
+
if (!this.structureConfig) {
|
|
32
|
+
this.structureConfig = detectStructureLevel(this.projectRoot);
|
|
33
|
+
}
|
|
34
|
+
return this.structureConfig;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Check if 2-level structure is detected
|
|
38
|
+
*/
|
|
39
|
+
is2LevelStructure() {
|
|
40
|
+
return this.getStructureConfig().level === 2;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get full target path for a user story
|
|
44
|
+
*
|
|
45
|
+
* For 1-level: returns project
|
|
46
|
+
* For 2-level: returns project/board
|
|
47
|
+
*
|
|
48
|
+
* @param us - User story
|
|
49
|
+
* @param defaultProject - Default project if US doesn't specify
|
|
50
|
+
* @param defaultBoard - Default board if US doesn't specify (2-level only)
|
|
51
|
+
* @returns Full path like "project" or "project/board"
|
|
52
|
+
*/
|
|
53
|
+
getUSTargetPath(us, defaultProject, defaultBoard) {
|
|
54
|
+
const project = us.project || defaultProject;
|
|
55
|
+
if (this.is2LevelStructure()) {
|
|
56
|
+
const board = us.board || defaultBoard || 'default';
|
|
57
|
+
return `${project}/${board}`;
|
|
58
|
+
}
|
|
59
|
+
return project;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Group user stories by their target path (project or project/board)
|
|
24
63
|
*
|
|
25
64
|
* @param userStories - User stories to group
|
|
26
65
|
* @param defaultProject - Default project for USs without explicit project
|
|
27
|
-
* @
|
|
66
|
+
* @param defaultBoard - Default board for USs without explicit board (2-level only)
|
|
67
|
+
* @returns Map of target path to user stories
|
|
68
|
+
*/
|
|
69
|
+
groupByProject(userStories, defaultProject, defaultBoard) {
|
|
70
|
+
const groups = new Map();
|
|
71
|
+
for (const us of userStories) {
|
|
72
|
+
const targetPath = this.getUSTargetPath(us, defaultProject, defaultBoard);
|
|
73
|
+
if (!groups.has(targetPath)) {
|
|
74
|
+
groups.set(targetPath, []);
|
|
75
|
+
}
|
|
76
|
+
groups.get(targetPath).push(us);
|
|
77
|
+
}
|
|
78
|
+
return groups;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Group user stories by project only (ignoring board)
|
|
82
|
+
* Useful for project-level operations like external sync
|
|
28
83
|
*/
|
|
29
|
-
|
|
84
|
+
groupByProjectOnly(userStories, defaultProject) {
|
|
30
85
|
const groups = new Map();
|
|
31
86
|
for (const us of userStories) {
|
|
32
87
|
const project = us.project || defaultProject;
|
|
@@ -38,26 +93,61 @@ export class CrossProjectSync {
|
|
|
38
93
|
return groups;
|
|
39
94
|
}
|
|
40
95
|
/**
|
|
41
|
-
* Check if an increment is cross-project (spans multiple projects)
|
|
96
|
+
* Check if an increment is cross-project (spans multiple projects/boards)
|
|
97
|
+
*
|
|
98
|
+
* For 1-level: checks if USs target different projects
|
|
99
|
+
* For 2-level: checks if USs target different project/board combinations
|
|
100
|
+
*/
|
|
101
|
+
isCrossProject(userStories, defaultProject, defaultBoard) {
|
|
102
|
+
const groups = this.groupByProject(userStories, defaultProject, defaultBoard);
|
|
103
|
+
return groups.size > 1;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Check if an increment spans multiple projects (ignoring boards)
|
|
107
|
+
* Useful for external sync which groups at project level
|
|
42
108
|
*/
|
|
43
|
-
|
|
44
|
-
const groups = this.
|
|
109
|
+
spansMultipleProjects(userStories, defaultProject) {
|
|
110
|
+
const groups = this.groupByProjectOnly(userStories, defaultProject);
|
|
45
111
|
return groups.size > 1;
|
|
46
112
|
}
|
|
47
113
|
/**
|
|
48
|
-
* Get the specs folder path for a
|
|
114
|
+
* Get the specs folder path for a target path
|
|
115
|
+
* Handles both 1-level (project) and 2-level (project/board)
|
|
116
|
+
*
|
|
117
|
+
* @param targetPath - Either "project" or "project/board"
|
|
118
|
+
*/
|
|
119
|
+
getSpecsPath(targetPath) {
|
|
120
|
+
return path.join(this.projectRoot, '.specweave/docs/internal/specs', targetPath);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Get the specs folder path for a project (legacy, 1-level only)
|
|
124
|
+
* @deprecated Use getSpecsPath() instead for 2-level support
|
|
49
125
|
*/
|
|
50
126
|
getProjectSpecsPath(projectId) {
|
|
51
127
|
return path.join(this.projectRoot, '.specweave/docs/internal/specs', projectId);
|
|
52
128
|
}
|
|
53
129
|
/**
|
|
54
|
-
* Get the feature folder path for a
|
|
130
|
+
* Get the feature folder path for a target path and feature
|
|
131
|
+
* Handles both 1-level and 2-level structures
|
|
132
|
+
*
|
|
133
|
+
* @param targetPath - Either "project" or "project/board"
|
|
134
|
+
* @param featureId - Feature ID (e.g., "FS-125")
|
|
135
|
+
*/
|
|
136
|
+
getFeaturePath(targetPath, featureId) {
|
|
137
|
+
return path.join(this.getSpecsPath(targetPath), featureId);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Ensure specs folder exists for a target path
|
|
141
|
+
* Handles both 1-level and 2-level structures
|
|
55
142
|
*/
|
|
56
|
-
|
|
57
|
-
|
|
143
|
+
async ensureSpecsFolder(targetPath) {
|
|
144
|
+
const folderPath = this.getSpecsPath(targetPath);
|
|
145
|
+
await ensureDir(folderPath);
|
|
146
|
+
return folderPath;
|
|
58
147
|
}
|
|
59
148
|
/**
|
|
60
|
-
* Ensure project specs folder exists
|
|
149
|
+
* Ensure project specs folder exists (legacy, 1-level only)
|
|
150
|
+
* @deprecated Use ensureSpecsFolder() instead
|
|
61
151
|
*/
|
|
62
152
|
async ensureProjectFolder(projectId) {
|
|
63
153
|
const folderPath = this.getProjectSpecsPath(projectId);
|
|
@@ -65,10 +155,11 @@ export class CrossProjectSync {
|
|
|
65
155
|
return folderPath;
|
|
66
156
|
}
|
|
67
157
|
/**
|
|
68
|
-
* Ensure feature folder exists within a
|
|
158
|
+
* Ensure feature folder exists within a target path
|
|
159
|
+
* Handles both 1-level and 2-level structures
|
|
69
160
|
*/
|
|
70
|
-
async ensureFeatureFolder(
|
|
71
|
-
const folderPath = this.getFeaturePath(
|
|
161
|
+
async ensureFeatureFolder(targetPath, featureId) {
|
|
162
|
+
const folderPath = this.getFeaturePath(targetPath, featureId);
|
|
72
163
|
await ensureDir(folderPath);
|
|
73
164
|
return folderPath;
|
|
74
165
|
}
|
|
@@ -76,39 +167,67 @@ export class CrossProjectSync {
|
|
|
76
167
|
* Generate cross-references section for FEATURE.md
|
|
77
168
|
*
|
|
78
169
|
* @param featureId - The feature ID (e.g., "FS-125")
|
|
79
|
-
* @param
|
|
80
|
-
* @param
|
|
170
|
+
* @param allTargetPaths - All target paths this feature spans (can be "project" or "project/board")
|
|
171
|
+
* @param currentTargetPath - The current target path being generated
|
|
81
172
|
* @returns Markdown content for "Related Projects" section
|
|
82
173
|
*/
|
|
83
|
-
generateCrossReferences(featureId,
|
|
84
|
-
const
|
|
85
|
-
if (
|
|
174
|
+
generateCrossReferences(featureId, allTargetPaths, currentTargetPath) {
|
|
175
|
+
const otherPaths = allTargetPaths.filter(p => p !== currentTargetPath);
|
|
176
|
+
if (otherPaths.length === 0) {
|
|
86
177
|
return '';
|
|
87
178
|
}
|
|
179
|
+
// Determine depth of current path for relative link calculation
|
|
180
|
+
const currentDepth = currentTargetPath.split('/').length;
|
|
88
181
|
const lines = [
|
|
89
182
|
'',
|
|
90
183
|
'## Related Projects',
|
|
91
184
|
'',
|
|
92
|
-
|
|
185
|
+
this.is2LevelStructure()
|
|
186
|
+
? 'This feature spans multiple project/board combinations:'
|
|
187
|
+
: 'This feature spans multiple projects:',
|
|
93
188
|
''
|
|
94
189
|
];
|
|
95
|
-
for (const
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
190
|
+
for (const targetPath of otherPaths) {
|
|
191
|
+
// Calculate relative path based on depth
|
|
192
|
+
// From project/board/FS-XXX/FEATURE.md to ../../../other-project/other-board/FS-XXX/
|
|
193
|
+
const upLevels = '../'.repeat(currentDepth + 1); // +1 for featureId folder
|
|
194
|
+
const relativePath = `${upLevels}${targetPath}/${featureId}/`;
|
|
195
|
+
// Display label (show full path for clarity)
|
|
196
|
+
const label = targetPath;
|
|
197
|
+
lines.push(`- [${label}](${relativePath})`);
|
|
99
198
|
}
|
|
100
199
|
lines.push('');
|
|
101
200
|
return lines.join('\n');
|
|
102
201
|
}
|
|
103
202
|
/**
|
|
104
203
|
* Generate cross-reference frontmatter for US files
|
|
204
|
+
*
|
|
205
|
+
* @param allTargetPaths - All target paths (can be "project" or "project/board")
|
|
206
|
+
* @param currentTargetPath - Current target path
|
|
105
207
|
*/
|
|
106
|
-
generateRelatedProjectsFrontmatter(
|
|
107
|
-
const
|
|
108
|
-
if (
|
|
208
|
+
generateRelatedProjectsFrontmatter(allTargetPaths, currentTargetPath) {
|
|
209
|
+
const otherPaths = allTargetPaths.filter(p => p !== currentTargetPath);
|
|
210
|
+
if (otherPaths.length === 0) {
|
|
109
211
|
return '';
|
|
110
212
|
}
|
|
111
|
-
return `related_projects: [${
|
|
213
|
+
return `related_projects: [${otherPaths.join(', ')}]`;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Extract project ID from target path
|
|
217
|
+
* "project" -> "project"
|
|
218
|
+
* "project/board" -> "project"
|
|
219
|
+
*/
|
|
220
|
+
extractProjectId(targetPath) {
|
|
221
|
+
return targetPath.split('/')[0];
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Extract board ID from target path (for 2-level structures)
|
|
225
|
+
* "project" -> undefined
|
|
226
|
+
* "project/board" -> "board"
|
|
227
|
+
*/
|
|
228
|
+
extractBoardId(targetPath) {
|
|
229
|
+
const parts = targetPath.split('/');
|
|
230
|
+
return parts.length > 1 ? parts[1] : undefined;
|
|
112
231
|
}
|
|
113
232
|
/**
|
|
114
233
|
* Log cross-project sync summary
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cross-project-sync.js","sourceRoot":"","sources":["../../../../src/core/living-docs/cross-project-sync.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"cross-project-sync.js","sourceRoot":"","sources":["../../../../src/core/living-docs/cross-project-sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"living-docs-sync.d.ts","sourceRoot":"","sources":["../../../../src/core/living-docs/living-docs-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAUH,OAAO,EAAE,MAAM,EAAiB,MAAM,uBAAuB,CAAC;AAO9D,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACV,UAAU,EACV,aAAa,EACb,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAoBpB,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,uBAAuB,EAAE,CAAC;AAE5F,qBAAa,cAAc;IACzB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAS;gBAEd,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO;IAUlE;;;;;;;OAOG;IACH,YAAY,IAAI,MAAM;IAItB;;;;;OAKG;YACW,mBAAmB;IASjC;;OAEG;IACG,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"living-docs-sync.d.ts","sourceRoot":"","sources":["../../../../src/core/living-docs/living-docs-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAUH,OAAO,EAAE,MAAM,EAAiB,MAAM,uBAAuB,CAAC;AAO9D,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACV,UAAU,EACV,aAAa,EACb,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAoBpB,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,uBAAuB,EAAE,CAAC;AAE5F,qBAAa,cAAc;IACzB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAS;gBAEd,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO;IAUlE;;;;;;;OAOG;IACH,YAAY,IAAI,MAAM;IAItB;;;;;OAKG;YACW,mBAAmB;IASjC;;OAEG;IACG,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,UAAU,CAAC;IA8PxF;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;YACW,wBAAwB;IAwDtC;;;;;;;;;OASG;IACH,OAAO,CAAC,8BAA8B;IA+BtC;;;;;;;;;;;;;OAaG;YACW,2BAA2B;IAmEzC;;;;OAIG;IACH,OAAO,CAAC,wBAAwB;IA6BhC;;OAEG;YACW,sBAAsB;IAKpC;;;;;;;;;;;;;;OAcG;YACW,kBAAkB;IA6GhC;;OAEG;YACW,uBAAuB;IAcrC;;OAEG;YACW,wBAAwB;IAmFtC;;;OAGG;YACW,sBAAsB;IAqGpC;;OAEG;YACW,iBAAiB;IA0C/B;;;;;;;;;;;OAWG;YACW,oBAAoB;IAwClC;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,iBAAiB;IAwCzB;;OAEG;YACW,kBAAkB;IAqHhC;;;;OAIG;YACW,sBAAsB;IAkCpC;;OAEG;YACW,kBAAkB;IAmChC;;;;;;;;;;;;OAYG;YACW,mBAAmB;IA6EjC;;;;;;OAMG;IACH;;;;;;;;;;;OAWG;YACW,mBAAmB;IAyIjC;;;;;;;;;OASG;YACW,YAAY;IA0F1B;;;;;;;;;;;;OAYG;YACW,UAAU;IAyFxB;;;;;;;;;;;;OAYG;YACW,SAAS;IAyFvB;;;;;;;;;OASG;YACW,4BAA4B;CAmB3C"}
|
|
@@ -124,27 +124,31 @@ export class LivingDocsSync {
|
|
|
124
124
|
this.logger.log(`📚 Syncing ${incrementId} → ${featureId}...`);
|
|
125
125
|
// Step 4: Parse increment spec
|
|
126
126
|
const parsed = await this.parseIncrementSpec(incrementId);
|
|
127
|
-
// Step 4b: Detect cross-project increments (v0.33.0
|
|
128
|
-
// If USs target different projects, sync to multiple
|
|
127
|
+
// Step 4b: Detect cross-project/cross-board increments (v0.33.0+, v0.34.0 2-level)
|
|
128
|
+
// If USs target different projects (or project/board combinations), sync to multiple folders
|
|
129
129
|
const defaultProject = parsed.frontmatter.project || resolvedProjectPath;
|
|
130
|
-
const
|
|
130
|
+
const defaultBoard = parsed.frontmatter.board;
|
|
131
|
+
const isCrossProject = this.crossProjectSync.isCrossProject(parsed.userStories, defaultProject, defaultBoard);
|
|
131
132
|
if (isCrossProject) {
|
|
132
|
-
this.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
133
|
+
const is2Level = this.crossProjectSync.is2LevelStructure();
|
|
134
|
+
this.logger.log(`📦 Cross-project increment detected${is2Level ? ' (2-level structure)' : ''}`);
|
|
135
|
+
// Group by full target path (project for 1-level, project/board for 2-level)
|
|
136
|
+
const groups = this.crossProjectSync.groupByProject(parsed.userStories, defaultProject, defaultBoard);
|
|
137
|
+
this.logger.log(` ${groups.size} target${is2Level ? ' paths' : ' projects'}: ${[...groups.keys()].join(', ')}`);
|
|
138
|
+
// For cross-project increments, create feature folder in each target path
|
|
139
|
+
// USs are synced to their respective target's feature folder
|
|
140
|
+
// Cross-references are added to link related projects/boards
|
|
141
|
+
for (const [targetPath, projectStories] of groups) {
|
|
142
|
+
// Use full target path (may be "project" or "project/board")
|
|
143
|
+
const crossProjectPath = path.join(basePath, targetPath, featureId);
|
|
144
|
+
this.logger.log(` 📁 Syncing ${projectStories.length} USs to ${targetPath}/${featureId}/`);
|
|
141
145
|
if (!options.dryRun) {
|
|
142
146
|
await ensureDir(crossProjectPath);
|
|
143
|
-
// Generate FEATURE.md with cross-references
|
|
144
|
-
const crossRefs = this.crossProjectSync.generateCrossReferences(featureId, [...groups.keys()],
|
|
147
|
+
// Generate FEATURE.md with cross-references (now uses target paths)
|
|
148
|
+
const crossRefs = this.crossProjectSync.generateCrossReferences(featureId, [...groups.keys()], targetPath);
|
|
145
149
|
let featureContent = generateFeatureFile(featureId, {
|
|
146
150
|
...parsed,
|
|
147
|
-
userStories: projectStories // Only this
|
|
151
|
+
userStories: projectStories // Only this target's USs
|
|
148
152
|
}, incrementId);
|
|
149
153
|
// Append cross-references section
|
|
150
154
|
if (crossRefs) {
|
|
@@ -152,8 +156,8 @@ export class LivingDocsSync {
|
|
|
152
156
|
}
|
|
153
157
|
await fs.writeFile(path.join(crossProjectPath, 'FEATURE.md'), featureContent, 'utf-8');
|
|
154
158
|
result.filesCreated.push(path.join(crossProjectPath, 'FEATURE.md'));
|
|
155
|
-
// Create user story files for this
|
|
156
|
-
const
|
|
159
|
+
// Create user story files for this target path
|
|
160
|
+
const allTargetPaths = [...groups.keys()];
|
|
157
161
|
for (const story of projectStories) {
|
|
158
162
|
const existingFile = await findExistingUserStoryFile(crossProjectPath, story.id, this.logger);
|
|
159
163
|
let storyFile;
|
|
@@ -164,15 +168,15 @@ export class LivingDocsSync {
|
|
|
164
168
|
const storySlug = story.title.toLowerCase().replace(/[^a-z0-9]+/g, '-');
|
|
165
169
|
storyFile = path.join(crossProjectPath, `${story.id.toLowerCase()}-${storySlug}.md`);
|
|
166
170
|
}
|
|
167
|
-
// Pass allProjects for related_projects frontmatter (v0.33.0
|
|
171
|
+
// Pass allProjects for related_projects frontmatter (v0.33.0+, v0.34.0 paths)
|
|
168
172
|
const storyContent = generateUserStoryFile(story, featureId, incrementId, {
|
|
169
173
|
...parsed,
|
|
170
174
|
userStories: projectStories
|
|
171
|
-
}, { allProjects });
|
|
175
|
+
}, { allProjects: allTargetPaths });
|
|
172
176
|
await fs.writeFile(storyFile, storyContent, 'utf-8');
|
|
173
177
|
result.filesCreated.push(storyFile);
|
|
174
178
|
}
|
|
175
|
-
// Cleanup and sync tasks for this
|
|
179
|
+
// Cleanup and sync tasks for this target's USs
|
|
176
180
|
await cleanupDuplicateFiles(crossProjectPath, this.logger);
|
|
177
181
|
await cleanupTempFiles(crossProjectPath, this.logger);
|
|
178
182
|
await this.syncTasksToUserStories(incrementId, featureId, projectStories, crossProjectPath);
|
|
@@ -182,8 +186,8 @@ export class LivingDocsSync {
|
|
|
182
186
|
result.success = true;
|
|
183
187
|
this.crossProjectSync.logSyncSummary({
|
|
184
188
|
success: true,
|
|
185
|
-
projects: [...groups.entries()].map(([
|
|
186
|
-
projectId,
|
|
189
|
+
projects: [...groups.entries()].map(([targetPath, stories]) => ({
|
|
190
|
+
projectId: targetPath, // Now contains full path (project or project/board)
|
|
187
191
|
success: true,
|
|
188
192
|
userStories: stories.map(s => s.id),
|
|
189
193
|
filesCreated: [],
|