universal-dev-standards 3.0.0
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/LICENSE +21 -0
- package/README.md +248 -0
- package/bin/uds.js +56 -0
- package/package.json +63 -0
- package/src/commands/check.js +149 -0
- package/src/commands/configure.js +221 -0
- package/src/commands/init.js +665 -0
- package/src/commands/list.js +100 -0
- package/src/commands/update.js +186 -0
- package/src/index.js +7 -0
- package/src/prompts/init.js +702 -0
- package/src/prompts/integrations.js +453 -0
- package/src/utils/copier.js +143 -0
- package/src/utils/detector.js +159 -0
- package/src/utils/github.js +508 -0
- package/src/utils/integration-generator.js +1694 -0
- package/src/utils/registry.js +207 -0
- package/standards-registry.json +658 -0
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* All available rule categories for integration files
|
|
6
|
+
*/
|
|
7
|
+
const RULE_CATEGORIES = {
|
|
8
|
+
'anti-hallucination': {
|
|
9
|
+
name: 'Anti-Hallucination Protocol',
|
|
10
|
+
nameZh: '反幻覺協議',
|
|
11
|
+
description: 'Evidence-based analysis and source attribution',
|
|
12
|
+
default: true
|
|
13
|
+
},
|
|
14
|
+
'commit-standards': {
|
|
15
|
+
name: 'Commit Message Standards',
|
|
16
|
+
nameZh: '提交訊息標準',
|
|
17
|
+
description: 'Conventional commits format and guidelines',
|
|
18
|
+
default: true
|
|
19
|
+
},
|
|
20
|
+
'code-review': {
|
|
21
|
+
name: 'Code Review Checklist',
|
|
22
|
+
nameZh: '程式碼審查清單',
|
|
23
|
+
description: 'Pre-commit quality verification',
|
|
24
|
+
default: true
|
|
25
|
+
},
|
|
26
|
+
'testing': {
|
|
27
|
+
name: 'Testing Standards',
|
|
28
|
+
nameZh: '測試標準',
|
|
29
|
+
description: 'Test pyramid and coverage requirements',
|
|
30
|
+
default: false
|
|
31
|
+
},
|
|
32
|
+
'documentation': {
|
|
33
|
+
name: 'Documentation Standards',
|
|
34
|
+
nameZh: '文件標準',
|
|
35
|
+
description: 'README and API documentation guidelines',
|
|
36
|
+
default: false
|
|
37
|
+
},
|
|
38
|
+
'git-workflow': {
|
|
39
|
+
name: 'Git Workflow',
|
|
40
|
+
nameZh: 'Git 工作流程',
|
|
41
|
+
description: 'Branch naming and merge strategies',
|
|
42
|
+
default: false
|
|
43
|
+
},
|
|
44
|
+
'error-handling': {
|
|
45
|
+
name: 'Error Handling',
|
|
46
|
+
nameZh: '錯誤處理',
|
|
47
|
+
description: 'Error codes and logging standards',
|
|
48
|
+
default: false
|
|
49
|
+
},
|
|
50
|
+
'project-structure': {
|
|
51
|
+
name: 'Project Structure',
|
|
52
|
+
nameZh: '專案結構',
|
|
53
|
+
description: 'Directory conventions and organization',
|
|
54
|
+
default: false
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Language-specific rule options
|
|
60
|
+
*/
|
|
61
|
+
const LANGUAGE_RULES = {
|
|
62
|
+
javascript: {
|
|
63
|
+
name: 'JavaScript/TypeScript',
|
|
64
|
+
rules: ['ES6+ syntax', 'Async/await patterns', 'Type safety (TS)']
|
|
65
|
+
},
|
|
66
|
+
python: {
|
|
67
|
+
name: 'Python',
|
|
68
|
+
rules: ['PEP 8 style', 'Type hints', 'Docstrings']
|
|
69
|
+
},
|
|
70
|
+
csharp: {
|
|
71
|
+
name: 'C#',
|
|
72
|
+
rules: ['.NET naming conventions', 'LINQ usage', 'Async patterns']
|
|
73
|
+
},
|
|
74
|
+
php: {
|
|
75
|
+
name: 'PHP',
|
|
76
|
+
rules: ['PSR-12 style', 'Type declarations', 'Namespace usage']
|
|
77
|
+
},
|
|
78
|
+
go: {
|
|
79
|
+
name: 'Go',
|
|
80
|
+
rules: ['Go idioms', 'Error handling', 'Package structure']
|
|
81
|
+
},
|
|
82
|
+
java: {
|
|
83
|
+
name: 'Java',
|
|
84
|
+
rules: ['Java conventions', 'Stream API', 'Dependency injection']
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Prompt for integration customization mode
|
|
90
|
+
* @returns {Promise<string>} 'default', 'custom', or 'merge'
|
|
91
|
+
*/
|
|
92
|
+
export async function promptIntegrationMode() {
|
|
93
|
+
console.log();
|
|
94
|
+
console.log(chalk.cyan('Integration Configuration:'));
|
|
95
|
+
console.log(chalk.gray(' Configure how AI tool rules are generated'));
|
|
96
|
+
console.log();
|
|
97
|
+
|
|
98
|
+
const { mode } = await inquirer.prompt([
|
|
99
|
+
{
|
|
100
|
+
type: 'list',
|
|
101
|
+
name: 'mode',
|
|
102
|
+
message: 'How would you like to configure integration files?',
|
|
103
|
+
choices: [
|
|
104
|
+
{
|
|
105
|
+
name: `${chalk.green('Default')} ${chalk.gray('(推薦)')} - Use standard rule set`,
|
|
106
|
+
value: 'default'
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: `${chalk.blue('Custom')} - Select specific rules to include`,
|
|
110
|
+
value: 'custom'
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: `${chalk.yellow('Merge')} - Merge with existing rules file`,
|
|
114
|
+
value: 'merge'
|
|
115
|
+
}
|
|
116
|
+
],
|
|
117
|
+
default: 'default'
|
|
118
|
+
}
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
return mode;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Prompt for selecting rule categories
|
|
126
|
+
* @param {Object} _detected - Detected project characteristics (for future use)
|
|
127
|
+
* @returns {Promise<string[]>} Selected rule category IDs
|
|
128
|
+
*/
|
|
129
|
+
// eslint-disable-next-line no-unused-vars
|
|
130
|
+
export async function promptRuleCategories(_detected = {}) {
|
|
131
|
+
console.log();
|
|
132
|
+
console.log(chalk.cyan('Rule Categories:'));
|
|
133
|
+
console.log(chalk.gray(' Select which standards to include in integration files'));
|
|
134
|
+
console.log();
|
|
135
|
+
|
|
136
|
+
const choices = Object.entries(RULE_CATEGORIES).map(([id, cat]) => ({
|
|
137
|
+
name: `${cat.name} ${chalk.gray(`(${cat.nameZh})`)} - ${cat.description}`,
|
|
138
|
+
value: id,
|
|
139
|
+
checked: cat.default
|
|
140
|
+
}));
|
|
141
|
+
|
|
142
|
+
const { categories } = await inquirer.prompt([
|
|
143
|
+
{
|
|
144
|
+
type: 'checkbox',
|
|
145
|
+
name: 'categories',
|
|
146
|
+
message: 'Select rule categories:',
|
|
147
|
+
choices,
|
|
148
|
+
validate: (answer) => {
|
|
149
|
+
if (answer.length === 0) {
|
|
150
|
+
return 'Please select at least one category';
|
|
151
|
+
}
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
]);
|
|
156
|
+
|
|
157
|
+
return categories;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Prompt for language-specific rules
|
|
162
|
+
* @param {Object} detected - Detected languages
|
|
163
|
+
* @returns {Promise<string[]>} Selected language IDs
|
|
164
|
+
*/
|
|
165
|
+
export async function promptLanguageRules(detected = {}) {
|
|
166
|
+
const detectedLanguages = Object.entries(detected)
|
|
167
|
+
.filter(([, v]) => v)
|
|
168
|
+
.map(([k]) => k);
|
|
169
|
+
|
|
170
|
+
if (detectedLanguages.length === 0) {
|
|
171
|
+
return [];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
console.log();
|
|
175
|
+
console.log(chalk.cyan('Language-Specific Rules:'));
|
|
176
|
+
console.log(chalk.gray(' Include language-specific coding standards'));
|
|
177
|
+
console.log();
|
|
178
|
+
|
|
179
|
+
const choices = detectedLanguages
|
|
180
|
+
.filter(lang => LANGUAGE_RULES[lang])
|
|
181
|
+
.map(lang => ({
|
|
182
|
+
name: `${LANGUAGE_RULES[lang].name} - ${LANGUAGE_RULES[lang].rules.join(', ')}`,
|
|
183
|
+
value: lang,
|
|
184
|
+
checked: true
|
|
185
|
+
}));
|
|
186
|
+
|
|
187
|
+
if (choices.length === 0) {
|
|
188
|
+
return [];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const { languages } = await inquirer.prompt([
|
|
192
|
+
{
|
|
193
|
+
type: 'checkbox',
|
|
194
|
+
name: 'languages',
|
|
195
|
+
message: 'Include language-specific rules:',
|
|
196
|
+
choices
|
|
197
|
+
}
|
|
198
|
+
]);
|
|
199
|
+
|
|
200
|
+
return languages;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Prompt for custom exclusion rules
|
|
205
|
+
* @returns {Promise<string[]>} List of patterns/rules to exclude
|
|
206
|
+
*/
|
|
207
|
+
export async function promptExclusions() {
|
|
208
|
+
console.log();
|
|
209
|
+
console.log(chalk.cyan('Custom Exclusions:'));
|
|
210
|
+
console.log(chalk.gray(' Specify patterns or rules to exclude from enforcement'));
|
|
211
|
+
console.log();
|
|
212
|
+
|
|
213
|
+
const { hasExclusions } = await inquirer.prompt([
|
|
214
|
+
{
|
|
215
|
+
type: 'confirm',
|
|
216
|
+
name: 'hasExclusions',
|
|
217
|
+
message: 'Do you want to add custom exclusions?',
|
|
218
|
+
default: false
|
|
219
|
+
}
|
|
220
|
+
]);
|
|
221
|
+
|
|
222
|
+
if (!hasExclusions) {
|
|
223
|
+
return [];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const { exclusions } = await inquirer.prompt([
|
|
227
|
+
{
|
|
228
|
+
type: 'input',
|
|
229
|
+
name: 'exclusions',
|
|
230
|
+
message: 'Enter exclusion patterns (comma-separated):',
|
|
231
|
+
filter: (input) => input.split(',').map(s => s.trim()).filter(s => s.length > 0)
|
|
232
|
+
}
|
|
233
|
+
]);
|
|
234
|
+
|
|
235
|
+
return exclusions;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Prompt for project-specific custom rules
|
|
240
|
+
* @returns {Promise<string[]>} List of custom rules to add
|
|
241
|
+
*/
|
|
242
|
+
export async function promptCustomRules() {
|
|
243
|
+
console.log();
|
|
244
|
+
console.log(chalk.cyan('Project-Specific Rules:'));
|
|
245
|
+
console.log(chalk.gray(' Add custom rules specific to your project'));
|
|
246
|
+
console.log();
|
|
247
|
+
|
|
248
|
+
const { hasCustomRules } = await inquirer.prompt([
|
|
249
|
+
{
|
|
250
|
+
type: 'confirm',
|
|
251
|
+
name: 'hasCustomRules',
|
|
252
|
+
message: 'Do you want to add project-specific custom rules?',
|
|
253
|
+
default: false
|
|
254
|
+
}
|
|
255
|
+
]);
|
|
256
|
+
|
|
257
|
+
if (!hasCustomRules) {
|
|
258
|
+
return [];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const customRules = [];
|
|
262
|
+
let addMore = true;
|
|
263
|
+
|
|
264
|
+
while (addMore) {
|
|
265
|
+
const { rule } = await inquirer.prompt([
|
|
266
|
+
{
|
|
267
|
+
type: 'input',
|
|
268
|
+
name: 'rule',
|
|
269
|
+
message: 'Enter custom rule (or empty to finish):',
|
|
270
|
+
}
|
|
271
|
+
]);
|
|
272
|
+
|
|
273
|
+
if (rule.trim()) {
|
|
274
|
+
customRules.push(rule.trim());
|
|
275
|
+
} else {
|
|
276
|
+
addMore = false;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return customRules;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Prompt for merge conflict resolution strategy
|
|
285
|
+
* @param {string} toolName - Name of the AI tool
|
|
286
|
+
* @returns {Promise<string>} 'keep', 'overwrite', or 'append'
|
|
287
|
+
*/
|
|
288
|
+
export async function promptMergeStrategy(toolName) {
|
|
289
|
+
console.log();
|
|
290
|
+
console.log(chalk.cyan(`Existing ${toolName} Rules Detected:`));
|
|
291
|
+
console.log(chalk.gray(' Choose how to handle existing rules'));
|
|
292
|
+
console.log();
|
|
293
|
+
|
|
294
|
+
const { strategy } = await inquirer.prompt([
|
|
295
|
+
{
|
|
296
|
+
type: 'list',
|
|
297
|
+
name: 'strategy',
|
|
298
|
+
message: `How should we handle the existing ${toolName} rules?`,
|
|
299
|
+
choices: [
|
|
300
|
+
{
|
|
301
|
+
name: `${chalk.green('Append')} ${chalk.gray('(推薦)')} - Add new rules after existing ones`,
|
|
302
|
+
value: 'append'
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
name: `${chalk.blue('Merge')} - Intelligently merge (avoid duplicates)`,
|
|
306
|
+
value: 'merge'
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
name: `${chalk.yellow('Overwrite')} - Replace with new rules`,
|
|
310
|
+
value: 'overwrite'
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
name: `${chalk.gray('Keep')} - Keep existing, skip installation`,
|
|
314
|
+
value: 'keep'
|
|
315
|
+
}
|
|
316
|
+
],
|
|
317
|
+
default: 'append'
|
|
318
|
+
}
|
|
319
|
+
]);
|
|
320
|
+
|
|
321
|
+
return strategy;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Prompt for rule detail level
|
|
326
|
+
* @returns {Promise<string>} 'minimal', 'standard', or 'comprehensive'
|
|
327
|
+
*/
|
|
328
|
+
export async function promptDetailLevel() {
|
|
329
|
+
console.log();
|
|
330
|
+
console.log(chalk.cyan('Rule Detail Level:'));
|
|
331
|
+
console.log(chalk.gray(' Choose how detailed the generated rules should be'));
|
|
332
|
+
console.log();
|
|
333
|
+
|
|
334
|
+
const { level } = await inquirer.prompt([
|
|
335
|
+
{
|
|
336
|
+
type: 'list',
|
|
337
|
+
name: 'level',
|
|
338
|
+
message: 'Select rule detail level:',
|
|
339
|
+
choices: [
|
|
340
|
+
{
|
|
341
|
+
name: `${chalk.green('Minimal')} - Essential rules only (~50 lines)`,
|
|
342
|
+
value: 'minimal'
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
name: `${chalk.blue('Standard')} ${chalk.gray('(推薦)')} - Balanced coverage (~150 lines)`,
|
|
346
|
+
value: 'standard'
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
name: `${chalk.yellow('Comprehensive')} - Full documentation (~300+ lines)`,
|
|
350
|
+
value: 'comprehensive'
|
|
351
|
+
}
|
|
352
|
+
],
|
|
353
|
+
default: 'standard'
|
|
354
|
+
}
|
|
355
|
+
]);
|
|
356
|
+
|
|
357
|
+
return level;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Prompt for output language preference
|
|
362
|
+
* @returns {Promise<string>} 'en', 'zh-tw', or 'bilingual'
|
|
363
|
+
*/
|
|
364
|
+
export async function promptRuleLanguage() {
|
|
365
|
+
const { language } = await inquirer.prompt([
|
|
366
|
+
{
|
|
367
|
+
type: 'list',
|
|
368
|
+
name: 'language',
|
|
369
|
+
message: 'Select rule documentation language:',
|
|
370
|
+
choices: [
|
|
371
|
+
{
|
|
372
|
+
name: `${chalk.green('English')} ${chalk.gray('(推薦)')}`,
|
|
373
|
+
value: 'en'
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
name: `${chalk.blue('繁體中文')} (Traditional Chinese)`,
|
|
377
|
+
value: 'zh-tw'
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
name: `${chalk.yellow('Bilingual')} (雙語)`,
|
|
381
|
+
value: 'bilingual'
|
|
382
|
+
}
|
|
383
|
+
],
|
|
384
|
+
default: 'en'
|
|
385
|
+
}
|
|
386
|
+
]);
|
|
387
|
+
|
|
388
|
+
return language;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Get all rule categories
|
|
393
|
+
* @returns {Object} Rule categories configuration
|
|
394
|
+
*/
|
|
395
|
+
export function getRuleCategories() {
|
|
396
|
+
return RULE_CATEGORIES;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Get all language rules
|
|
401
|
+
* @returns {Object} Language rules configuration
|
|
402
|
+
*/
|
|
403
|
+
export function getLanguageRules() {
|
|
404
|
+
return LANGUAGE_RULES;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Prompt for complete integration configuration
|
|
409
|
+
* @param {string} tool - AI tool name
|
|
410
|
+
* @param {Object} detected - Detected project characteristics
|
|
411
|
+
* @param {boolean} existingRulesFound - Whether existing rules file was found
|
|
412
|
+
* @returns {Promise<Object>} Complete integration configuration
|
|
413
|
+
*/
|
|
414
|
+
export async function promptIntegrationConfig(tool, detected, existingRulesFound = false) {
|
|
415
|
+
const config = {
|
|
416
|
+
tool,
|
|
417
|
+
mode: 'default',
|
|
418
|
+
categories: Object.keys(RULE_CATEGORIES).filter(k => RULE_CATEGORIES[k].default),
|
|
419
|
+
languages: [],
|
|
420
|
+
exclusions: [],
|
|
421
|
+
customRules: [],
|
|
422
|
+
mergeStrategy: null,
|
|
423
|
+
detailLevel: 'standard',
|
|
424
|
+
language: 'en'
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
// If existing rules found, ask about merge strategy first
|
|
428
|
+
if (existingRulesFound) {
|
|
429
|
+
config.mergeStrategy = await promptMergeStrategy(tool);
|
|
430
|
+
if (config.mergeStrategy === 'keep') {
|
|
431
|
+
return config;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Ask for configuration mode
|
|
436
|
+
config.mode = await promptIntegrationMode();
|
|
437
|
+
|
|
438
|
+
if (config.mode === 'custom') {
|
|
439
|
+
// Custom mode: detailed configuration
|
|
440
|
+
config.categories = await promptRuleCategories(detected);
|
|
441
|
+
config.languages = await promptLanguageRules(detected.languages || {});
|
|
442
|
+
config.detailLevel = await promptDetailLevel();
|
|
443
|
+
config.language = await promptRuleLanguage();
|
|
444
|
+
config.exclusions = await promptExclusions();
|
|
445
|
+
config.customRules = await promptCustomRules();
|
|
446
|
+
} else if (config.mode === 'merge' && !existingRulesFound) {
|
|
447
|
+
// Merge mode selected but no existing file
|
|
448
|
+
console.log(chalk.yellow(' No existing rules file found. Using default mode.'));
|
|
449
|
+
config.mode = 'default';
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return config;
|
|
453
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, copyFileSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { dirname, join, basename } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { downloadStandard, downloadIntegration } from './github.js';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = dirname(__filename);
|
|
8
|
+
|
|
9
|
+
// Root of the universal-dev-standards repository (for local development)
|
|
10
|
+
const REPO_ROOT = join(__dirname, '../../..');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Copy a standard file to the target project
|
|
14
|
+
* Falls back to downloading from GitHub if local file not found
|
|
15
|
+
* @param {string} sourcePath - Relative path from repo root (e.g., 'core/anti-hallucination.md')
|
|
16
|
+
* @param {string} targetDir - Target directory (usually '.standards')
|
|
17
|
+
* @param {string} projectPath - Project root path
|
|
18
|
+
* @returns {Promise<Object>} Result with success status and copied path
|
|
19
|
+
*/
|
|
20
|
+
export async function copyStandard(sourcePath, targetDir, projectPath) {
|
|
21
|
+
const source = join(REPO_ROOT, sourcePath);
|
|
22
|
+
const targetFolder = join(projectPath, targetDir);
|
|
23
|
+
const targetFile = join(targetFolder, basename(sourcePath));
|
|
24
|
+
|
|
25
|
+
// Ensure target directory exists
|
|
26
|
+
if (!existsSync(targetFolder)) {
|
|
27
|
+
mkdirSync(targetFolder, { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Try local copy first
|
|
31
|
+
if (existsSync(source)) {
|
|
32
|
+
try {
|
|
33
|
+
copyFileSync(source, targetFile);
|
|
34
|
+
return {
|
|
35
|
+
success: true,
|
|
36
|
+
error: null,
|
|
37
|
+
path: targetFile
|
|
38
|
+
};
|
|
39
|
+
} catch (error) {
|
|
40
|
+
return {
|
|
41
|
+
success: false,
|
|
42
|
+
error: error.message,
|
|
43
|
+
path: null
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Fall back to downloading from GitHub
|
|
49
|
+
return downloadStandard(sourcePath, targetDir, projectPath);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Copy an integration file to its target location
|
|
54
|
+
* Falls back to downloading from GitHub if local file not found
|
|
55
|
+
* @param {string} sourcePath - Source path relative to repo root
|
|
56
|
+
* @param {string} targetPath - Target path relative to project root
|
|
57
|
+
* @param {string} projectPath - Project root path
|
|
58
|
+
* @returns {Promise<Object>} Result
|
|
59
|
+
*/
|
|
60
|
+
export async function copyIntegration(sourcePath, targetPath, projectPath) {
|
|
61
|
+
const source = join(REPO_ROOT, sourcePath);
|
|
62
|
+
const target = join(projectPath, targetPath);
|
|
63
|
+
|
|
64
|
+
// Ensure target directory exists
|
|
65
|
+
const targetDir = dirname(target);
|
|
66
|
+
if (!existsSync(targetDir)) {
|
|
67
|
+
mkdirSync(targetDir, { recursive: true });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Try local copy first
|
|
71
|
+
if (existsSync(source)) {
|
|
72
|
+
try {
|
|
73
|
+
copyFileSync(source, target);
|
|
74
|
+
return {
|
|
75
|
+
success: true,
|
|
76
|
+
error: null,
|
|
77
|
+
path: target
|
|
78
|
+
};
|
|
79
|
+
} catch (error) {
|
|
80
|
+
return {
|
|
81
|
+
success: false,
|
|
82
|
+
error: error.message,
|
|
83
|
+
path: null
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Fall back to downloading from GitHub
|
|
89
|
+
return downloadIntegration(sourcePath, targetPath, projectPath);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Create or update the manifest file
|
|
94
|
+
* @param {Object} manifest - Manifest data
|
|
95
|
+
* @param {string} projectPath - Project root path
|
|
96
|
+
*/
|
|
97
|
+
export function writeManifest(manifest, projectPath) {
|
|
98
|
+
const manifestPath = join(projectPath, '.standards', 'manifest.json');
|
|
99
|
+
const manifestDir = dirname(manifestPath);
|
|
100
|
+
|
|
101
|
+
if (!existsSync(manifestDir)) {
|
|
102
|
+
mkdirSync(manifestDir, { recursive: true });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
106
|
+
return manifestPath;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Read the manifest file
|
|
111
|
+
* @param {string} projectPath - Project root path
|
|
112
|
+
* @returns {Object|null} Manifest data or null if not found
|
|
113
|
+
*/
|
|
114
|
+
export function readManifest(projectPath) {
|
|
115
|
+
const manifestPath = join(projectPath, '.standards', 'manifest.json');
|
|
116
|
+
|
|
117
|
+
if (!existsSync(manifestPath)) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
return JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
123
|
+
} catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Check if standards are already initialized
|
|
130
|
+
* @param {string} projectPath - Project root path
|
|
131
|
+
* @returns {boolean} True if initialized
|
|
132
|
+
*/
|
|
133
|
+
export function isInitialized(projectPath) {
|
|
134
|
+
return existsSync(join(projectPath, '.standards', 'manifest.json'));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Get the repository root path
|
|
139
|
+
* @returns {string} Repository root path
|
|
140
|
+
*/
|
|
141
|
+
export function getRepoRoot() {
|
|
142
|
+
return REPO_ROOT;
|
|
143
|
+
}
|