specweave 1.0.461 → 1.0.462
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/plugins/specweave-ado/lib/ado-ac-checkbox-sync.d.ts +0 -1
- package/dist/plugins/specweave-ado/lib/ado-ac-checkbox-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-ac-checkbox-sync.js +1 -7
- package/dist/plugins/specweave-ado/lib/ado-ac-checkbox-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-ac-checkbox-sync.js +1 -1
- package/dist/plugins/specweave-github/lib/github-ac-checkbox-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-client-v2.js +1 -1
- package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-ac-checkbox-sync.d.ts +0 -1
- package/dist/plugins/specweave-jira/lib/jira-ac-checkbox-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-ac-checkbox-sync.js +2 -9
- package/dist/plugins/specweave-jira/lib/jira-ac-checkbox-sync.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/lib/vendor/utils/clean-env.d.ts +47 -0
- package/plugins/specweave/lib/vendor/utils/clean-env.js +63 -0
- package/plugins/specweave/lib/vendor/utils/clean-env.js.map +1 -0
- package/plugins/specweave/lib/vendor/utils/execFileNoThrow.d.ts +99 -0
- package/plugins/specweave/lib/vendor/utils/execFileNoThrow.js +149 -0
- package/plugins/specweave/lib/vendor/utils/execFileNoThrow.js.map +1 -0
- package/plugins/specweave-ado/lib/ado-ac-checkbox-sync.js +0 -5
- package/plugins/specweave-ado/lib/ado-ac-checkbox-sync.ts +1 -7
- package/plugins/specweave-github/lib/github-ac-checkbox-sync.js +1 -1
- package/plugins/specweave-github/lib/github-ac-checkbox-sync.ts +1 -1
- package/plugins/specweave-github/lib/github-client-v2.js +1 -1
- package/plugins/specweave-github/lib/github-client-v2.ts +1 -1
- package/plugins/specweave-jira/lib/jira-ac-checkbox-sync.js +1 -7
- package/plugins/specweave-jira/lib/jira-ac-checkbox-sync.ts +2 -9
- package/plugins/specweave/lib/vendor/utils/project-detection.d.ts +0 -250
- package/plugins/specweave/lib/vendor/utils/project-detection.js +0 -560
- package/plugins/specweave/lib/vendor/utils/project-detection.js.map +0 -1
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { execFile, execFileSync } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
import { getCleanEnv } from './clean-env.js';
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
/**
|
|
6
|
+
* Safely execute a command without throwing errors
|
|
7
|
+
*
|
|
8
|
+
* This utility uses child_process.execFile instead of exec/execSync:
|
|
9
|
+
* - ✅ Prevents command injection vulnerabilities (no shell interpolation)
|
|
10
|
+
* - ✅ Cross-platform compatible (Windows, Mac, Linux)
|
|
11
|
+
* - ✅ Proper error handling (returns result instead of throwing)
|
|
12
|
+
* - ✅ Structured output (stdout, stderr, exitCode)
|
|
13
|
+
*
|
|
14
|
+
* @param command - Command to execute (must be in PATH or absolute path)
|
|
15
|
+
* @param args - Array of arguments (safely escaped automatically)
|
|
16
|
+
* @param options - Additional execution options
|
|
17
|
+
* @returns Promise resolving to execution result
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* // ✅ Safe: Arguments automatically escaped
|
|
22
|
+
* const result = await execFileNoThrow('git', ['add', userProvidedFilename]);
|
|
23
|
+
* if (result.success) {
|
|
24
|
+
* console.log('Git add succeeded:', result.stdout);
|
|
25
|
+
* } else {
|
|
26
|
+
* console.error('Git add failed:', result.stderr);
|
|
27
|
+
* }
|
|
28
|
+
*
|
|
29
|
+
* // ✅ Check if command exists
|
|
30
|
+
* const which = process.platform === 'win32' ? 'where' : 'which';
|
|
31
|
+
* const checkResult = await execFileNoThrow(which, ['claude']);
|
|
32
|
+
* if (checkResult.success) {
|
|
33
|
+
* console.log('Claude CLI found at:', checkResult.stdout.trim());
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export async function execFileNoThrow(command, args = [], options = {}) {
|
|
38
|
+
try {
|
|
39
|
+
// CRITICAL: On Windows, shell is needed for .cmd/.bat files
|
|
40
|
+
// Without shell, execFileAsync can't find 'claude.cmd' even if it's in PATH
|
|
41
|
+
const needsShell = process.platform === 'win32' && options.shell !== false;
|
|
42
|
+
// CRITICAL: Use clean environment to prevent debugger/instrumentation from breaking child processes
|
|
43
|
+
// If caller provides custom env, merge with clean env (caller's values take precedence)
|
|
44
|
+
const cleanEnv = getCleanEnv();
|
|
45
|
+
const mergedEnv = options.env ? { ...cleanEnv, ...options.env } : cleanEnv;
|
|
46
|
+
const { stdout, stderr } = await execFileAsync(command, args, {
|
|
47
|
+
...options,
|
|
48
|
+
env: mergedEnv,
|
|
49
|
+
encoding: 'utf-8',
|
|
50
|
+
windowsHide: true, // Don't show console window on Windows
|
|
51
|
+
shell: needsShell,
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
stdout: stdout || '',
|
|
55
|
+
stderr: stderr || '',
|
|
56
|
+
exitCode: 0,
|
|
57
|
+
success: true,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
const execError = error;
|
|
62
|
+
return {
|
|
63
|
+
stdout: execError.stdout || '',
|
|
64
|
+
stderr: execError.stderr || '',
|
|
65
|
+
exitCode: typeof execError.code === 'number' ? execError.code : 1,
|
|
66
|
+
success: false,
|
|
67
|
+
error: execError,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Synchronous version of execFileNoThrow
|
|
73
|
+
*
|
|
74
|
+
* Use sparingly - prefer async version when possible.
|
|
75
|
+
* Useful for initialization code that needs to be synchronous.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* const result = execFileNoThrowSync('claude', ['--version']);
|
|
80
|
+
* if (result.success) {
|
|
81
|
+
* console.log('Claude version:', result.stdout.trim());
|
|
82
|
+
* }
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export function execFileNoThrowSync(command, args = [], options = {}) {
|
|
86
|
+
try {
|
|
87
|
+
// CRITICAL: On Windows, shell is needed for .cmd/.bat files
|
|
88
|
+
// Without shell, execFileSync can't find 'claude.cmd' even if it's in PATH
|
|
89
|
+
const needsShell = process.platform === 'win32' && options.shell !== false;
|
|
90
|
+
// CRITICAL: Use clean environment to prevent debugger/instrumentation from breaking child processes
|
|
91
|
+
// If caller provides custom env, merge with clean env (caller's values take precedence)
|
|
92
|
+
const cleanEnv = getCleanEnv();
|
|
93
|
+
const mergedEnv = options.env ? { ...cleanEnv, ...options.env } : cleanEnv;
|
|
94
|
+
const stdout = execFileSync(command, args, {
|
|
95
|
+
...options,
|
|
96
|
+
env: mergedEnv,
|
|
97
|
+
encoding: 'utf-8',
|
|
98
|
+
windowsHide: true,
|
|
99
|
+
shell: needsShell,
|
|
100
|
+
stdio: ['pipe', 'pipe', 'pipe'], // Capture all streams, don't leak to terminal
|
|
101
|
+
});
|
|
102
|
+
return {
|
|
103
|
+
stdout: stdout || '',
|
|
104
|
+
stderr: '',
|
|
105
|
+
exitCode: 0,
|
|
106
|
+
success: true,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
return {
|
|
111
|
+
stdout: error.stdout || '',
|
|
112
|
+
stderr: error.stderr || '',
|
|
113
|
+
exitCode: typeof error.status === 'number' ? error.status : 1,
|
|
114
|
+
success: false,
|
|
115
|
+
error: error,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Check if a command is available in PATH
|
|
121
|
+
*
|
|
122
|
+
* Cross-platform helper that uses 'which' (Unix) or 'where' (Windows)
|
|
123
|
+
*
|
|
124
|
+
* @param command - Command name to check (e.g., 'claude', 'git', 'node')
|
|
125
|
+
* @returns Promise resolving to true if command exists, false otherwise
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```typescript
|
|
129
|
+
* if (await isCommandAvailable('claude')) {
|
|
130
|
+
* console.log('Claude CLI is installed');
|
|
131
|
+
* } else {
|
|
132
|
+
* console.log('Claude CLI not found - install from https://claude.com/code');
|
|
133
|
+
* }
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
export async function isCommandAvailable(command) {
|
|
137
|
+
const whichCommand = process.platform === 'win32' ? 'where' : 'which';
|
|
138
|
+
const result = await execFileNoThrow(whichCommand, [command]);
|
|
139
|
+
return result.success;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Synchronous version of isCommandAvailable
|
|
143
|
+
*/
|
|
144
|
+
export function isCommandAvailableSync(command) {
|
|
145
|
+
const whichCommand = process.platform === 'win32' ? 'where' : 'which';
|
|
146
|
+
const result = execFileNoThrowSync(whichCommand, [command]);
|
|
147
|
+
return result.success;
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=execFileNoThrow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execFileNoThrow.js","sourceRoot":"","sources":["../../../src/utils/execFileNoThrow.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,7 +3,6 @@ import path from "path";
|
|
|
3
3
|
import yaml from "yaml";
|
|
4
4
|
import axios from "axios";
|
|
5
5
|
import { consoleLogger } from "../../specweave/lib/vendor/utils/logger.js";
|
|
6
|
-
import { autoDetectProjectIdSync } from "../../specweave/lib/vendor/utils/project-detection.js";
|
|
7
6
|
import { deriveFeatureId } from "../../specweave/lib/vendor/utils/feature-id-derivation.js";
|
|
8
7
|
import { GitHubACCheckboxSync } from "../../specweave-github/lib/github-ac-checkbox-sync.js";
|
|
9
8
|
class AdoACCheckboxSync {
|
|
@@ -11,7 +10,6 @@ class AdoACCheckboxSync {
|
|
|
11
10
|
this.projectRoot = options.projectRoot;
|
|
12
11
|
this.incrementId = options.incrementId;
|
|
13
12
|
this.logger = options.logger ?? consoleLogger;
|
|
14
|
-
this.projectId = autoDetectProjectIdSync(this.projectRoot) || "default";
|
|
15
13
|
}
|
|
16
14
|
async syncACCheckboxesToAdo(config) {
|
|
17
15
|
const result = { success: true, updated: 0, issues: [] };
|
|
@@ -202,12 +200,9 @@ ${acLines}
|
|
|
202
200
|
const specsRoot = path.join(this.projectRoot, ".specweave/docs/internal/specs");
|
|
203
201
|
const usFiles = [];
|
|
204
202
|
const projectDirs = [];
|
|
205
|
-
const primary = path.join(specsRoot, this.projectId, featureId);
|
|
206
|
-
if (existsSync(primary)) projectDirs.push(primary);
|
|
207
203
|
if (existsSync(specsRoot)) {
|
|
208
204
|
try {
|
|
209
205
|
for (const proj of await fs.readdir(specsRoot)) {
|
|
210
|
-
if (proj === this.projectId) continue;
|
|
211
206
|
const p = path.join(specsRoot, proj, featureId);
|
|
212
207
|
if (existsSync(p)) projectDirs.push(p);
|
|
213
208
|
}
|
|
@@ -13,7 +13,6 @@ import path from 'path';
|
|
|
13
13
|
import yaml from 'yaml';
|
|
14
14
|
import axios, { AxiosInstance } from 'axios';
|
|
15
15
|
import { Logger, consoleLogger } from '../../specweave/lib/vendor/utils/logger.js';
|
|
16
|
-
import { autoDetectProjectIdSync } from '../../specweave/lib/vendor/utils/project-detection.js';
|
|
17
16
|
import { deriveFeatureId } from '../../specweave/lib/vendor/utils/feature-id-derivation.js';
|
|
18
17
|
import { GitHubACCheckboxSync } from '../../specweave-github/lib/github-ac-checkbox-sync.js';
|
|
19
18
|
import type { SpecWeaveConfig } from '../../../src/core/config/types.js';
|
|
@@ -28,7 +27,6 @@ export interface AdoACCheckboxSyncResult {
|
|
|
28
27
|
export class AdoACCheckboxSync {
|
|
29
28
|
private projectRoot: string;
|
|
30
29
|
private incrementId: string;
|
|
31
|
-
private projectId: string;
|
|
32
30
|
private logger: Logger;
|
|
33
31
|
|
|
34
32
|
constructor(options: {
|
|
@@ -39,7 +37,6 @@ export class AdoACCheckboxSync {
|
|
|
39
37
|
this.projectRoot = options.projectRoot;
|
|
40
38
|
this.incrementId = options.incrementId;
|
|
41
39
|
this.logger = options.logger ?? consoleLogger;
|
|
42
|
-
this.projectId = autoDetectProjectIdSync(this.projectRoot) || 'default';
|
|
43
40
|
}
|
|
44
41
|
|
|
45
42
|
async syncACCheckboxesToAdo(config: SpecWeaveConfig): Promise<AdoACCheckboxSyncResult> {
|
|
@@ -271,13 +268,10 @@ ${acLines}
|
|
|
271
268
|
const usFiles: LivingDocsUSFile[] = [];
|
|
272
269
|
const projectDirs: string[] = [];
|
|
273
270
|
|
|
274
|
-
|
|
275
|
-
if (existsSync(primary)) projectDirs.push(primary);
|
|
276
|
-
|
|
271
|
+
// Search all project directories for the feature
|
|
277
272
|
if (existsSync(specsRoot)) {
|
|
278
273
|
try {
|
|
279
274
|
for (const proj of await fs.readdir(specsRoot)) {
|
|
280
|
-
if (proj === this.projectId) continue;
|
|
281
275
|
const p = path.join(specsRoot, proj, featureId);
|
|
282
276
|
if (existsSync(p)) projectDirs.push(p);
|
|
283
277
|
}
|
|
@@ -3,7 +3,7 @@ import path from "path";
|
|
|
3
3
|
import yaml from "yaml";
|
|
4
4
|
import { GitHubClientV2 } from "./github-client-v2.js";
|
|
5
5
|
import { consoleLogger } from "../../specweave/lib/vendor/utils/logger.js";
|
|
6
|
-
import { autoDetectProjectIdSync } from "
|
|
6
|
+
import { autoDetectProjectIdSync } from "../../../src/utils/project-detection.js";
|
|
7
7
|
import { deriveFeatureId } from "../../specweave/lib/vendor/utils/feature-id-derivation.js";
|
|
8
8
|
import {
|
|
9
9
|
ProviderRouter
|
|
@@ -13,7 +13,7 @@ import path from 'path';
|
|
|
13
13
|
import yaml from 'yaml';
|
|
14
14
|
import { GitHubClientV2 } from './github-client-v2.js';
|
|
15
15
|
import { Logger, consoleLogger } from '../../specweave/lib/vendor/utils/logger.js';
|
|
16
|
-
import { autoDetectProjectIdSync } from '
|
|
16
|
+
import { autoDetectProjectIdSync } from '../../../src/utils/project-detection.js';
|
|
17
17
|
import { deriveFeatureId } from '../../specweave/lib/vendor/utils/feature-id-derivation.js';
|
|
18
18
|
import {
|
|
19
19
|
ProviderRouter,
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - Secure command execution (no shell injection)
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { execFileNoThrow } from '
|
|
11
|
+
import { execFileNoThrow } from '../../specweave/lib/vendor/utils/execFileNoThrow.js';
|
|
12
12
|
import { GitHubIssue, GitHubMilestone, GitHubExternalChange } from './types';
|
|
13
13
|
import { SyncProfile, GitHubConfig, TimeRangePreset } from '../../../src/core/types/sync-profile';
|
|
14
14
|
|
|
@@ -3,7 +3,6 @@ import path from "path";
|
|
|
3
3
|
import yaml from "yaml";
|
|
4
4
|
import axios from "axios";
|
|
5
5
|
import { consoleLogger } from "../../specweave/lib/vendor/utils/logger.js";
|
|
6
|
-
import { autoDetectProjectIdSync } from "../../specweave/lib/vendor/utils/project-detection.js";
|
|
7
6
|
import { deriveFeatureId } from "../../specweave/lib/vendor/utils/feature-id-derivation.js";
|
|
8
7
|
import { getApiBaseUrl } from "./jira-deployment-detector.js";
|
|
9
8
|
import { GitHubACCheckboxSync } from "../../specweave-github/lib/github-ac-checkbox-sync.js";
|
|
@@ -12,7 +11,6 @@ class JiraACCheckboxSync {
|
|
|
12
11
|
this.projectRoot = options.projectRoot;
|
|
13
12
|
this.incrementId = options.incrementId;
|
|
14
13
|
this.logger = options.logger ?? consoleLogger;
|
|
15
|
-
this.projectId = autoDetectProjectIdSync(this.projectRoot) || "default";
|
|
16
14
|
}
|
|
17
15
|
async syncACCheckboxesToJira(config) {
|
|
18
16
|
const result = { success: true, updated: 0, issues: [] };
|
|
@@ -224,13 +222,9 @@ class JiraACCheckboxSync {
|
|
|
224
222
|
const specsRoot = path.join(this.projectRoot, ".specweave/docs/internal/specs");
|
|
225
223
|
const usFiles = [];
|
|
226
224
|
const projectDirs = [];
|
|
227
|
-
const primaryPath = path.join(specsRoot, this.projectId, featureId);
|
|
228
|
-
if (existsSync(primaryPath)) projectDirs.push(primaryPath);
|
|
229
225
|
if (existsSync(specsRoot)) {
|
|
230
226
|
try {
|
|
231
|
-
const
|
|
232
|
-
for (const proj of allProjects) {
|
|
233
|
-
if (proj === this.projectId) continue;
|
|
227
|
+
for (const proj of await fs.readdir(specsRoot)) {
|
|
234
228
|
const p = path.join(specsRoot, proj, featureId);
|
|
235
229
|
if (existsSync(p)) projectDirs.push(p);
|
|
236
230
|
}
|
|
@@ -16,7 +16,6 @@ import path from 'path';
|
|
|
16
16
|
import yaml from 'yaml';
|
|
17
17
|
import axios, { AxiosInstance } from 'axios';
|
|
18
18
|
import { Logger, consoleLogger } from '../../specweave/lib/vendor/utils/logger.js';
|
|
19
|
-
import { autoDetectProjectIdSync } from '../../specweave/lib/vendor/utils/project-detection.js';
|
|
20
19
|
import { deriveFeatureId } from '../../specweave/lib/vendor/utils/feature-id-derivation.js';
|
|
21
20
|
import { detectDeploymentType, getApiBaseUrl } from './jira-deployment-detector.js';
|
|
22
21
|
import type { SpecWeaveConfig } from '../../../src/core/config/types.js';
|
|
@@ -32,7 +31,6 @@ export interface JiraACCheckboxSyncResult {
|
|
|
32
31
|
export class JiraACCheckboxSync {
|
|
33
32
|
private projectRoot: string;
|
|
34
33
|
private incrementId: string;
|
|
35
|
-
private projectId: string;
|
|
36
34
|
private logger: Logger;
|
|
37
35
|
|
|
38
36
|
constructor(options: {
|
|
@@ -43,7 +41,6 @@ export class JiraACCheckboxSync {
|
|
|
43
41
|
this.projectRoot = options.projectRoot;
|
|
44
42
|
this.incrementId = options.incrementId;
|
|
45
43
|
this.logger = options.logger ?? consoleLogger;
|
|
46
|
-
this.projectId = autoDetectProjectIdSync(this.projectRoot) || 'default';
|
|
47
44
|
}
|
|
48
45
|
|
|
49
46
|
async syncACCheckboxesToJira(config: SpecWeaveConfig): Promise<JiraACCheckboxSyncResult> {
|
|
@@ -299,15 +296,11 @@ export class JiraACCheckboxSync {
|
|
|
299
296
|
const specsRoot = path.join(this.projectRoot, '.specweave/docs/internal/specs');
|
|
300
297
|
const usFiles: LivingDocsUSFile[] = [];
|
|
301
298
|
|
|
299
|
+
// Search all project directories for the feature
|
|
302
300
|
const projectDirs: string[] = [];
|
|
303
|
-
const primaryPath = path.join(specsRoot, this.projectId, featureId);
|
|
304
|
-
if (existsSync(primaryPath)) projectDirs.push(primaryPath);
|
|
305
|
-
|
|
306
301
|
if (existsSync(specsRoot)) {
|
|
307
302
|
try {
|
|
308
|
-
const
|
|
309
|
-
for (const proj of allProjects) {
|
|
310
|
-
if (proj === this.projectId) continue;
|
|
303
|
+
for (const proj of await fs.readdir(specsRoot)) {
|
|
311
304
|
const p = path.join(specsRoot, proj, featureId);
|
|
312
305
|
if (existsSync(p)) projectDirs.push(p);
|
|
313
306
|
}
|
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Project ID Auto-Detection Utilities
|
|
3
|
-
*
|
|
4
|
-
* Detects project ID from (priority order):
|
|
5
|
-
* 1. sync.defaultProfile (for multi-profile monorepos)
|
|
6
|
-
* 2. Git remote (GitHub repo name)
|
|
7
|
-
* 3. Sync configuration (JIRA project key, ADO project name)
|
|
8
|
-
* 4. User prompt (fallback)
|
|
9
|
-
*
|
|
10
|
-
* NOTE: multiProject.activeProject has been REMOVED!
|
|
11
|
-
* Per-US project targeting replaces global activeProject.
|
|
12
|
-
* See: 0125-cross-project-user-story-targeting
|
|
13
|
-
*
|
|
14
|
-
* Also provides repo name parsing for domain context understanding.
|
|
15
|
-
*/
|
|
16
|
-
/**
|
|
17
|
-
* Parsed repo name structure for domain understanding
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* parseRepoName('sw-qr-menu-be')
|
|
21
|
-
* // Returns: { prefix: 'sw', product: 'qr-menu', component: 'be', full: 'sw-qr-menu-be' }
|
|
22
|
-
*/
|
|
23
|
-
export interface ParsedRepoName {
|
|
24
|
-
/** Short prefix (e.g., 'sw' for company/project abbreviation) */
|
|
25
|
-
prefix: string | null;
|
|
26
|
-
/** Product/domain name (e.g., 'qr-menu') */
|
|
27
|
-
product: string;
|
|
28
|
-
/** Component suffix (e.g., 'be', 'fe', 'shared', 'api', 'web', 'mobile') */
|
|
29
|
-
component: string | null;
|
|
30
|
-
/** Full original repo name */
|
|
31
|
-
full: string;
|
|
32
|
-
/** Detected domain context based on product name */
|
|
33
|
-
domain: string | null;
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Parse repo name to extract product, component, and domain context
|
|
37
|
-
*
|
|
38
|
-
* Understands naming conventions like:
|
|
39
|
-
* - `sw-qr-menu-be` → prefix: sw, product: qr-menu, component: be
|
|
40
|
-
* - `my-app-frontend` → prefix: null, product: my-app, component: frontend
|
|
41
|
-
* - `ecommerce-api` → prefix: null, product: ecommerce, component: api
|
|
42
|
-
*
|
|
43
|
-
* @param repoName - Repository name (e.g., "sw-qr-menu-be")
|
|
44
|
-
* @returns Parsed repo name structure
|
|
45
|
-
*/
|
|
46
|
-
export declare function parseRepoName(repoName: string): ParsedRepoName;
|
|
47
|
-
/**
|
|
48
|
-
* Detect default project ID from configuration
|
|
49
|
-
*
|
|
50
|
-
* NOTE: multiProject.activeProject has been REMOVED!
|
|
51
|
-
* This function now only uses sync.defaultProfile for legacy umbrella repos.
|
|
52
|
-
*
|
|
53
|
-
* Priority:
|
|
54
|
-
* 1. sync.defaultProfile - for umbrella repos where profile maps to folder
|
|
55
|
-
*
|
|
56
|
-
* @param projectRoot - Project root directory
|
|
57
|
-
* @returns Default project ID or null if not configured
|
|
58
|
-
*/
|
|
59
|
-
export declare function detectDefaultProfileId(projectRoot: string): string | null;
|
|
60
|
-
/**
|
|
61
|
-
* Auto-detect project ID from git remote URL
|
|
62
|
-
*
|
|
63
|
-
* Extracts repository name from git remote URL
|
|
64
|
-
*
|
|
65
|
-
* @param projectRoot - Project root directory
|
|
66
|
-
* @returns Project ID (repo name) or null if not detected
|
|
67
|
-
*
|
|
68
|
-
* @example
|
|
69
|
-
* // Git remote: https://github.com/anton-abyzov/specweave.git
|
|
70
|
-
* detectProjectIdFromGit('/path/to/project')
|
|
71
|
-
* // Returns: "specweave"
|
|
72
|
-
*
|
|
73
|
-
* @example
|
|
74
|
-
* // Git remote: git@github.com:my-org/web-app.git
|
|
75
|
-
* detectProjectIdFromGit('/path/to/project')
|
|
76
|
-
* // Returns: "web-app"
|
|
77
|
-
*/
|
|
78
|
-
export declare function detectProjectIdFromGit(projectRoot: string): string | null;
|
|
79
|
-
/**
|
|
80
|
-
* Auto-detect project ID from sync configuration
|
|
81
|
-
*
|
|
82
|
-
* Checks existing sync profiles for JIRA project key or ADO project name
|
|
83
|
-
*
|
|
84
|
-
* @param projectRoot - Project root directory
|
|
85
|
-
* @returns Project ID from sync config or null if not detected
|
|
86
|
-
*
|
|
87
|
-
* @example
|
|
88
|
-
* // Config has JIRA: { projectKey: "WEBAPP" }
|
|
89
|
-
* detectProjectIdFromSync('/path/to/project')
|
|
90
|
-
* // Returns: "webapp"
|
|
91
|
-
*/
|
|
92
|
-
export declare function detectProjectIdFromSync(projectRoot: string): string | null;
|
|
93
|
-
/**
|
|
94
|
-
* Prompt user for project ID
|
|
95
|
-
*
|
|
96
|
-
* Interactive prompt with validation and helpful examples
|
|
97
|
-
*
|
|
98
|
-
* @param suggestedId - Optional suggested project ID (from detection)
|
|
99
|
-
* @returns Project ID entered by user
|
|
100
|
-
*
|
|
101
|
-
* @example
|
|
102
|
-
* await promptForProjectId()
|
|
103
|
-
* // User sees:
|
|
104
|
-
* // Project ID (matches GitHub repo, JIRA project, or ADO project): █
|
|
105
|
-
* // User enters: "web-app"
|
|
106
|
-
* // Returns: "web-app"
|
|
107
|
-
*/
|
|
108
|
-
export declare function promptForProjectId(suggestedId?: string): Promise<string>;
|
|
109
|
-
/**
|
|
110
|
-
* Auto-detect project ID synchronously (no prompts)
|
|
111
|
-
*
|
|
112
|
-
* Priority:
|
|
113
|
-
* 1. Active sync profile ID (for multi-profile monorepos like sw-qr-menu)
|
|
114
|
-
* 2. Git remote (GitHub repo name) - for single-repo projects
|
|
115
|
-
* 3. Sync configuration (JIRA/ADO project) - legacy fallback
|
|
116
|
-
* 4. "default" (fallback)
|
|
117
|
-
*
|
|
118
|
-
* @param projectRoot - Project root directory
|
|
119
|
-
* @param options - Detection options
|
|
120
|
-
* @returns Detected project ID or "default"
|
|
121
|
-
*
|
|
122
|
-
* @example
|
|
123
|
-
* // Multi-project setup with activeProject: "be"
|
|
124
|
-
* autoDetectProjectIdSync('/path/to/sw-qr-menu')
|
|
125
|
-
* // Returns: "be" (profile ID, NOT "sw-qr-menu-be")
|
|
126
|
-
*
|
|
127
|
-
* @example
|
|
128
|
-
* // Single repo: https://github.com/anton-abyzov/specweave.git
|
|
129
|
-
* autoDetectProjectIdSync('/path/to/project')
|
|
130
|
-
* // Returns: "specweave"
|
|
131
|
-
*
|
|
132
|
-
* @example
|
|
133
|
-
* // No git, no sync
|
|
134
|
-
* autoDetectProjectIdSync('/path/to/project')
|
|
135
|
-
* // Returns: "default"
|
|
136
|
-
*/
|
|
137
|
-
export declare function autoDetectProjectIdSync(projectRoot: string, options?: {
|
|
138
|
-
silent?: boolean;
|
|
139
|
-
}): string;
|
|
140
|
-
/**
|
|
141
|
-
* Auto-detect project ID with fallback chain (async version with prompts)
|
|
142
|
-
*
|
|
143
|
-
* Priority:
|
|
144
|
-
* 1. Default sync profile ID (for multi-profile monorepos)
|
|
145
|
-
* 2. Git remote (GitHub repo name)
|
|
146
|
-
* 3. Sync configuration (JIRA/ADO project)
|
|
147
|
-
* 4. User prompt (with detected suggestion)
|
|
148
|
-
* 5. "default" (if user accepts default in prompt)
|
|
149
|
-
*
|
|
150
|
-
* @param projectRoot - Project root directory
|
|
151
|
-
* @param options - Detection options
|
|
152
|
-
* @returns Detected or prompted project ID
|
|
153
|
-
*
|
|
154
|
-
* @example
|
|
155
|
-
* // Multi-project setup with defaultProfile: "be"
|
|
156
|
-
* await autoDetectProjectId('/path/to/sw-qr-menu')
|
|
157
|
-
* // Output: "✅ Detected default profile: be"
|
|
158
|
-
* // Returns: "be"
|
|
159
|
-
*
|
|
160
|
-
* @example
|
|
161
|
-
* // In git repo: https://github.com/anton-abyzov/specweave.git
|
|
162
|
-
* await autoDetectProjectId('/path/to/project')
|
|
163
|
-
* // Output: "✅ Detected git repository: specweave"
|
|
164
|
-
* // Returns: "specweave"
|
|
165
|
-
*
|
|
166
|
-
* @example
|
|
167
|
-
* // No git, no sync, user prompted
|
|
168
|
-
* await autoDetectProjectId('/path/to/project')
|
|
169
|
-
* // Output: "📝 No git repository or sync configuration detected."
|
|
170
|
-
* // Prompts user for project ID
|
|
171
|
-
* // Returns: user input (e.g., "my-project")
|
|
172
|
-
*/
|
|
173
|
-
export declare function autoDetectProjectId(projectRoot: string, options?: {
|
|
174
|
-
silent?: boolean;
|
|
175
|
-
promptIfNotDetected?: boolean;
|
|
176
|
-
}): Promise<string>;
|
|
177
|
-
/**
|
|
178
|
-
* Format project ID to display name
|
|
179
|
-
*
|
|
180
|
-
* Converts kebab-case/snake_case to Title Case
|
|
181
|
-
*
|
|
182
|
-
* @param projectId - Project ID (e.g., "web-app", "mobile_app")
|
|
183
|
-
* @returns Formatted name (e.g., "Web App", "Mobile App")
|
|
184
|
-
*
|
|
185
|
-
* @example
|
|
186
|
-
* formatProjectName('web-app')
|
|
187
|
-
* // Returns: "Web App"
|
|
188
|
-
*
|
|
189
|
-
* @example
|
|
190
|
-
* formatProjectName('mobile_app')
|
|
191
|
-
* // Returns: "Mobile App"
|
|
192
|
-
*
|
|
193
|
-
* @example
|
|
194
|
-
* formatProjectName('specweave')
|
|
195
|
-
* // Returns: "SpecWeave"
|
|
196
|
-
*/
|
|
197
|
-
export declare function formatProjectName(projectId: string): string;
|
|
198
|
-
/**
|
|
199
|
-
* Validate project ID format
|
|
200
|
-
*
|
|
201
|
-
* @param projectId - Project ID to validate
|
|
202
|
-
* @returns True if valid, error message if invalid
|
|
203
|
-
*/
|
|
204
|
-
export declare function validateProjectId(projectId: string): true | string;
|
|
205
|
-
/**
|
|
206
|
-
* Full project context for intelligent user story routing and domain awareness
|
|
207
|
-
*/
|
|
208
|
-
export interface ProjectContext {
|
|
209
|
-
/** Default project/profile ID (folder name under specs/) */
|
|
210
|
-
projectId: string;
|
|
211
|
-
/** How projectId was detected */
|
|
212
|
-
detectedFrom: 'defaultProfile' | 'gitRemote' | 'syncConfig' | 'fallback';
|
|
213
|
-
/** Parsed repo name with domain context (if git remote detected) */
|
|
214
|
-
repoInfo: ParsedRepoName | null;
|
|
215
|
-
/** All available profiles (for multi-profile monorepos) */
|
|
216
|
-
availableProfiles: string[];
|
|
217
|
-
/** Default profile config (if multi-profile) */
|
|
218
|
-
defaultProfile: {
|
|
219
|
-
id: string;
|
|
220
|
-
displayName: string;
|
|
221
|
-
provider: string;
|
|
222
|
-
repo?: string;
|
|
223
|
-
} | null;
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* Get full project context for intelligent routing and domain awareness
|
|
227
|
-
*
|
|
228
|
-
* Use this to understand:
|
|
229
|
-
* - Which project folder to use for specs (projectId)
|
|
230
|
-
* - What domain/product this project is about (repoInfo.domain)
|
|
231
|
-
* - What component type this is (repoInfo.component: be/fe/shared)
|
|
232
|
-
* - What other profiles are available (for cross-project user stories)
|
|
233
|
-
*
|
|
234
|
-
* @param projectRoot - Project root directory
|
|
235
|
-
* @returns Full project context
|
|
236
|
-
*
|
|
237
|
-
* @example
|
|
238
|
-
* // sw-qr-menu monorepo with defaultProfile: "be"
|
|
239
|
-
* getProjectContext('/path/to/sw-qr-menu')
|
|
240
|
-
* // Returns:
|
|
241
|
-
* // {
|
|
242
|
-
* // projectId: 'be',
|
|
243
|
-
* // detectedFrom: 'defaultProfile',
|
|
244
|
-
* // repoInfo: { prefix: 'sw', product: 'qr-menu', component: 'be', domain: 'hospitality/restaurant' },
|
|
245
|
-
* // availableProfiles: ['be', 'fe', 'shared'],
|
|
246
|
-
* // defaultProfile: { id: 'be', displayName: 'sw-qr-menu-be service', provider: 'github', repo: 'sw-qr-menu-be' }
|
|
247
|
-
* // }
|
|
248
|
-
*/
|
|
249
|
-
export declare function getProjectContext(projectRoot: string): ProjectContext;
|
|
250
|
-
//# sourceMappingURL=project-detection.d.ts.map
|