specweave 0.30.16 → 0.30.17
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 +43 -0
- package/dist/src/cli/workers/living-docs-worker.js +80 -44
- package/dist/src/cli/workers/living-docs-worker.js.map +1 -1
- package/dist/src/core/living-docs/living-docs-sync.d.ts +26 -14
- package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
- package/dist/src/core/living-docs/living-docs-sync.js +137 -71
- package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
- package/dist/src/core/llm/index.d.ts +1 -0
- package/dist/src/core/llm/index.d.ts.map +1 -1
- package/dist/src/core/llm/index.js +2 -0
- package/dist/src/core/llm/index.js.map +1 -1
- package/dist/src/core/llm/providers/anthropic-provider.d.ts.map +1 -1
- package/dist/src/core/llm/providers/anthropic-provider.js +15 -26
- package/dist/src/core/llm/providers/anthropic-provider.js.map +1 -1
- package/dist/src/core/llm/providers/azure-openai-provider.d.ts.map +1 -1
- package/dist/src/core/llm/providers/azure-openai-provider.js +13 -5
- package/dist/src/core/llm/providers/azure-openai-provider.js.map +1 -1
- package/dist/src/core/llm/providers/bedrock-provider.d.ts.map +1 -1
- package/dist/src/core/llm/providers/bedrock-provider.js +12 -8
- package/dist/src/core/llm/providers/bedrock-provider.js.map +1 -1
- package/dist/src/core/llm/providers/claude-code-provider.d.ts.map +1 -1
- package/dist/src/core/llm/providers/claude-code-provider.js +15 -25
- package/dist/src/core/llm/providers/claude-code-provider.js.map +1 -1
- package/dist/src/core/llm/providers/ollama-provider.d.ts.map +1 -1
- package/dist/src/core/llm/providers/ollama-provider.js +12 -9
- package/dist/src/core/llm/providers/ollama-provider.js.map +1 -1
- package/dist/src/core/llm/providers/openai-provider.d.ts.map +1 -1
- package/dist/src/core/llm/providers/openai-provider.js +13 -6
- package/dist/src/core/llm/providers/openai-provider.js.map +1 -1
- package/dist/src/core/llm/providers/vertex-ai-provider.d.ts.map +1 -1
- package/dist/src/core/llm/providers/vertex-ai-provider.js +12 -8
- package/dist/src/core/llm/providers/vertex-ai-provider.js.map +1 -1
- package/dist/src/utils/llm-json-extractor.d.ts +105 -0
- package/dist/src/utils/llm-json-extractor.d.ts.map +1 -0
- package/dist/src/utils/llm-json-extractor.js +336 -0
- package/dist/src/utils/llm-json-extractor.js.map +1 -0
- package/dist/src/utils/structure-level-detector.d.ts +105 -0
- package/dist/src/utils/structure-level-detector.d.ts.map +1 -0
- package/dist/src/utils/structure-level-detector.js +388 -0
- package/dist/src/utils/structure-level-detector.js.map +1 -0
- package/package.json +1 -1
- package/plugins/specweave/commands/specweave-increment.md +57 -9
- package/plugins/specweave/commands/specweave-sync-specs.md +37 -6
- package/plugins/specweave/hooks/hooks.json +10 -0
- package/plugins/specweave/hooks/spec-project-validator.sh +111 -0
- package/plugins/specweave/skills/increment-planner/SKILL.md +109 -10
- package/plugins/specweave/skills/increment-planner/templates/spec-multi-project.md +2 -0
- package/plugins/specweave/skills/increment-planner/templates/spec-single-project.md +1 -0
- package/plugins/specweave/skills/multi-project-spec-mapper/SKILL.md +24 -1
- package/plugins/specweave/skills/spec-generator/SKILL.md +18 -0
- package/plugins/specweave/skills/specweave-framework/SKILL.md +25 -0
- package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +14 -0
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +21 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM JSON Extractor - Robust JSON parsing from LLM responses
|
|
3
|
+
*
|
|
4
|
+
* Handles common LLM output patterns:
|
|
5
|
+
* - "Based on the analysis, here is the JSON: {...}"
|
|
6
|
+
* - "```json\n{...}\n```"
|
|
7
|
+
* - "Here's what I found:\n{...}"
|
|
8
|
+
* - Pure JSON responses
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - 4-level extraction strategy (pure -> code blocks -> regex -> array)
|
|
12
|
+
* - Input sanitization (BOM, trailing commas)
|
|
13
|
+
* - Schema-aware correction prompts
|
|
14
|
+
* - Required field validation
|
|
15
|
+
* - Retry-with-correction prompting
|
|
16
|
+
*/
|
|
17
|
+
import { consoleLogger } from './logger.js';
|
|
18
|
+
/**
|
|
19
|
+
* Extract JSON from an LLM response that may contain prose
|
|
20
|
+
*
|
|
21
|
+
* Tries multiple extraction strategies in order:
|
|
22
|
+
* 1. Direct parse (pure JSON)
|
|
23
|
+
* 2. Remove markdown code blocks
|
|
24
|
+
* 3. Find JSON object via regex (handles prose around JSON)
|
|
25
|
+
* 4. Find JSON array via regex
|
|
26
|
+
*/
|
|
27
|
+
export function extractJson(response, options = {}) {
|
|
28
|
+
const logger = options.logger ?? consoleLogger;
|
|
29
|
+
const result = {
|
|
30
|
+
success: false,
|
|
31
|
+
data: null,
|
|
32
|
+
rawResponse: response,
|
|
33
|
+
extractedJson: null,
|
|
34
|
+
error: null,
|
|
35
|
+
extractionMethod: 'failed'
|
|
36
|
+
};
|
|
37
|
+
if (!response || typeof response !== 'string') {
|
|
38
|
+
result.error = 'Empty or invalid response';
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
// Sanitize input: strip BOM and normalize
|
|
42
|
+
const sanitized = sanitizeInput(response);
|
|
43
|
+
const trimmed = sanitized.trim();
|
|
44
|
+
// Strategy 1: Direct parse (pure JSON) - with trailing comma fallback
|
|
45
|
+
const directParsed = tryParseJson(trimmed);
|
|
46
|
+
if (directParsed !== null && validateParsedJson(directParsed, options.requiredFields)) {
|
|
47
|
+
result.success = true;
|
|
48
|
+
result.data = directParsed;
|
|
49
|
+
result.extractedJson = trimmed;
|
|
50
|
+
result.extractionMethod = 'pure';
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
// Strategy 2: Remove markdown code blocks
|
|
54
|
+
const codeBlockPatterns = [
|
|
55
|
+
/^```(?:json)?\s*\n?([\s\S]*?)\n?```$/m, // Full code block
|
|
56
|
+
/```(?:json)?\s*\n?([\s\S]*?)\n?```/, // Code block anywhere
|
|
57
|
+
];
|
|
58
|
+
for (const pattern of codeBlockPatterns) {
|
|
59
|
+
const match = trimmed.match(pattern);
|
|
60
|
+
if (match?.[1]) {
|
|
61
|
+
const codeBlockContent = match[1].trim();
|
|
62
|
+
const parsed = tryParseJson(codeBlockContent);
|
|
63
|
+
if (parsed !== null && validateParsedJson(parsed, options.requiredFields)) {
|
|
64
|
+
result.success = true;
|
|
65
|
+
result.data = parsed;
|
|
66
|
+
result.extractedJson = codeBlockContent;
|
|
67
|
+
result.extractionMethod = 'code-block';
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Strategy 3: Find JSON object via regex (handles "Based on... here is: {...}")
|
|
73
|
+
// Find the outermost balanced braces
|
|
74
|
+
const jsonObject = findBalancedJson(trimmed, '{', '}');
|
|
75
|
+
if (jsonObject) {
|
|
76
|
+
const parsed = tryParseJson(jsonObject);
|
|
77
|
+
if (parsed !== null && validateParsedJson(parsed, options.requiredFields)) {
|
|
78
|
+
result.success = true;
|
|
79
|
+
result.data = parsed;
|
|
80
|
+
result.extractedJson = jsonObject;
|
|
81
|
+
result.extractionMethod = 'regex';
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Strategy 4: Find JSON array
|
|
86
|
+
const jsonArray = findBalancedJson(trimmed, '[', ']');
|
|
87
|
+
if (jsonArray) {
|
|
88
|
+
const parsed = tryParseJson(jsonArray);
|
|
89
|
+
if (parsed !== null && validateParsedJson(parsed, options.requiredFields)) {
|
|
90
|
+
result.success = true;
|
|
91
|
+
result.data = parsed;
|
|
92
|
+
result.extractedJson = jsonArray;
|
|
93
|
+
result.extractionMethod = 'regex';
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// All strategies failed
|
|
98
|
+
result.error = generateErrorMessage(trimmed);
|
|
99
|
+
logger.debug?.(`JSON extraction failed: ${result.error}`);
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Find balanced JSON by matching opening/closing brackets
|
|
104
|
+
* Handles nested structures and strings with escaped characters
|
|
105
|
+
*/
|
|
106
|
+
function findBalancedJson(text, openChar, closeChar) {
|
|
107
|
+
const startIdx = text.indexOf(openChar);
|
|
108
|
+
if (startIdx === -1)
|
|
109
|
+
return null;
|
|
110
|
+
let depth = 0;
|
|
111
|
+
let inString = false;
|
|
112
|
+
let escapeNext = false;
|
|
113
|
+
for (let i = startIdx; i < text.length; i++) {
|
|
114
|
+
const char = text[i];
|
|
115
|
+
if (escapeNext) {
|
|
116
|
+
escapeNext = false;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (char === '\\' && inString) {
|
|
120
|
+
escapeNext = true;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (char === '"') {
|
|
124
|
+
inString = !inString;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (!inString) {
|
|
128
|
+
if (char === openChar) {
|
|
129
|
+
depth++;
|
|
130
|
+
}
|
|
131
|
+
else if (char === closeChar) {
|
|
132
|
+
depth--;
|
|
133
|
+
if (depth === 0) {
|
|
134
|
+
return text.slice(startIdx, i + 1);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Validate parsed JSON has required fields
|
|
143
|
+
*/
|
|
144
|
+
function validateParsedJson(parsed, requiredFields) {
|
|
145
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
if (!requiredFields || requiredFields.length === 0) {
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
const obj = parsed;
|
|
152
|
+
return requiredFields.every(field => field in obj);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Sanitize LLM response input before JSON extraction
|
|
156
|
+
*
|
|
157
|
+
* Handles common issues:
|
|
158
|
+
* - UTF-8 BOM character
|
|
159
|
+
* - Trailing commas in arrays/objects (common LLM mistake)
|
|
160
|
+
*/
|
|
161
|
+
function sanitizeInput(input) {
|
|
162
|
+
// Strip UTF-8 BOM if present
|
|
163
|
+
let sanitized = input.replace(/^\uFEFF/, '');
|
|
164
|
+
return sanitized;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Clean trailing commas from JSON string
|
|
168
|
+
* Called after extraction but before parsing
|
|
169
|
+
*/
|
|
170
|
+
function cleanTrailingCommas(json) {
|
|
171
|
+
// Remove trailing commas before ] or }
|
|
172
|
+
// Be careful not to modify strings - only do this on extracted JSON
|
|
173
|
+
return json.replace(/,\s*([}\]])/g, '$1');
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Try to parse JSON with trailing comma cleanup as fallback
|
|
177
|
+
*/
|
|
178
|
+
function tryParseJson(json) {
|
|
179
|
+
try {
|
|
180
|
+
return JSON.parse(json);
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
// Try with trailing comma cleanup
|
|
184
|
+
try {
|
|
185
|
+
const cleaned = cleanTrailingCommas(json);
|
|
186
|
+
return JSON.parse(cleaned);
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Generate helpful error message for failed extraction
|
|
195
|
+
*/
|
|
196
|
+
function generateErrorMessage(response) {
|
|
197
|
+
const preview = response.slice(0, 100).replace(/\n/g, ' ');
|
|
198
|
+
if (response.toLowerCase().startsWith('based on')) {
|
|
199
|
+
return `Model returned prose instead of JSON. Response starts with: "${preview}..."`;
|
|
200
|
+
}
|
|
201
|
+
if (response.toLowerCase().includes('i apologize') || response.toLowerCase().includes('sorry')) {
|
|
202
|
+
return `Model returned an apology/error message instead of JSON. Response starts with: "${preview}..."`;
|
|
203
|
+
}
|
|
204
|
+
if (response.includes('{')) {
|
|
205
|
+
return `Found JSON-like content but parsing failed. Response starts with: "${preview}..."`;
|
|
206
|
+
}
|
|
207
|
+
return `No valid JSON found in response. Response starts with: "${preview}..."`;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Generate a correction prompt for retry after failed JSON extraction
|
|
211
|
+
*
|
|
212
|
+
* @param originalPrompt - The original prompt that was sent
|
|
213
|
+
* @param failedResponse - The failed response from the LLM
|
|
214
|
+
* @param schema - Optional schema to show expected structure (recommended)
|
|
215
|
+
*/
|
|
216
|
+
export function generateCorrectionPrompt(originalPrompt, failedResponse, schema) {
|
|
217
|
+
// Build schema example - either from provided schema or use default
|
|
218
|
+
let schemaExample;
|
|
219
|
+
if (schema && Object.keys(schema).length > 0) {
|
|
220
|
+
// Use provided schema
|
|
221
|
+
schemaExample = JSON.stringify(schema, null, 2);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
// Fallback to generic example (backward compatible)
|
|
225
|
+
schemaExample = `{
|
|
226
|
+
"purpose": "...",
|
|
227
|
+
"keyConcepts": [...],
|
|
228
|
+
"dependencies": [...],
|
|
229
|
+
"complexity": "low|medium|high",
|
|
230
|
+
"suggestedDocs": [...],
|
|
231
|
+
"architecturalNotes": "..."
|
|
232
|
+
}`;
|
|
233
|
+
}
|
|
234
|
+
return `Your previous response was not valid JSON. I need ONLY a JSON object, with no explanations or prose.
|
|
235
|
+
|
|
236
|
+
INCORRECT (what you sent):
|
|
237
|
+
${failedResponse.slice(0, 200)}${failedResponse.length > 200 ? '...' : ''}
|
|
238
|
+
|
|
239
|
+
CORRECT format - respond with ONLY this JSON structure, nothing else before or after:
|
|
240
|
+
${schemaExample}
|
|
241
|
+
|
|
242
|
+
Now provide ONLY the JSON for: ${originalPrompt.slice(0, 500)}`;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Extract required field names from a JSON schema object
|
|
246
|
+
*
|
|
247
|
+
* Handles common schema patterns:
|
|
248
|
+
* - Simple objects: extracts top-level keys
|
|
249
|
+
* - Objects with "type" descriptors: extracts keys
|
|
250
|
+
*
|
|
251
|
+
* @param schema - The schema object passed to analyzeStructured (any object type)
|
|
252
|
+
* @returns Array of required field names
|
|
253
|
+
*/
|
|
254
|
+
export function extractRequiredFieldsFromSchema(schema) {
|
|
255
|
+
if (!schema || typeof schema !== 'object') {
|
|
256
|
+
return [];
|
|
257
|
+
}
|
|
258
|
+
// For simple schemas like { purpose: "...", complexity: "..." }
|
|
259
|
+
// Return all top-level keys
|
|
260
|
+
return Object.keys(schema);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Build a prompt that strongly encourages JSON-only output
|
|
264
|
+
*
|
|
265
|
+
* Uses multiple techniques:
|
|
266
|
+
* 1. Leading instruction (most important position)
|
|
267
|
+
* 2. Schema definition
|
|
268
|
+
* 3. Trailing reminder
|
|
269
|
+
*/
|
|
270
|
+
export function buildJsonPrompt(context, schema, instruction) {
|
|
271
|
+
const schemaExample = JSON.stringify(Object.fromEntries(Object.entries(schema).map(([k, v]) => [k, v])), null, 2);
|
|
272
|
+
return `IMPORTANT: Respond with ONLY valid JSON. No explanations, no markdown, no text before or after.
|
|
273
|
+
|
|
274
|
+
${instruction}
|
|
275
|
+
|
|
276
|
+
${context}
|
|
277
|
+
|
|
278
|
+
Required JSON schema:
|
|
279
|
+
${schemaExample}
|
|
280
|
+
|
|
281
|
+
Remember: Output ONLY the JSON object. Any text outside the JSON will cause errors.`;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Retry wrapper that attempts extraction with correction prompts
|
|
285
|
+
*
|
|
286
|
+
* Uses schema-aware correction prompts for better retry success.
|
|
287
|
+
*/
|
|
288
|
+
export async function extractJsonWithRetry(sendPrompt, originalPrompt, options = {}) {
|
|
289
|
+
const maxRetries = options.maxRetries ?? 2;
|
|
290
|
+
const logger = options.logger ?? consoleLogger;
|
|
291
|
+
let lastResult;
|
|
292
|
+
let currentPrompt = originalPrompt;
|
|
293
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
294
|
+
if (attempt > 0) {
|
|
295
|
+
logger.debug?.(`JSON extraction retry ${attempt}/${maxRetries}`);
|
|
296
|
+
}
|
|
297
|
+
const response = await sendPrompt(currentPrompt);
|
|
298
|
+
lastResult = extractJson(response, options);
|
|
299
|
+
if (lastResult.success) {
|
|
300
|
+
if (attempt > 0) {
|
|
301
|
+
logger.debug?.(`JSON extraction succeeded on retry ${attempt}`);
|
|
302
|
+
}
|
|
303
|
+
return lastResult;
|
|
304
|
+
}
|
|
305
|
+
// Generate schema-aware correction prompt for next attempt
|
|
306
|
+
if (attempt < maxRetries) {
|
|
307
|
+
currentPrompt = generateCorrectionPrompt(originalPrompt, response, options.schema);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return lastResult;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Create a structured output analyzer with built-in retry
|
|
314
|
+
*
|
|
315
|
+
* This is the recommended way to get structured JSON from LLMs.
|
|
316
|
+
* Combines prompt building, extraction, validation, and retry.
|
|
317
|
+
*
|
|
318
|
+
* @param analyze - Function that sends prompt to LLM and returns response
|
|
319
|
+
* @param schema - Expected schema (used for prompt, validation, and correction)
|
|
320
|
+
* @param options - Configuration options
|
|
321
|
+
*/
|
|
322
|
+
export async function analyzeStructuredWithRetry(analyze, prompt, schema, options = {}) {
|
|
323
|
+
const requiredFields = extractRequiredFieldsFromSchema(schema);
|
|
324
|
+
const result = await extractJsonWithRetry(analyze, prompt, {
|
|
325
|
+
requiredFields,
|
|
326
|
+
schema,
|
|
327
|
+
maxRetries: options.maxRetries ?? 2,
|
|
328
|
+
logger: options.logger
|
|
329
|
+
});
|
|
330
|
+
return {
|
|
331
|
+
success: result.success,
|
|
332
|
+
data: result.data,
|
|
333
|
+
error: result.error
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
//# sourceMappingURL=llm-json-extractor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm-json-extractor.js","sourceRoot":"","sources":["../../../src/utils/llm-json-extractor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structure Level Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects whether the project uses 1-level or 2-level folder structure
|
|
5
|
+
* for living docs specs:
|
|
6
|
+
*
|
|
7
|
+
* - 1-Level: internal/specs/{project}/FS-XXX/
|
|
8
|
+
* - 2-Level: internal/specs/{project}/{board}/FS-XXX/
|
|
9
|
+
*
|
|
10
|
+
* 2-level is used when:
|
|
11
|
+
* - ADO area path mapping is configured (project → area paths)
|
|
12
|
+
* - JIRA board mapping is configured with multiple boards per project
|
|
13
|
+
* - Umbrella with team-based organization
|
|
14
|
+
*
|
|
15
|
+
* @module utils/structure-level-detector
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Structure level configuration
|
|
19
|
+
*/
|
|
20
|
+
export interface StructureLevelConfig {
|
|
21
|
+
/** Whether this is 1-level or 2-level structure */
|
|
22
|
+
level: 1 | 2;
|
|
23
|
+
/** Available projects (for 1-level) or top-level containers (for 2-level) */
|
|
24
|
+
projects: ProjectInfo[];
|
|
25
|
+
/** Available boards (only for 2-level) - organized by project */
|
|
26
|
+
boardsByProject?: Record<string, BoardInfo[]>;
|
|
27
|
+
/** Detection reason for debugging */
|
|
28
|
+
detectionReason: string;
|
|
29
|
+
/** Source of configuration */
|
|
30
|
+
source: 'ado-area-path' | 'jira-board' | 'umbrella-teams' | 'multi-project' | 'single-project' | 'existing-folders';
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Project information
|
|
34
|
+
*/
|
|
35
|
+
export interface ProjectInfo {
|
|
36
|
+
id: string;
|
|
37
|
+
name: string;
|
|
38
|
+
/** For 2-level: this is the ADO project or JIRA project key */
|
|
39
|
+
externalId?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Board information (for 2-level structures)
|
|
43
|
+
*/
|
|
44
|
+
export interface BoardInfo {
|
|
45
|
+
id: string;
|
|
46
|
+
name: string;
|
|
47
|
+
/** Parent project ID */
|
|
48
|
+
projectId: string;
|
|
49
|
+
/** Keywords for matching */
|
|
50
|
+
keywords?: string[];
|
|
51
|
+
/** ADO area path */
|
|
52
|
+
areaPath?: string;
|
|
53
|
+
/** JIRA board ID */
|
|
54
|
+
jiraBoardId?: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Validation result for spec.md project/board context
|
|
58
|
+
*/
|
|
59
|
+
export interface ProjectContextValidation {
|
|
60
|
+
isValid: boolean;
|
|
61
|
+
errors: string[];
|
|
62
|
+
warnings: string[];
|
|
63
|
+
/** Detected structure level */
|
|
64
|
+
structureLevel: 1 | 2;
|
|
65
|
+
/** Parsed project from spec */
|
|
66
|
+
project?: string;
|
|
67
|
+
/** Parsed board from spec (for 2-level) */
|
|
68
|
+
board?: string;
|
|
69
|
+
/** Expected path where this spec will sync to */
|
|
70
|
+
expectedPath?: string;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Detect structure level from configuration
|
|
74
|
+
*
|
|
75
|
+
* @param projectRoot - Path to project root
|
|
76
|
+
* @returns Structure level configuration
|
|
77
|
+
*/
|
|
78
|
+
export declare function detectStructureLevel(projectRoot?: string): StructureLevelConfig;
|
|
79
|
+
/**
|
|
80
|
+
* Validate spec.md has required project/board context
|
|
81
|
+
*
|
|
82
|
+
* @param specContent - Content of spec.md file
|
|
83
|
+
* @param projectRoot - Path to project root
|
|
84
|
+
* @returns Validation result
|
|
85
|
+
*/
|
|
86
|
+
export declare function validateProjectContext(specContent: string, projectRoot?: string): ProjectContextValidation;
|
|
87
|
+
/**
|
|
88
|
+
* Get required fields for spec.md based on structure level
|
|
89
|
+
*
|
|
90
|
+
* @param projectRoot - Path to project root
|
|
91
|
+
* @returns Object describing required fields
|
|
92
|
+
*/
|
|
93
|
+
export declare function getRequiredSpecFields(projectRoot?: string): {
|
|
94
|
+
level: 1 | 2;
|
|
95
|
+
requiredFields: string[];
|
|
96
|
+
optionalFields: string[];
|
|
97
|
+
projects: ProjectInfo[];
|
|
98
|
+
boardsByProject?: Record<string, BoardInfo[]>;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Normalize ID to kebab-case
|
|
102
|
+
*/
|
|
103
|
+
declare function normalizeId(name: string): string;
|
|
104
|
+
export { normalizeId };
|
|
105
|
+
//# sourceMappingURL=structure-level-detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"structure-level-detector.d.ts","sourceRoot":"","sources":["../../../src/utils/structure-level-detector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,mDAAmD;IACnD,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;IAEb,6EAA6E;IAC7E,QAAQ,EAAE,WAAW,EAAE,CAAC;IAExB,iEAAiE;IACjE,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IAE9C,qCAAqC;IACrC,eAAe,EAAE,MAAM,CAAC;IAExB,8BAA8B;IAC9B,MAAM,EAAE,eAAe,GAAG,YAAY,GAAG,gBAAgB,GAAG,eAAe,GAAG,gBAAgB,GAAG,kBAAkB,CAAC;CACrH;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,+DAA+D;IAC/D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,wBAAwB;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,4BAA4B;IAC5B,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oBAAoB;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,+BAA+B;IAC/B,cAAc,EAAE,CAAC,GAAG,CAAC,CAAC;IACtB,+BAA+B;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA+CD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,GAAE,MAAsB,GAAG,oBAAoB,CAoQ9F;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,EACnB,WAAW,GAAE,MAAsB,GAClC,wBAAwB,CAgH1B;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,GAAE,MAAsB,GAAG;IAC1E,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;IACb,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;CAC/C,CAmBA;AAED;;GAEG;AACH,iBAAS,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOzC;AAED,OAAO,EAAE,WAAW,EAAE,CAAC"}
|