specweave 0.7.1 → 0.8.2
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 +307 -11
- package/README.md +41 -3
- package/bin/specweave.js +0 -27
- package/dist/cli/commands/import-docs.d.ts +21 -0
- package/dist/cli/commands/import-docs.d.ts.map +1 -0
- package/dist/cli/commands/import-docs.js +146 -0
- package/dist/cli/commands/import-docs.js.map +1 -0
- package/dist/cli/commands/init-multiproject.d.ts +11 -0
- package/dist/cli/commands/init-multiproject.d.ts.map +1 -0
- package/dist/cli/commands/init-multiproject.js +202 -0
- package/dist/cli/commands/init-multiproject.js.map +1 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +4 -93
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/migrate-to-multiproject.d.ts +37 -0
- package/dist/cli/commands/migrate-to-multiproject.d.ts.map +1 -0
- package/dist/cli/commands/migrate-to-multiproject.js +189 -0
- package/dist/cli/commands/migrate-to-multiproject.js.map +1 -0
- package/dist/cli/commands/migrate-to-profiles.d.ts +25 -0
- package/dist/cli/commands/migrate-to-profiles.d.ts.map +1 -0
- package/dist/cli/commands/migrate-to-profiles.js +350 -0
- package/dist/cli/commands/migrate-to-profiles.js.map +1 -0
- package/dist/cli/commands/switch-project.d.ts +13 -0
- package/dist/cli/commands/switch-project.d.ts.map +1 -0
- package/dist/cli/commands/switch-project.js +91 -0
- package/dist/cli/commands/switch-project.js.map +1 -0
- package/dist/cli/helpers/issue-tracker/index.js +4 -4
- package/dist/cli/helpers/issue-tracker/index.js.map +1 -1
- package/dist/cli/helpers/issue-tracker/utils.d.ts +6 -3
- package/dist/cli/helpers/issue-tracker/utils.d.ts.map +1 -1
- package/dist/cli/helpers/issue-tracker/utils.js +9 -7
- package/dist/cli/helpers/issue-tracker/utils.js.map +1 -1
- package/dist/core/brownfield/analyzer.d.ts +86 -0
- package/dist/core/brownfield/analyzer.d.ts.map +1 -0
- package/dist/core/brownfield/analyzer.js +365 -0
- package/dist/core/brownfield/analyzer.js.map +1 -0
- package/dist/core/brownfield/importer.d.ts +76 -0
- package/dist/core/brownfield/importer.d.ts.map +1 -0
- package/dist/core/brownfield/importer.js +287 -0
- package/dist/core/brownfield/importer.js.map +1 -0
- package/dist/core/config-manager.d.ts +47 -0
- package/dist/core/config-manager.d.ts.map +1 -0
- package/dist/core/config-manager.js +136 -0
- package/dist/core/config-manager.js.map +1 -0
- package/dist/core/project-manager.d.ts +127 -0
- package/dist/core/project-manager.d.ts.map +1 -0
- package/dist/core/project-manager.js +524 -0
- package/dist/core/project-manager.js.map +1 -0
- package/dist/core/sync/profile-manager.d.ts +72 -0
- package/dist/core/sync/profile-manager.d.ts.map +1 -0
- package/dist/core/sync/profile-manager.js +338 -0
- package/dist/core/sync/profile-manager.js.map +1 -0
- package/dist/core/sync/profile-selector.d.ts +52 -0
- package/dist/core/sync/profile-selector.d.ts.map +1 -0
- package/dist/core/sync/profile-selector.js +179 -0
- package/dist/core/sync/profile-selector.js.map +1 -0
- package/dist/core/sync/project-context.d.ts +81 -0
- package/dist/core/sync/project-context.d.ts.map +1 -0
- package/dist/core/sync/project-context.js +354 -0
- package/dist/core/sync/project-context.js.map +1 -0
- package/dist/core/sync/rate-limiter.d.ts +116 -0
- package/dist/core/sync/rate-limiter.d.ts.map +1 -0
- package/dist/core/sync/rate-limiter.js +308 -0
- package/dist/core/sync/rate-limiter.js.map +1 -0
- package/dist/core/sync/time-range-selector.d.ts +48 -0
- package/dist/core/sync/time-range-selector.d.ts.map +1 -0
- package/dist/core/sync/time-range-selector.js +224 -0
- package/dist/core/sync/time-range-selector.js.map +1 -0
- package/dist/core/types/config.d.ts +4 -0
- package/dist/core/types/config.d.ts.map +1 -1
- package/dist/core/types/config.js.map +1 -1
- package/dist/core/types/sync-profile.d.ts +205 -0
- package/dist/core/types/sync-profile.d.ts.map +1 -0
- package/dist/core/types/sync-profile.js +8 -0
- package/dist/core/types/sync-profile.js.map +1 -0
- package/dist/utils/project-detection.d.ts +141 -0
- package/dist/utils/project-detection.d.ts.map +1 -0
- package/dist/utils/project-detection.js +321 -0
- package/dist/utils/project-detection.js.map +1 -0
- package/package.json +2 -1
- package/plugins/specweave/agents/pm/AGENT.md +7 -4
- package/plugins/specweave/commands/specweave-abandon.md +17 -17
- package/plugins/specweave/commands/specweave-check-tests.md +14 -14
- package/plugins/specweave/commands/specweave-costs.md +1 -1
- package/plugins/specweave/commands/specweave-do.md +12 -12
- package/plugins/specweave/commands/specweave-done.md +28 -15
- package/plugins/specweave/commands/specweave-import-docs.md +212 -0
- package/plugins/specweave/commands/specweave-increment.md +10 -10
- package/plugins/specweave/commands/specweave-init-multiproject.md +146 -0
- package/plugins/specweave/commands/specweave-next.md +16 -16
- package/plugins/specweave/commands/specweave-pause.md +17 -17
- package/plugins/specweave/commands/specweave-progress.md +10 -10
- package/plugins/specweave/commands/specweave-qa.md +11 -11
- package/plugins/specweave/commands/specweave-resume.md +22 -22
- package/plugins/specweave/commands/specweave-status.md +18 -18
- package/plugins/specweave/commands/specweave-switch-project.md +168 -0
- package/plugins/specweave/commands/specweave-sync-docs.md +1 -1
- package/plugins/specweave/commands/specweave-sync-tasks.md +9 -9
- package/plugins/specweave/commands/specweave-tdd-cycle.md +7 -0
- package/plugins/specweave/commands/specweave-tdd-green.md +7 -0
- package/plugins/specweave/commands/specweave-tdd-red.md +7 -0
- package/plugins/specweave/commands/specweave-tdd-refactor.md +7 -0
- package/plugins/specweave/commands/specweave-translate.md +1 -1
- package/plugins/specweave/commands/specweave-update-scope.md +8 -8
- package/plugins/specweave/commands/specweave-validate.md +18 -20
- package/plugins/specweave/commands/specweave.md +5 -5
- package/plugins/specweave/skills/SKILLS-INDEX.md +1 -1
- package/plugins/specweave/skills/increment-planner/SKILL.md +40 -4
- package/plugins/specweave/skills/increment-quality-judge/SKILL.md +5 -5
- package/plugins/specweave/skills/increment-quality-judge-v2/SKILL.md +5 -5
- package/plugins/specweave/skills/specweave-detector/SKILL.md +3 -3
- package/plugins/specweave-ado/commands/{close-workitem.md → specweave-ado-close-workitem.md} +1 -1
- package/plugins/specweave-ado/commands/{create-workitem.md → specweave-ado-create-workitem.md} +1 -1
- package/plugins/specweave-ado/commands/{status.md → specweave-ado-status.md} +1 -1
- package/plugins/specweave-ado/commands/{sync.md → specweave-ado-sync.md} +1 -1
- package/plugins/specweave-ado/lib/ado-client-v2.ts +547 -0
- package/plugins/specweave-github/commands/{close-issue.md → specweave-github-close-issue.md} +1 -1
- package/plugins/specweave-github/commands/{create-issue.md → specweave-github-create-issue.md} +1 -1
- package/plugins/specweave-github/commands/{status.md → specweave-github-status.md} +1 -1
- package/plugins/specweave-github/commands/{sync-tasks.md → specweave-github-sync-tasks.md} +1 -1
- package/plugins/specweave-github/commands/specweave-github-sync.md +568 -0
- package/plugins/specweave-github/lib/github-client-v2.ts +555 -0
- package/plugins/specweave-infrastructure/commands/{monitor-setup.md → specweave-infrastructure-monitor-setup.md} +1 -1
- package/plugins/specweave-infrastructure/commands/{slo-implement.md → specweave-infrastructure-slo-implement.md} +1 -1
- package/plugins/specweave-jira/commands/{sync.md → specweave-jira-sync.md} +1 -1
- package/plugins/specweave-jira/lib/jira-client-v2.ts +529 -0
- package/plugins/specweave-ml/commands/{ml-deploy.md → specweave-ml-deploy.md} +1 -1
- package/plugins/specweave-ml/commands/{ml-evaluate.md → specweave-ml-evaluate.md} +1 -1
- package/plugins/specweave-ml/commands/{ml-explain.md → specweave-ml-explain.md} +1 -1
- package/plugins/specweave-ml/commands/{ml-pipeline.md → specweave-ml-pipeline.md} +1 -1
- package/src/templates/AGENTS.md.template +1 -0
- package/src/templates/CLAUDE.md.template +1 -0
- package/plugins/specweave/hooks/post-increment-plugin-detect.sh +0 -142
- package/plugins/specweave/hooks/pre-task-plugin-detect.sh +0 -96
- package/plugins/specweave/skills/plugin-detector/SKILL.md +0 -324
- package/plugins/specweave-github/commands/sync.md +0 -443
- /package/plugins/specweave/{commands/README.md → COMMANDS.md} +0 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync Profile Types for Multi-Project External Integration
|
|
3
|
+
*
|
|
4
|
+
* Supports multiple projects per provider (GitHub, JIRA, ADO, etc.)
|
|
5
|
+
* with time range filtering and rate limiting protection.
|
|
6
|
+
*/
|
|
7
|
+
export type SyncProvider = 'github' | 'jira' | 'ado';
|
|
8
|
+
export interface GitHubConfig {
|
|
9
|
+
owner: string;
|
|
10
|
+
repo: string;
|
|
11
|
+
}
|
|
12
|
+
export interface JiraConfig {
|
|
13
|
+
domain: string;
|
|
14
|
+
projectKey: string;
|
|
15
|
+
issueType?: 'Epic' | 'Story' | 'Task';
|
|
16
|
+
}
|
|
17
|
+
export interface AdoConfig {
|
|
18
|
+
organization: string;
|
|
19
|
+
project: string;
|
|
20
|
+
workItemType?: 'Epic' | 'Feature' | 'User Story';
|
|
21
|
+
areaPath?: string;
|
|
22
|
+
iterationPath?: string;
|
|
23
|
+
}
|
|
24
|
+
export type ProviderConfig = GitHubConfig | JiraConfig | AdoConfig;
|
|
25
|
+
export type TimeRangePreset = '1W' | '2W' | '1M' | '3M' | '6M' | '1Y' | 'ALL';
|
|
26
|
+
export interface TimeRangeConfig {
|
|
27
|
+
/** Preset time range */
|
|
28
|
+
preset?: TimeRangePreset;
|
|
29
|
+
/** Custom date range */
|
|
30
|
+
custom?: {
|
|
31
|
+
start: string;
|
|
32
|
+
end?: string;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export interface TimeRangeEstimate {
|
|
36
|
+
/** Estimated work items to sync */
|
|
37
|
+
items: number;
|
|
38
|
+
/** Estimated API requests */
|
|
39
|
+
apiCalls: number;
|
|
40
|
+
/** Estimated sync duration in minutes */
|
|
41
|
+
durationMinutes: number;
|
|
42
|
+
/** Rate limit impact level */
|
|
43
|
+
rateLimitImpact: 'low' | 'medium' | 'high' | 'critical';
|
|
44
|
+
}
|
|
45
|
+
export interface RateLimitConfig {
|
|
46
|
+
/** Maximum items to sync in one operation */
|
|
47
|
+
maxItemsPerSync: number;
|
|
48
|
+
/** Warn user when exceeding this threshold */
|
|
49
|
+
warnThreshold: number;
|
|
50
|
+
}
|
|
51
|
+
export interface RateLimitStatus {
|
|
52
|
+
/** Remaining requests in current window */
|
|
53
|
+
remaining: number;
|
|
54
|
+
/** Total limit per window */
|
|
55
|
+
limit: number;
|
|
56
|
+
/** When the rate limit resets (ISO timestamp) */
|
|
57
|
+
resetAt: string;
|
|
58
|
+
/** Percentage of limit used */
|
|
59
|
+
percentUsed: number;
|
|
60
|
+
}
|
|
61
|
+
export interface ProviderRateLimits {
|
|
62
|
+
github: {
|
|
63
|
+
limit: 5000;
|
|
64
|
+
window: '1h';
|
|
65
|
+
thresholds: {
|
|
66
|
+
low: 250;
|
|
67
|
+
medium: 1000;
|
|
68
|
+
high: 2500;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
jira: {
|
|
72
|
+
limit: 100;
|
|
73
|
+
window: '1m';
|
|
74
|
+
thresholds: {
|
|
75
|
+
low: 25;
|
|
76
|
+
medium: 50;
|
|
77
|
+
high: 75;
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
ado: {
|
|
81
|
+
limit: 200;
|
|
82
|
+
window: '5m';
|
|
83
|
+
thresholds: {
|
|
84
|
+
low: 50;
|
|
85
|
+
medium: 100;
|
|
86
|
+
high: 150;
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
export interface SyncProfile {
|
|
91
|
+
/** Provider type */
|
|
92
|
+
provider: SyncProvider;
|
|
93
|
+
/** Human-readable name */
|
|
94
|
+
displayName: string;
|
|
95
|
+
/** Optional description */
|
|
96
|
+
description?: string;
|
|
97
|
+
/** Provider-specific configuration */
|
|
98
|
+
config: ProviderConfig;
|
|
99
|
+
/** Time range configuration */
|
|
100
|
+
timeRange: {
|
|
101
|
+
/** Default time range for this profile */
|
|
102
|
+
default: TimeRangePreset;
|
|
103
|
+
/** Maximum allowed time range */
|
|
104
|
+
max: TimeRangePreset;
|
|
105
|
+
};
|
|
106
|
+
/** Rate limiting configuration */
|
|
107
|
+
rateLimits?: RateLimitConfig;
|
|
108
|
+
/** Project context (for smart detection) */
|
|
109
|
+
projectContext?: {
|
|
110
|
+
/** Project name (e.g., "SpecWeave", "Client A Mobile") */
|
|
111
|
+
name: string;
|
|
112
|
+
/** Project description */
|
|
113
|
+
description?: string;
|
|
114
|
+
/** Keywords for auto-detection */
|
|
115
|
+
keywords?: string[];
|
|
116
|
+
/** Team/organization name */
|
|
117
|
+
team?: string;
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
export interface SyncProfiles {
|
|
121
|
+
/** Active profile (default selection) */
|
|
122
|
+
activeProfile?: string;
|
|
123
|
+
/** All available profiles */
|
|
124
|
+
profiles: Record<string, SyncProfile>;
|
|
125
|
+
}
|
|
126
|
+
export interface IncrementSyncMetadata {
|
|
127
|
+
/** Which profile this increment uses */
|
|
128
|
+
profile: string;
|
|
129
|
+
/** External issue/work item number */
|
|
130
|
+
issueNumber?: number;
|
|
131
|
+
/** External issue/work item key (for JIRA) */
|
|
132
|
+
issueKey?: string;
|
|
133
|
+
/** Direct URL to external issue */
|
|
134
|
+
issueUrl?: string;
|
|
135
|
+
/** Time range used for this sync */
|
|
136
|
+
timeRange: TimeRangePreset | 'custom';
|
|
137
|
+
/** Custom time range (if timeRange === 'custom') */
|
|
138
|
+
customTimeRange?: {
|
|
139
|
+
start: string;
|
|
140
|
+
end?: string;
|
|
141
|
+
};
|
|
142
|
+
/** When sync was created */
|
|
143
|
+
createdAt: string;
|
|
144
|
+
/** Last successful sync timestamp */
|
|
145
|
+
lastSyncAt?: string;
|
|
146
|
+
/** Sync status */
|
|
147
|
+
status?: 'active' | 'paused' | 'failed' | 'completed';
|
|
148
|
+
/** Last sync error (if any) */
|
|
149
|
+
lastError?: {
|
|
150
|
+
message: string;
|
|
151
|
+
timestamp: string;
|
|
152
|
+
rateLimited?: boolean;
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
export interface ProjectContext {
|
|
156
|
+
/** Unique project identifier (kebab-case) */
|
|
157
|
+
id: string;
|
|
158
|
+
/** Project name */
|
|
159
|
+
name: string;
|
|
160
|
+
/** Project description */
|
|
161
|
+
description: string;
|
|
162
|
+
/** Team/organization */
|
|
163
|
+
team?: string;
|
|
164
|
+
/** Keywords for auto-detection */
|
|
165
|
+
keywords: string[];
|
|
166
|
+
/** Default sync profile for this project */
|
|
167
|
+
defaultSyncProfile?: string;
|
|
168
|
+
/** Project-specific specs folder */
|
|
169
|
+
specsFolder: string;
|
|
170
|
+
/** Related increments (for tracking) */
|
|
171
|
+
increments?: string[];
|
|
172
|
+
}
|
|
173
|
+
export interface SyncConfiguration {
|
|
174
|
+
/** Active profile */
|
|
175
|
+
activeProfile?: string;
|
|
176
|
+
/** All sync profiles */
|
|
177
|
+
profiles: Record<string, SyncProfile>;
|
|
178
|
+
/** Project contexts */
|
|
179
|
+
projects?: Record<string, ProjectContext>;
|
|
180
|
+
/** Global settings */
|
|
181
|
+
settings?: {
|
|
182
|
+
/** Auto-detect project from increment description */
|
|
183
|
+
autoDetectProject?: boolean;
|
|
184
|
+
/** Default time range for new syncs */
|
|
185
|
+
defaultTimeRange?: TimeRangePreset;
|
|
186
|
+
/** Enable rate limit protection */
|
|
187
|
+
rateLimitProtection?: boolean;
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
export interface ProfileValidationResult {
|
|
191
|
+
valid: boolean;
|
|
192
|
+
errors: string[];
|
|
193
|
+
warnings?: string[];
|
|
194
|
+
}
|
|
195
|
+
export interface ProjectDetectionResult {
|
|
196
|
+
/** Matched project (if any) */
|
|
197
|
+
project?: ProjectContext;
|
|
198
|
+
/** Confidence score (0-1) */
|
|
199
|
+
confidence: number;
|
|
200
|
+
/** Matched keywords */
|
|
201
|
+
matchedKeywords: string[];
|
|
202
|
+
/** Suggested sync profile */
|
|
203
|
+
suggestedProfile?: string;
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=sync-profile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-profile.d.ts","sourceRoot":"","sources":["../../../src/core/types/sync-profile.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-profile.js","sourceRoot":"","sources":["../../../src/core/types/sync-profile.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project ID Auto-Detection Utilities
|
|
3
|
+
*
|
|
4
|
+
* Detects project ID from:
|
|
5
|
+
* 1. Git remote (GitHub repo name)
|
|
6
|
+
* 2. Sync configuration (JIRA project key, ADO project name)
|
|
7
|
+
* 3. User prompt (fallback)
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Auto-detect project ID from git remote URL
|
|
11
|
+
*
|
|
12
|
+
* Extracts repository name from git remote URL
|
|
13
|
+
*
|
|
14
|
+
* @param projectRoot - Project root directory
|
|
15
|
+
* @returns Project ID (repo name) or null if not detected
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* // Git remote: https://github.com/anton-abyzov/specweave.git
|
|
19
|
+
* detectProjectIdFromGit('/path/to/project')
|
|
20
|
+
* // Returns: "specweave"
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* // Git remote: git@github.com:my-org/web-app.git
|
|
24
|
+
* detectProjectIdFromGit('/path/to/project')
|
|
25
|
+
* // Returns: "web-app"
|
|
26
|
+
*/
|
|
27
|
+
export declare function detectProjectIdFromGit(projectRoot: string): string | null;
|
|
28
|
+
/**
|
|
29
|
+
* Auto-detect project ID from sync configuration
|
|
30
|
+
*
|
|
31
|
+
* Checks existing sync profiles for JIRA project key or ADO project name
|
|
32
|
+
*
|
|
33
|
+
* @param projectRoot - Project root directory
|
|
34
|
+
* @returns Project ID from sync config or null if not detected
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* // Config has JIRA: { projectKey: "WEBAPP" }
|
|
38
|
+
* detectProjectIdFromSync('/path/to/project')
|
|
39
|
+
* // Returns: "webapp"
|
|
40
|
+
*/
|
|
41
|
+
export declare function detectProjectIdFromSync(projectRoot: string): string | null;
|
|
42
|
+
/**
|
|
43
|
+
* Prompt user for project ID
|
|
44
|
+
*
|
|
45
|
+
* Interactive prompt with validation and helpful examples
|
|
46
|
+
*
|
|
47
|
+
* @param suggestedId - Optional suggested project ID (from detection)
|
|
48
|
+
* @returns Project ID entered by user
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* await promptForProjectId()
|
|
52
|
+
* // User sees:
|
|
53
|
+
* // Project ID (matches GitHub repo, JIRA project, or ADO project): █
|
|
54
|
+
* // User enters: "web-app"
|
|
55
|
+
* // Returns: "web-app"
|
|
56
|
+
*/
|
|
57
|
+
export declare function promptForProjectId(suggestedId?: string): Promise<string>;
|
|
58
|
+
/**
|
|
59
|
+
* Auto-detect project ID synchronously (no prompts)
|
|
60
|
+
*
|
|
61
|
+
* Priority:
|
|
62
|
+
* 1. Git remote (GitHub repo name)
|
|
63
|
+
* 2. Sync configuration (JIRA/ADO project)
|
|
64
|
+
* 3. "default" (fallback)
|
|
65
|
+
*
|
|
66
|
+
* @param projectRoot - Project root directory
|
|
67
|
+
* @param options - Detection options
|
|
68
|
+
* @returns Detected project ID or "default"
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* // In git repo: https://github.com/anton-abyzov/specweave.git
|
|
72
|
+
* autoDetectProjectIdSync('/path/to/project')
|
|
73
|
+
* // Returns: "specweave"
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* // No git, no sync
|
|
77
|
+
* autoDetectProjectIdSync('/path/to/project')
|
|
78
|
+
* // Returns: "default"
|
|
79
|
+
*/
|
|
80
|
+
export declare function autoDetectProjectIdSync(projectRoot: string, options?: {
|
|
81
|
+
silent?: boolean;
|
|
82
|
+
}): string;
|
|
83
|
+
/**
|
|
84
|
+
* Auto-detect project ID with fallback chain (async version with prompts)
|
|
85
|
+
*
|
|
86
|
+
* Priority:
|
|
87
|
+
* 1. Git remote (GitHub repo name)
|
|
88
|
+
* 2. Sync configuration (JIRA/ADO project)
|
|
89
|
+
* 3. User prompt (with detected suggestion)
|
|
90
|
+
* 4. "default" (if user accepts default in prompt)
|
|
91
|
+
*
|
|
92
|
+
* @param projectRoot - Project root directory
|
|
93
|
+
* @param options - Detection options
|
|
94
|
+
* @returns Detected or prompted project ID
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* // In git repo: https://github.com/anton-abyzov/specweave.git
|
|
98
|
+
* await autoDetectProjectId('/path/to/project')
|
|
99
|
+
* // Output: "✅ Detected git repository: specweave"
|
|
100
|
+
* // Returns: "specweave"
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* // No git, no sync, user prompted
|
|
104
|
+
* await autoDetectProjectId('/path/to/project')
|
|
105
|
+
* // Output: "📝 No git repository or sync configuration detected."
|
|
106
|
+
* // Prompts user for project ID
|
|
107
|
+
* // Returns: user input (e.g., "my-project")
|
|
108
|
+
*/
|
|
109
|
+
export declare function autoDetectProjectId(projectRoot: string, options?: {
|
|
110
|
+
silent?: boolean;
|
|
111
|
+
promptIfNotDetected?: boolean;
|
|
112
|
+
}): Promise<string>;
|
|
113
|
+
/**
|
|
114
|
+
* Format project ID to display name
|
|
115
|
+
*
|
|
116
|
+
* Converts kebab-case/snake_case to Title Case
|
|
117
|
+
*
|
|
118
|
+
* @param projectId - Project ID (e.g., "web-app", "mobile_app")
|
|
119
|
+
* @returns Formatted name (e.g., "Web App", "Mobile App")
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* formatProjectName('web-app')
|
|
123
|
+
* // Returns: "Web App"
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* formatProjectName('mobile_app')
|
|
127
|
+
* // Returns: "Mobile App"
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* formatProjectName('specweave')
|
|
131
|
+
* // Returns: "SpecWeave"
|
|
132
|
+
*/
|
|
133
|
+
export declare function formatProjectName(projectId: string): string;
|
|
134
|
+
/**
|
|
135
|
+
* Validate project ID format
|
|
136
|
+
*
|
|
137
|
+
* @param projectId - Project ID to validate
|
|
138
|
+
* @returns True if valid, error message if invalid
|
|
139
|
+
*/
|
|
140
|
+
export declare function validateProjectId(projectId: string): true | string;
|
|
141
|
+
//# sourceMappingURL=project-detection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-detection.d.ts","sourceRoot":"","sources":["../../src/utils/project-detection.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkCzE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAsD1E;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,kBAAkB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA4B9E;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE;IACP,MAAM,CAAC,EAAE,OAAO,CAAC;CACb,GACL,MAAM,CAuBR;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,mBAAmB,CACvC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE;IACP,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC1B,GACL,OAAO,CAAC,MAAM,CAAC,CAsCjB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAkB3D;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAkBlE"}
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project ID Auto-Detection Utilities
|
|
3
|
+
*
|
|
4
|
+
* Detects project ID from:
|
|
5
|
+
* 1. Git remote (GitHub repo name)
|
|
6
|
+
* 2. Sync configuration (JIRA project key, ADO project name)
|
|
7
|
+
* 3. User prompt (fallback)
|
|
8
|
+
*/
|
|
9
|
+
import fs from 'fs-extra';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import inquirer from 'inquirer';
|
|
12
|
+
import { ConfigManager } from '../core/config-manager';
|
|
13
|
+
/**
|
|
14
|
+
* Auto-detect project ID from git remote URL
|
|
15
|
+
*
|
|
16
|
+
* Extracts repository name from git remote URL
|
|
17
|
+
*
|
|
18
|
+
* @param projectRoot - Project root directory
|
|
19
|
+
* @returns Project ID (repo name) or null if not detected
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* // Git remote: https://github.com/anton-abyzov/specweave.git
|
|
23
|
+
* detectProjectIdFromGit('/path/to/project')
|
|
24
|
+
* // Returns: "specweave"
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* // Git remote: git@github.com:my-org/web-app.git
|
|
28
|
+
* detectProjectIdFromGit('/path/to/project')
|
|
29
|
+
* // Returns: "web-app"
|
|
30
|
+
*/
|
|
31
|
+
export function detectProjectIdFromGit(projectRoot) {
|
|
32
|
+
try {
|
|
33
|
+
const gitConfigPath = path.join(projectRoot, '.git', 'config');
|
|
34
|
+
// Check if .git/config exists
|
|
35
|
+
if (!fs.existsSync(gitConfigPath)) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
// Read git config
|
|
39
|
+
const gitConfig = fs.readFileSync(gitConfigPath, 'utf8');
|
|
40
|
+
// Extract remote URL (match both HTTPS and SSH formats)
|
|
41
|
+
// HTTPS: https://github.com/owner/repo.git
|
|
42
|
+
// SSH: git@github.com:owner/repo.git
|
|
43
|
+
const httpsMatch = gitConfig.match(/url = https?:\/\/[^\/]+\/[^\/]+\/([^\/\s]+?)(?:\.git)?$/m);
|
|
44
|
+
const sshMatch = gitConfig.match(/url = git@[^:]+:[^\/]+\/([^\/\s]+?)(?:\.git)?$/m);
|
|
45
|
+
const match = httpsMatch || sshMatch;
|
|
46
|
+
if (match && match[1]) {
|
|
47
|
+
const repoName = match[1].trim();
|
|
48
|
+
// Validate repo name (lowercase, alphanumeric, hyphens, underscores)
|
|
49
|
+
if (/^[a-z0-9_-]+$/i.test(repoName)) {
|
|
50
|
+
return repoName.toLowerCase();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
// Silently fail (not in git repo or permission error)
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Auto-detect project ID from sync configuration
|
|
62
|
+
*
|
|
63
|
+
* Checks existing sync profiles for JIRA project key or ADO project name
|
|
64
|
+
*
|
|
65
|
+
* @param projectRoot - Project root directory
|
|
66
|
+
* @returns Project ID from sync config or null if not detected
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* // Config has JIRA: { projectKey: "WEBAPP" }
|
|
70
|
+
* detectProjectIdFromSync('/path/to/project')
|
|
71
|
+
* // Returns: "webapp"
|
|
72
|
+
*/
|
|
73
|
+
export function detectProjectIdFromSync(projectRoot) {
|
|
74
|
+
try {
|
|
75
|
+
const configManager = new ConfigManager(projectRoot);
|
|
76
|
+
const config = configManager.load();
|
|
77
|
+
// Check sync profiles for project identifiers
|
|
78
|
+
if (config.sync?.profiles) {
|
|
79
|
+
const profiles = Object.values(config.sync.profiles);
|
|
80
|
+
for (const profile of profiles) {
|
|
81
|
+
// GitHub profile (extract repo name)
|
|
82
|
+
if (profile.provider === 'github') {
|
|
83
|
+
const githubConfig = profile.config;
|
|
84
|
+
if (githubConfig?.repo) {
|
|
85
|
+
return githubConfig.repo.toLowerCase();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// JIRA profile (use project key)
|
|
89
|
+
if (profile.provider === 'jira') {
|
|
90
|
+
const jiraConfig = profile.config;
|
|
91
|
+
if (jiraConfig?.projectKey) {
|
|
92
|
+
return jiraConfig.projectKey.toLowerCase();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Azure DevOps profile (use project name)
|
|
96
|
+
if (profile.provider === 'ado') {
|
|
97
|
+
const adoConfig = profile.config;
|
|
98
|
+
if (adoConfig?.project) {
|
|
99
|
+
return adoConfig.project.toLowerCase().replace(/\s+/g, '-');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Legacy: Check old sync config format (pre-profiles)
|
|
105
|
+
if (config.sync?.jira?.projectKey) {
|
|
106
|
+
return config.sync.jira.projectKey.toLowerCase();
|
|
107
|
+
}
|
|
108
|
+
if (config.sync?.ado?.project) {
|
|
109
|
+
return config.sync.ado.project.toLowerCase().replace(/\s+/g, '-');
|
|
110
|
+
}
|
|
111
|
+
if (config.sync?.github?.repo) {
|
|
112
|
+
return config.sync.github.repo.toLowerCase();
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
// Config doesn't exist or is invalid
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Prompt user for project ID
|
|
123
|
+
*
|
|
124
|
+
* Interactive prompt with validation and helpful examples
|
|
125
|
+
*
|
|
126
|
+
* @param suggestedId - Optional suggested project ID (from detection)
|
|
127
|
+
* @returns Project ID entered by user
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* await promptForProjectId()
|
|
131
|
+
* // User sees:
|
|
132
|
+
* // Project ID (matches GitHub repo, JIRA project, or ADO project): █
|
|
133
|
+
* // User enters: "web-app"
|
|
134
|
+
* // Returns: "web-app"
|
|
135
|
+
*/
|
|
136
|
+
export async function promptForProjectId(suggestedId) {
|
|
137
|
+
const { projectId } = await inquirer.prompt([{
|
|
138
|
+
type: 'input',
|
|
139
|
+
name: 'projectId',
|
|
140
|
+
message: 'Project ID (matches GitHub repo, JIRA project, or ADO project):',
|
|
141
|
+
default: suggestedId || 'default',
|
|
142
|
+
validate: (input) => {
|
|
143
|
+
if (!input) {
|
|
144
|
+
return 'Project ID is required';
|
|
145
|
+
}
|
|
146
|
+
if (!/^[a-z0-9_-]+$/.test(input)) {
|
|
147
|
+
return 'Project ID must be lowercase, alphanumeric, with hyphens or underscores';
|
|
148
|
+
}
|
|
149
|
+
if (input.length < 2) {
|
|
150
|
+
return 'Project ID must be at least 2 characters';
|
|
151
|
+
}
|
|
152
|
+
if (input.length > 64) {
|
|
153
|
+
return 'Project ID must be at most 64 characters';
|
|
154
|
+
}
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
}]);
|
|
158
|
+
return projectId;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Auto-detect project ID synchronously (no prompts)
|
|
162
|
+
*
|
|
163
|
+
* Priority:
|
|
164
|
+
* 1. Git remote (GitHub repo name)
|
|
165
|
+
* 2. Sync configuration (JIRA/ADO project)
|
|
166
|
+
* 3. "default" (fallback)
|
|
167
|
+
*
|
|
168
|
+
* @param projectRoot - Project root directory
|
|
169
|
+
* @param options - Detection options
|
|
170
|
+
* @returns Detected project ID or "default"
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* // In git repo: https://github.com/anton-abyzov/specweave.git
|
|
174
|
+
* autoDetectProjectIdSync('/path/to/project')
|
|
175
|
+
* // Returns: "specweave"
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* // No git, no sync
|
|
179
|
+
* autoDetectProjectIdSync('/path/to/project')
|
|
180
|
+
* // Returns: "default"
|
|
181
|
+
*/
|
|
182
|
+
export function autoDetectProjectIdSync(projectRoot, options = {}) {
|
|
183
|
+
const { silent = false } = options;
|
|
184
|
+
// 1. Try git remote
|
|
185
|
+
const gitProjectId = detectProjectIdFromGit(projectRoot);
|
|
186
|
+
if (gitProjectId) {
|
|
187
|
+
if (!silent) {
|
|
188
|
+
console.log(`✅ Detected git repository: ${gitProjectId}`);
|
|
189
|
+
}
|
|
190
|
+
return gitProjectId;
|
|
191
|
+
}
|
|
192
|
+
// 2. Try sync config
|
|
193
|
+
const syncProjectId = detectProjectIdFromSync(projectRoot);
|
|
194
|
+
if (syncProjectId) {
|
|
195
|
+
if (!silent) {
|
|
196
|
+
console.log(`✅ Detected sync configuration: ${syncProjectId}`);
|
|
197
|
+
}
|
|
198
|
+
return syncProjectId;
|
|
199
|
+
}
|
|
200
|
+
// 3. Fallback to "default"
|
|
201
|
+
return 'default';
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Auto-detect project ID with fallback chain (async version with prompts)
|
|
205
|
+
*
|
|
206
|
+
* Priority:
|
|
207
|
+
* 1. Git remote (GitHub repo name)
|
|
208
|
+
* 2. Sync configuration (JIRA/ADO project)
|
|
209
|
+
* 3. User prompt (with detected suggestion)
|
|
210
|
+
* 4. "default" (if user accepts default in prompt)
|
|
211
|
+
*
|
|
212
|
+
* @param projectRoot - Project root directory
|
|
213
|
+
* @param options - Detection options
|
|
214
|
+
* @returns Detected or prompted project ID
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* // In git repo: https://github.com/anton-abyzov/specweave.git
|
|
218
|
+
* await autoDetectProjectId('/path/to/project')
|
|
219
|
+
* // Output: "✅ Detected git repository: specweave"
|
|
220
|
+
* // Returns: "specweave"
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* // No git, no sync, user prompted
|
|
224
|
+
* await autoDetectProjectId('/path/to/project')
|
|
225
|
+
* // Output: "📝 No git repository or sync configuration detected."
|
|
226
|
+
* // Prompts user for project ID
|
|
227
|
+
* // Returns: user input (e.g., "my-project")
|
|
228
|
+
*/
|
|
229
|
+
export async function autoDetectProjectId(projectRoot, options = {}) {
|
|
230
|
+
const { silent = false, promptIfNotDetected = true } = options;
|
|
231
|
+
// 1. Try git remote
|
|
232
|
+
const gitProjectId = detectProjectIdFromGit(projectRoot);
|
|
233
|
+
if (gitProjectId) {
|
|
234
|
+
if (!silent) {
|
|
235
|
+
console.log(`✅ Detected git repository: ${gitProjectId}`);
|
|
236
|
+
}
|
|
237
|
+
return gitProjectId;
|
|
238
|
+
}
|
|
239
|
+
// 2. Try sync config
|
|
240
|
+
const syncProjectId = detectProjectIdFromSync(projectRoot);
|
|
241
|
+
if (syncProjectId) {
|
|
242
|
+
if (!silent) {
|
|
243
|
+
console.log(`✅ Detected sync configuration: ${syncProjectId}`);
|
|
244
|
+
}
|
|
245
|
+
return syncProjectId;
|
|
246
|
+
}
|
|
247
|
+
// 3. Prompt user (if enabled)
|
|
248
|
+
if (promptIfNotDetected) {
|
|
249
|
+
if (!silent) {
|
|
250
|
+
console.log('\n📝 No git repository or sync configuration detected.');
|
|
251
|
+
console.log(' Please enter your project identifier.\n');
|
|
252
|
+
console.log(' Examples:');
|
|
253
|
+
console.log(' • GitHub repo "web-app" → enter: web-app');
|
|
254
|
+
console.log(' • JIRA project "WEBAPP" → enter: webapp');
|
|
255
|
+
console.log(' • ADO project "Platform" → enter: platform');
|
|
256
|
+
console.log(' • Generic project → enter: default\n');
|
|
257
|
+
}
|
|
258
|
+
return await promptForProjectId();
|
|
259
|
+
}
|
|
260
|
+
// 4. Fallback to "default" (no prompt)
|
|
261
|
+
return 'default';
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Format project ID to display name
|
|
265
|
+
*
|
|
266
|
+
* Converts kebab-case/snake_case to Title Case
|
|
267
|
+
*
|
|
268
|
+
* @param projectId - Project ID (e.g., "web-app", "mobile_app")
|
|
269
|
+
* @returns Formatted name (e.g., "Web App", "Mobile App")
|
|
270
|
+
*
|
|
271
|
+
* @example
|
|
272
|
+
* formatProjectName('web-app')
|
|
273
|
+
* // Returns: "Web App"
|
|
274
|
+
*
|
|
275
|
+
* @example
|
|
276
|
+
* formatProjectName('mobile_app')
|
|
277
|
+
* // Returns: "Mobile App"
|
|
278
|
+
*
|
|
279
|
+
* @example
|
|
280
|
+
* formatProjectName('specweave')
|
|
281
|
+
* // Returns: "SpecWeave"
|
|
282
|
+
*/
|
|
283
|
+
export function formatProjectName(projectId) {
|
|
284
|
+
// Special cases (known project names)
|
|
285
|
+
const specialCases = {
|
|
286
|
+
'specweave': 'SpecWeave',
|
|
287
|
+
'webapp': 'WebApp',
|
|
288
|
+
'default': 'Default Project'
|
|
289
|
+
};
|
|
290
|
+
if (specialCases[projectId.toLowerCase()]) {
|
|
291
|
+
return specialCases[projectId.toLowerCase()];
|
|
292
|
+
}
|
|
293
|
+
// Convert kebab-case or snake_case to Title Case
|
|
294
|
+
return projectId
|
|
295
|
+
.replace(/[-_]/g, ' ')
|
|
296
|
+
.split(' ')
|
|
297
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
298
|
+
.join(' ');
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Validate project ID format
|
|
302
|
+
*
|
|
303
|
+
* @param projectId - Project ID to validate
|
|
304
|
+
* @returns True if valid, error message if invalid
|
|
305
|
+
*/
|
|
306
|
+
export function validateProjectId(projectId) {
|
|
307
|
+
if (!projectId) {
|
|
308
|
+
return 'Project ID is required';
|
|
309
|
+
}
|
|
310
|
+
if (!/^[a-z0-9_-]+$/.test(projectId)) {
|
|
311
|
+
return 'Project ID must be lowercase, alphanumeric, with hyphens or underscores';
|
|
312
|
+
}
|
|
313
|
+
if (projectId.length < 2) {
|
|
314
|
+
return 'Project ID must be at least 2 characters';
|
|
315
|
+
}
|
|
316
|
+
if (projectId.length > 64) {
|
|
317
|
+
return 'Project ID must be at most 64 characters';
|
|
318
|
+
}
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
//# sourceMappingURL=project-detection.js.map
|