universal-dev-standards 6.1.1 → 6.2.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/bundled/locales/zh-CN/CHANGELOG.md +33 -3
- package/bundled/locales/zh-CN/README.md +75 -33
- package/bundled/locales/zh-CN/SECURITY.md +1 -1
- package/bundled/locales/zh-CN/core/behavior-snapshot.md +2 -2
- package/bundled/locales/zh-CN/core/data-migration-testing.md +2 -2
- package/bundled/locales/zh-CN/docs/CHEATSHEET.md +4 -4
- package/bundled/locales/zh-CN/docs/CLI-INIT-OPTIONS.md +9 -3
- package/bundled/locales/zh-CN/docs/FEATURE-REFERENCE.md +10 -11
- package/bundled/locales/zh-CN/docs/USER-MANUAL.md +40 -21
- package/bundled/locales/zh-CN/integrations/gemini-cli/README.md +12 -0
- package/bundled/locales/zh-CN/skills/ac-coverage/SKILL.md +11 -5
- package/bundled/locales/zh-CN/skills/adr-assistant/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/commands/brainstorm.md +2 -2
- package/bundled/locales/zh-CN/skills/commit-standards/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/contract-test-assistant/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/deploy-assistant/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/dev-methodology/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/dev-workflow-guide/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/journey-test-assistant/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/knowledge-graph/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/knowledge-graph/guide.md +2 -2
- package/bundled/locales/zh-CN/skills/migration-assistant/SKILL.md +188 -2
- package/bundled/locales/zh-CN/skills/observability-assistant/guide.md +2 -2
- package/bundled/locales/zh-CN/skills/orchestrate/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/plan/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/push/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/retrospective-assistant/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/reverse-engineer/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/runbook-assistant/guide.md +2 -2
- package/bundled/locales/zh-CN/skills/skill-builder/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/slo-assistant/guide.md +2 -2
- package/bundled/locales/zh-CN/skills/spec-derivation/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/spec-driven-dev/SKILL.md +40 -2
- package/bundled/locales/zh-CN/skills/sweep/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/testing-guide/SKILL.md +2 -2
- package/bundled/locales/zh-TW/CHANGELOG.md +34 -3
- package/bundled/locales/zh-TW/README.md +75 -33
- package/bundled/locales/zh-TW/SECURITY.md +1 -1
- package/bundled/locales/zh-TW/core/audit-trail.md +2 -2
- package/bundled/locales/zh-TW/core/behavior-snapshot.md +2 -2
- package/bundled/locales/zh-TW/core/browser-compatibility-standards.md +18 -7
- package/bundled/locales/zh-TW/docs/CHEATSHEET.md +4 -4
- package/bundled/locales/zh-TW/docs/CLI-INIT-OPTIONS.md +9 -3
- package/bundled/locales/zh-TW/docs/FEATURE-REFERENCE.md +10 -11
- package/bundled/locales/zh-TW/docs/USER-MANUAL.md +40 -21
- package/bundled/locales/zh-TW/integrations/gemini-cli/README.md +12 -0
- package/bundled/skills/commands/journey-test.md +80 -6
- package/bundled/skills/commands/skill-builder.md +75 -6
- package/package.json +3 -3
- package/src/commands/check.js +54 -7
- package/src/commands/config.js +9 -0
- package/src/commands/init.js +15 -1
- package/src/commands/release.js +1 -1
- package/src/commands/update.js +82 -31
- package/src/config/ai-agent-paths.js +47 -7
- package/src/core/constants.js +61 -1
- package/src/core/manifest.js +56 -0
- package/src/flow/flow-parser.js +1 -1
- package/src/flow/gate-loader.js +1 -1
- package/src/i18n/messages.js +6 -0
- package/src/installers/standards-installer.js +10 -20
- package/src/reconciler/actual-state-scanner.js +118 -43
- package/src/reconciler/desired-state-calculator.js +157 -43
- package/src/reconciler/diff-engine.js +7 -2
- package/src/reconciler/manifest-migrator.js +5 -2
- package/src/reconciler/plan-executor.js +53 -14
- package/src/uninstallers/integration-uninstaller.js +7 -1
- package/src/utils/config-loader.js +1 -1
- package/src/utils/config-manager.js +1 -1
- package/src/utils/github.js +5 -1
- package/src/utils/hasher.js +7 -4
- package/src/utils/integration-generator.js +121 -78
- package/src/utils/registry.js +39 -0
- package/src/utils/skills-installer.js +49 -34
- package/src/utils/skills-source.js +51 -0
- package/src/utils/standard-fixer.js +1 -1
- package/src/utils/standard-validator.js +1 -1
- package/standards-registry.json +21 -11
package/src/commands/check.js
CHANGED
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
parseReferences,
|
|
31
31
|
compareStandardsWithReferences
|
|
32
32
|
} from '../utils/reference-sync.js';
|
|
33
|
-
import { extractMarkedContent, getToolFilePath } from '../utils/integration-generator.js';
|
|
33
|
+
import { extractMarkedContent, getToolFilePath, parseStandardsIndexCount } from '../utils/integration-generator.js';
|
|
34
34
|
import { getToolFormat } from '../core/constants.js';
|
|
35
35
|
import { checkForUpdates } from '../utils/npm-registry.js';
|
|
36
36
|
import { writeUpdateCache } from '../utils/update-checker.js';
|
|
@@ -39,6 +39,7 @@ import { WorkflowGate } from '../utils/workflow-gate.js';
|
|
|
39
39
|
import { t, setLanguage, isLanguageExplicitlySet } from '../i18n/messages.js';
|
|
40
40
|
import { guardAgainstSelfAdoption } from '../utils/detect-self-adoption.js';
|
|
41
41
|
import { lintAll as lintI18nAll, partitionFindings as partitionI18nFindings } from '../lint/i18n.js';
|
|
42
|
+
import { resolveIntegrationFile } from '../core/constants.js';
|
|
42
43
|
|
|
43
44
|
/**
|
|
44
45
|
* Display the summary of file integrity status
|
|
@@ -161,7 +162,9 @@ function performFileIntegrityCheck(projectPath, manifest, msg) {
|
|
|
161
162
|
}
|
|
162
163
|
|
|
163
164
|
// Check integrations
|
|
164
|
-
for (const
|
|
165
|
+
for (const intEntry of manifest.integrations) {
|
|
166
|
+
// 兩種形狀都要能解出路徑(XSPEC-343 R1)
|
|
167
|
+
const int = resolveIntegrationFile(intEntry) || intEntry;
|
|
165
168
|
const filePath = join(projectPath, int);
|
|
166
169
|
if (existsSync(filePath)) {
|
|
167
170
|
fileStatus.noHash.push(int);
|
|
@@ -727,7 +730,9 @@ function removeFromManifest(manifest, relativePath) {
|
|
|
727
730
|
const fileName = basename(relativePath);
|
|
728
731
|
manifest.standards = manifest.standards.filter(s => !s.endsWith(fileName));
|
|
729
732
|
manifest.extensions = manifest.extensions.filter(e => typeof e !== 'string' || !e.endsWith(fileName));
|
|
730
|
-
manifest.integrations = manifest.integrations.filter(
|
|
733
|
+
manifest.integrations = manifest.integrations.filter(
|
|
734
|
+
i => (resolveIntegrationFile(i) || i) !== relativePath
|
|
735
|
+
);
|
|
731
736
|
|
|
732
737
|
// Remove from integrationBlockHashes
|
|
733
738
|
if (manifest.integrationBlockHashes && manifest.integrationBlockHashes[relativePath]) {
|
|
@@ -761,7 +766,7 @@ export function getSourcePathFromRelative(manifest, relativePath) {
|
|
|
761
766
|
}
|
|
762
767
|
|
|
763
768
|
// Check integrations - these might need special handling
|
|
764
|
-
if (manifest.integrations.
|
|
769
|
+
if (manifest.integrations.some(i => (resolveIntegrationFile(i) || i) === relativePath)) {
|
|
765
770
|
// Integration files have different source paths
|
|
766
771
|
const integrationMappings = {
|
|
767
772
|
'.cursorrules': 'integrations/cursor/.cursorrules',
|
|
@@ -831,7 +836,8 @@ async function migrateToHashBasedTracking(projectPath, manifest) {
|
|
|
831
836
|
}
|
|
832
837
|
|
|
833
838
|
// Process integrations
|
|
834
|
-
for (const
|
|
839
|
+
for (const intEntry of manifest.integrations) {
|
|
840
|
+
const int = resolveIntegrationFile(intEntry) || intEntry;
|
|
835
841
|
const fullPath = join(projectPath, int);
|
|
836
842
|
|
|
837
843
|
const hashInfo = computeFileHash(fullPath);
|
|
@@ -1148,6 +1154,27 @@ function checkIntegrationFiles(manifest, projectPath, msg) {
|
|
|
1148
1154
|
// Report status - use all installed standards as the total
|
|
1149
1155
|
const totalTrackable = standardsFiles.length;
|
|
1150
1156
|
|
|
1157
|
+
// Index-mode blocks deliberately do NOT enumerate standards (XSPEC-358 R1);
|
|
1158
|
+
// grepping for each name reports a false failure and points at `uds update`,
|
|
1159
|
+
// which regenerates the identical block. Assert the declared count instead.
|
|
1160
|
+
const declaredCount = parseStandardsIndexCount(content);
|
|
1161
|
+
if (declaredCount !== null) {
|
|
1162
|
+
if (declaredCount === totalTrackable) {
|
|
1163
|
+
console.log(chalk.green(` ✓ ${toolFile}:`));
|
|
1164
|
+
console.log(chalk.gray(` ${msg.standardsIndexPresent}`));
|
|
1165
|
+
console.log(chalk.gray(` ${msg.standardsIndexCount
|
|
1166
|
+
? msg.standardsIndexCount.replace('{count}', declaredCount)
|
|
1167
|
+
: `Index declares ${declaredCount} standards (matches manifest)`}`));
|
|
1168
|
+
} else {
|
|
1169
|
+
console.log(chalk.yellow(` ⚠ ${toolFile}:`));
|
|
1170
|
+
console.log(chalk.yellow(` ${msg.standardsIndexCountMismatch
|
|
1171
|
+
? msg.standardsIndexCountMismatch.replace('{declared}', declaredCount).replace('{actual}', totalTrackable)
|
|
1172
|
+
: `Index declares ${declaredCount} standards but the manifest has ${totalTrackable}`}`));
|
|
1173
|
+
hasIssues = true;
|
|
1174
|
+
}
|
|
1175
|
+
continue;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1151
1178
|
if (hasStandardsIndex && missingStandards.length === 0) {
|
|
1152
1179
|
console.log(chalk.green(` ✓ ${toolFile}:`));
|
|
1153
1180
|
console.log(chalk.gray(` ${msg.standardsIndexPresent}`));
|
|
@@ -1219,6 +1246,25 @@ function checkAgentsMdSync(manifest, projectPath, msg) {
|
|
|
1219
1246
|
const content = readFileSync(agentsMdPath, 'utf-8');
|
|
1220
1247
|
const installedStandards = (manifest.standards || []).map(s => basename(s));
|
|
1221
1248
|
|
|
1249
|
+
// Same as the AI-tool integration check above: an index-mode block declares a
|
|
1250
|
+
// count instead of listing names, so the name grep below cannot apply to it.
|
|
1251
|
+
const declaredCount = parseStandardsIndexCount(content);
|
|
1252
|
+
if (declaredCount !== null) {
|
|
1253
|
+
const actual = installedStandards.length;
|
|
1254
|
+
if (declaredCount === actual) {
|
|
1255
|
+
console.log(chalk.green(` ✓ AGENTS.md ${msg.standardsSynced || 'standards synced'} (${declaredCount})`));
|
|
1256
|
+
} else {
|
|
1257
|
+
console.log(chalk.yellow(` ⚠ AGENTS.md ${msg.standardsOutOfSync || 'standards out of sync'} (${declaredCount} != ${actual})`));
|
|
1258
|
+
console.log(chalk.gray(` ${msg.runUpdateToSync || 'Run "uds update" to sync'}`));
|
|
1259
|
+
}
|
|
1260
|
+
const idxLineCount = content.split('\n').length;
|
|
1261
|
+
if (idxLineCount > 150) {
|
|
1262
|
+
console.log(chalk.yellow(` ⚠ AGENTS.md ${msg.exceedsLineLimit || 'exceeds 150 line limit'} (${idxLineCount} lines)`));
|
|
1263
|
+
}
|
|
1264
|
+
console.log();
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1222
1268
|
// Check standards listed in AGENTS.md vs manifest
|
|
1223
1269
|
const aiYamlStandards = installedStandards.filter(s => s.endsWith('.ai.yaml'));
|
|
1224
1270
|
let listedCount = 0;
|
|
@@ -1264,7 +1310,8 @@ function checkReferenceSync(manifest, projectPath, msg) {
|
|
|
1264
1310
|
|
|
1265
1311
|
// Pre-scan: collect results for files that have references
|
|
1266
1312
|
const results = [];
|
|
1267
|
-
for (const
|
|
1313
|
+
for (const integrationEntry of manifest.integrations) {
|
|
1314
|
+
const integrationPath = resolveIntegrationFile(integrationEntry) || integrationEntry;
|
|
1268
1315
|
const fullPath = join(projectPath, integrationPath);
|
|
1269
1316
|
|
|
1270
1317
|
if (!existsSync(fullPath)) continue;
|
|
@@ -1700,7 +1747,7 @@ function getFileStatusCounts(manifest, projectPath) {
|
|
|
1700
1747
|
const allFiles = [
|
|
1701
1748
|
...manifest.standards.map(s => `.standards/${basename(s)}`),
|
|
1702
1749
|
...manifest.extensions.filter(e => typeof e === 'string').map(e => `.standards/${basename(e)}`),
|
|
1703
|
-
...manifest.integrations
|
|
1750
|
+
...manifest.integrations.map(i => resolveIntegrationFile(i) || i)
|
|
1704
1751
|
];
|
|
1705
1752
|
for (const relativePath of allFiles) {
|
|
1706
1753
|
const fullPath = join(projectPath, relativePath);
|
package/src/commands/config.js
CHANGED
|
@@ -49,6 +49,7 @@ import {
|
|
|
49
49
|
} from '../utils/integration-generator.js';
|
|
50
50
|
import { getMarketplaceSkillsInfo } from '../utils/github.js';
|
|
51
51
|
import { regenerateIntegrations } from './update.js';
|
|
52
|
+
import { mergeInstalledNames } from '../core/manifest.js';
|
|
52
53
|
|
|
53
54
|
/**
|
|
54
55
|
* Get localized message with fallback (for config-specific keys)
|
|
@@ -995,6 +996,7 @@ export async function runProjectConfiguration(options) {
|
|
|
995
996
|
manifest.skills.installations.push(inst);
|
|
996
997
|
}
|
|
997
998
|
}
|
|
999
|
+
manifest.skills.names = mergeInstalledNames(manifest.skills.names, skillResult);
|
|
998
1000
|
if (skillResult.allFileHashes) {
|
|
999
1001
|
manifest.skillHashes = { ...(manifest.skillHashes || {}), ...skillResult.allFileHashes };
|
|
1000
1002
|
}
|
|
@@ -1031,6 +1033,7 @@ export async function runProjectConfiguration(options) {
|
|
|
1031
1033
|
manifest.commands.installations.push(inst);
|
|
1032
1034
|
}
|
|
1033
1035
|
}
|
|
1036
|
+
manifest.commands.names = mergeInstalledNames(manifest.commands.names, cmdResult);
|
|
1034
1037
|
if (cmdResult.allFileHashes) {
|
|
1035
1038
|
manifest.commandHashes = { ...(manifest.commandHashes || {}), ...cmdResult.allFileHashes };
|
|
1036
1039
|
}
|
|
@@ -1234,6 +1237,7 @@ async function handleSkillsConfiguration(manifest, projectPath, msgObj, common,
|
|
|
1234
1237
|
} else {
|
|
1235
1238
|
manifest.skills.installations.push(installations[0]);
|
|
1236
1239
|
}
|
|
1240
|
+
manifest.skills.names = mergeInstalledNames(manifest.skills.names, result);
|
|
1237
1241
|
writeManifest(manifest, projectPath);
|
|
1238
1242
|
return;
|
|
1239
1243
|
}
|
|
@@ -1368,6 +1372,7 @@ async function handleSkillsConfiguration(manifest, projectPath, msgObj, common,
|
|
|
1368
1372
|
// Update manifest - clear declined status for installed tools
|
|
1369
1373
|
manifest.skills = manifest.skills || {};
|
|
1370
1374
|
manifest.skills.installations = installations;
|
|
1375
|
+
manifest.skills.names = mergeInstalledNames(manifest.skills.names, result);
|
|
1371
1376
|
if (manifest.declinedFeatures?.skills) {
|
|
1372
1377
|
manifest.declinedFeatures.skills = manifest.declinedFeatures.skills.filter(
|
|
1373
1378
|
tool => !declinedToolsWithSupport.includes(tool)
|
|
@@ -1402,6 +1407,7 @@ async function handleSkillsConfiguration(manifest, projectPath, msgObj, common,
|
|
|
1402
1407
|
// Update manifest
|
|
1403
1408
|
manifest.skills = manifest.skills || {};
|
|
1404
1409
|
manifest.skills.installations = installations;
|
|
1410
|
+
manifest.skills.names = mergeInstalledNames(manifest.skills.names, result);
|
|
1405
1411
|
writeManifest(manifest, projectPath);
|
|
1406
1412
|
}
|
|
1407
1413
|
|
|
@@ -1465,6 +1471,7 @@ async function handleCommandsConfiguration(manifest, projectPath, msgObj, common
|
|
|
1465
1471
|
} else {
|
|
1466
1472
|
manifest.commands.installations.push(installations[0]);
|
|
1467
1473
|
}
|
|
1474
|
+
manifest.commands.names = mergeInstalledNames(manifest.commands.names, result);
|
|
1468
1475
|
writeManifest(manifest, projectPath);
|
|
1469
1476
|
return;
|
|
1470
1477
|
}
|
|
@@ -1566,6 +1573,7 @@ async function handleCommandsConfiguration(manifest, projectPath, msgObj, common
|
|
|
1566
1573
|
// Update manifest - clear declined status for installed tools
|
|
1567
1574
|
manifest.commands = manifest.commands || {};
|
|
1568
1575
|
manifest.commands.installations = declinedCommandsWithSupport;
|
|
1576
|
+
manifest.commands.names = mergeInstalledNames(manifest.commands.names, result);
|
|
1569
1577
|
if (manifest.declinedFeatures?.commands) {
|
|
1570
1578
|
manifest.declinedFeatures.commands = manifest.declinedFeatures.commands.filter(
|
|
1571
1579
|
tool => !declinedCommandsWithSupport.includes(tool)
|
|
@@ -1601,5 +1609,6 @@ async function handleCommandsConfiguration(manifest, projectPath, msgObj, common
|
|
|
1601
1609
|
// Update manifest
|
|
1602
1610
|
manifest.commands = manifest.commands || {};
|
|
1603
1611
|
manifest.commands.installations = selectedAgents;
|
|
1612
|
+
manifest.commands.names = mergeInstalledNames(manifest.commands.names, result);
|
|
1604
1613
|
writeManifest(manifest, projectPath);
|
|
1605
1614
|
}
|
package/src/commands/init.js
CHANGED
|
@@ -240,7 +240,21 @@ export async function initCommand(options) {
|
|
|
240
240
|
* - Node.js projects: use husky
|
|
241
241
|
* - Non-Node.js projects: write native .git/hooks/pre-commit
|
|
242
242
|
*/
|
|
243
|
-
export async function setupHuskyHook(projectPath) {
|
|
243
|
+
export async function setupHuskyHook(projectPath, { allowInTest = false } = {}) {
|
|
244
|
+
// 2026-07-30:本函式在跑測試時改寫了 universal-dev-standards 自己
|
|
245
|
+
// ——`npm install --save-dev husky`、改 package.json、建 .husky/pre-commit。
|
|
246
|
+
//
|
|
247
|
+
// 路徑是:測試呼叫 `initCommand({...})`(三個 init 測試檔都沒帶路徑、
|
|
248
|
+
// 也沒 mock child_process)→ `init.js` 取 `process.cwd()` → 走到這裡。
|
|
249
|
+
// 而下面那個 `.git` 是唯一的守衛,它只問「cwd 是不是一個 repo」:
|
|
250
|
+
// 從 `cli/` 跑 → cwd 沒有 .git → 提早 return,無害;
|
|
251
|
+
// 從 repo root 跑 → cwd **有** .git → 照跑,動到開發者自己的 repo。
|
|
252
|
+
// 同一批測試、不同 cwd,一個無害一個會寫檔。
|
|
253
|
+
//
|
|
254
|
+
// 守衛用顯式 opt-in 而非「偵測到測試就一律跳過」:`init.husky.test.js` 有十幾條
|
|
255
|
+
// 測試是**刻意**在暫存目錄裡驗這個函式的行為,一律跳過會把它們變成永遠通過的空測試。
|
|
256
|
+
if (process.env.VITEST && !allowInTest) return;
|
|
257
|
+
|
|
244
258
|
const hasGit = existsSync(join(projectPath, '.git'));
|
|
245
259
|
if (!hasGit) return;
|
|
246
260
|
|
package/src/commands/release.js
CHANGED
package/src/commands/update.js
CHANGED
|
@@ -2,7 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import ora from 'ora';
|
|
3
3
|
import { select, confirm as inquirerConfirm, checkbox, Separator } from '@inquirer/prompts';
|
|
4
4
|
import { execSync } from 'child_process';
|
|
5
|
-
import { existsSync, unlinkSync } from 'fs';
|
|
5
|
+
import { existsSync, readFileSync, unlinkSync } from 'fs';
|
|
6
6
|
import { join, basename } from 'path';
|
|
7
7
|
import { readManifest, writeManifest, copyStandard, isInitialized } from '../utils/copier.js';
|
|
8
8
|
import { getRepositoryInfo, getAllStandards, getStandardSource } from '../utils/registry.js';
|
|
@@ -11,7 +11,10 @@ import {
|
|
|
11
11
|
writeIntegrationFile,
|
|
12
12
|
getToolFilePath,
|
|
13
13
|
writeAgentsMdSummary,
|
|
14
|
-
resolveContentModeForTool
|
|
14
|
+
resolveContentModeForTool,
|
|
15
|
+
generateIntegrationContent,
|
|
16
|
+
extractMarkedContent,
|
|
17
|
+
buildToolIntegrationConfig
|
|
15
18
|
} from '../utils/integration-generator.js';
|
|
16
19
|
import {
|
|
17
20
|
calculateCategoriesFromStandards,
|
|
@@ -51,6 +54,8 @@ import {
|
|
|
51
54
|
} from '../reconciler/index.js';
|
|
52
55
|
import { restoreSingleFile } from './check.js';
|
|
53
56
|
import { guardAgainstSelfAdoption } from '../utils/detect-self-adoption.js';
|
|
57
|
+
import { resolveIntegrationFile } from '../core/constants.js';
|
|
58
|
+
import { mergeInstalledNames } from '../core/manifest.js';
|
|
54
59
|
|
|
55
60
|
/**
|
|
56
61
|
* Determine the correct target directory for a standard file.
|
|
@@ -335,8 +340,12 @@ export async function updateCommand(options) {
|
|
|
335
340
|
}
|
|
336
341
|
|
|
337
342
|
// Handle --integrations-only option
|
|
343
|
+
// ⚠️ 這個分支在 --plan 檢查(見下)**之前**,所以必須自己處理 --plan——
|
|
344
|
+
// 否則 `--plan --integrations-only` 會靜默寫檔。2026-07-30 實測踩到:
|
|
345
|
+
// 以該組合驗證 XSPEC-358 的區塊變更時,它改了 machine-setup 的
|
|
346
|
+
// CLAUDE.md 與 .standards/manifest.json,而旗標文件寫的是 "without executing"。
|
|
338
347
|
if (options.integrationsOnly) {
|
|
339
|
-
await updateIntegrationsOnly(projectPath, manifest);
|
|
348
|
+
await updateIntegrationsOnly(projectPath, manifest, options);
|
|
340
349
|
return;
|
|
341
350
|
}
|
|
342
351
|
|
|
@@ -847,8 +856,9 @@ export async function updateCommand(options) {
|
|
|
847
856
|
const fileName = basename(ext);
|
|
848
857
|
allTrackedFiles.push(join('.standards', fileName));
|
|
849
858
|
}
|
|
850
|
-
for (const
|
|
851
|
-
|
|
859
|
+
for (const intEntry of (manifest.integrations || [])) {
|
|
860
|
+
// 兩種形狀都要能解出路徑(XSPEC-343 R1)
|
|
861
|
+
const filePath = resolveIntegrationFile(intEntry) || getToolFilePath(intEntry);
|
|
852
862
|
if (filePath) {
|
|
853
863
|
allTrackedFiles.push(filePath);
|
|
854
864
|
}
|
|
@@ -951,6 +961,7 @@ export async function updateCommand(options) {
|
|
|
951
961
|
...(manifest.skills.installations || []),
|
|
952
962
|
...installSkills
|
|
953
963
|
];
|
|
964
|
+
manifest.skills.names = mergeInstalledNames(manifest.skills.names, skillResult);
|
|
954
965
|
|
|
955
966
|
// Derive location from installations if not set
|
|
956
967
|
if (!manifest.skills.location) {
|
|
@@ -987,6 +998,7 @@ export async function updateCommand(options) {
|
|
|
987
998
|
// Update manifest version
|
|
988
999
|
if (!manifest.skills) manifest.skills = {};
|
|
989
1000
|
manifest.skills.version = repoInfo.skills.version;
|
|
1001
|
+
manifest.skills.names = mergeInstalledNames(manifest.skills.names, updateResult);
|
|
990
1002
|
|
|
991
1003
|
// Derive location from installations if not set
|
|
992
1004
|
if (!manifest.skills.location && manifest.skills.installations?.length > 0) {
|
|
@@ -1028,6 +1040,7 @@ export async function updateCommand(options) {
|
|
|
1028
1040
|
...(manifest.commands.installations || []),
|
|
1029
1041
|
...installCommands
|
|
1030
1042
|
];
|
|
1043
|
+
manifest.commands.names = mergeInstalledNames(manifest.commands.names, cmdResult);
|
|
1031
1044
|
|
|
1032
1045
|
// Update command hashes for integrity tracking
|
|
1033
1046
|
if (cmdResult.allFileHashes) {
|
|
@@ -1053,6 +1066,7 @@ export async function updateCommand(options) {
|
|
|
1053
1066
|
// Update manifest version
|
|
1054
1067
|
if (!manifest.commands) manifest.commands = {};
|
|
1055
1068
|
manifest.commands.version = repoInfo.skills.version;
|
|
1069
|
+
manifest.commands.names = mergeInstalledNames(manifest.commands.names, updateCmdResult);
|
|
1056
1070
|
|
|
1057
1071
|
// Update command hashes for integrity tracking
|
|
1058
1072
|
if (updateCmdResult.allFileHashes) {
|
|
@@ -1127,6 +1141,7 @@ export async function updateCommand(options) {
|
|
|
1127
1141
|
manifest.skills.installed = true;
|
|
1128
1142
|
manifest.skills.version = repoInfo.skills.version;
|
|
1129
1143
|
manifest.skills.installations = [...(manifest.skills.installations || []), ...missingSkills];
|
|
1144
|
+
manifest.skills.names = mergeInstalledNames(manifest.skills.names, result);
|
|
1130
1145
|
if (result.allFileHashes) {
|
|
1131
1146
|
if (!manifest.skillHashes) manifest.skillHashes = {};
|
|
1132
1147
|
Object.assign(manifest.skillHashes, result.allFileHashes);
|
|
@@ -1144,6 +1159,7 @@ export async function updateCommand(options) {
|
|
|
1144
1159
|
const result = await installSkillsToMultipleAgents(outdatedSkills, null, projectPath, skillsLocale);
|
|
1145
1160
|
if (!manifest.skills) manifest.skills = {};
|
|
1146
1161
|
manifest.skills.version = repoInfo.skills.version;
|
|
1162
|
+
manifest.skills.names = mergeInstalledNames(manifest.skills.names, result);
|
|
1147
1163
|
if (!manifest.skills.location && manifest.skills.installations?.length > 0) {
|
|
1148
1164
|
const levels = manifest.skills.installations.map(s => s.level).filter(Boolean);
|
|
1149
1165
|
const uniqueLevels = [...new Set(levels)];
|
|
@@ -1168,6 +1184,7 @@ export async function updateCommand(options) {
|
|
|
1168
1184
|
manifest.commands.installed = true;
|
|
1169
1185
|
manifest.commands.version = repoInfo.skills.version;
|
|
1170
1186
|
manifest.commands.installations = [...(manifest.commands.installations || []), ...missingCommands];
|
|
1187
|
+
manifest.commands.names = mergeInstalledNames(manifest.commands.names, result);
|
|
1171
1188
|
if (result.allFileHashes) {
|
|
1172
1189
|
if (!manifest.commandHashes) manifest.commandHashes = {};
|
|
1173
1190
|
replaceCommandHashesForUpdatedAgents(manifest.commandHashes, result.allFileHashes);
|
|
@@ -1185,6 +1202,7 @@ export async function updateCommand(options) {
|
|
|
1185
1202
|
const result = await installCommandsToMultipleAgents(outdatedCommands, null, projectPath, skillsLocale);
|
|
1186
1203
|
if (!manifest.commands) manifest.commands = {};
|
|
1187
1204
|
manifest.commands.version = repoInfo.skills.version;
|
|
1205
|
+
manifest.commands.names = mergeInstalledNames(manifest.commands.names, result);
|
|
1188
1206
|
if (result.allFileHashes) {
|
|
1189
1207
|
if (!manifest.commandHashes) manifest.commandHashes = {};
|
|
1190
1208
|
replaceCommandHashesForUpdatedAgents(manifest.commandHashes, result.allFileHashes);
|
|
@@ -1319,17 +1337,6 @@ export function regenerateIntegrations(projectPath, manifest) {
|
|
|
1319
1337
|
return { success: true, updated: [], errors: [] };
|
|
1320
1338
|
}
|
|
1321
1339
|
|
|
1322
|
-
// Build installed standards list
|
|
1323
|
-
const installedStandardsList = manifest.standards?.map(s => basename(s)) || [];
|
|
1324
|
-
|
|
1325
|
-
// Determine language setting
|
|
1326
|
-
let commonLanguage = 'en';
|
|
1327
|
-
if ((manifest.options?.output_language || manifest.options?.commit_language) === 'bilingual') {
|
|
1328
|
-
commonLanguage = 'bilingual';
|
|
1329
|
-
} else if ((manifest.options?.output_language || manifest.options?.commit_language) === 'traditional-chinese') {
|
|
1330
|
-
commonLanguage = 'zh-tw';
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
1340
|
const results = {
|
|
1334
1341
|
updated: [],
|
|
1335
1342
|
errors: []
|
|
@@ -1345,19 +1352,8 @@ export function regenerateIntegrations(projectPath, manifest) {
|
|
|
1345
1352
|
continue; // Skip if already generated (AGENTS.md sharing)
|
|
1346
1353
|
}
|
|
1347
1354
|
|
|
1348
|
-
//
|
|
1349
|
-
const
|
|
1350
|
-
const resolvedMode = resolveContentModeForTool(tool, savedMode);
|
|
1351
|
-
const toolConfig = {
|
|
1352
|
-
tool,
|
|
1353
|
-
categories: ['anti-hallucination', 'commit-standards', 'code-review'],
|
|
1354
|
-
language: commonLanguage,
|
|
1355
|
-
installedStandards: installedStandardsList,
|
|
1356
|
-
contentMode: resolvedMode.contentMode,
|
|
1357
|
-
level: resolvedMode.level,
|
|
1358
|
-
// Pass output_language for dynamic commit standards generation
|
|
1359
|
-
outputLanguage: manifest.options?.output_language || manifest.options?.commit_language || 'english'
|
|
1360
|
-
};
|
|
1355
|
+
// Shared with the reconciler so both paths emit the identical block.
|
|
1356
|
+
const toolConfig = buildToolIntegrationConfig(manifest, tool);
|
|
1361
1357
|
|
|
1362
1358
|
const result = writeIntegrationFile(tool, toolConfig, projectPath);
|
|
1363
1359
|
if (result.success) {
|
|
@@ -1392,7 +1388,7 @@ export function regenerateIntegrations(projectPath, manifest) {
|
|
|
1392
1388
|
// Regenerate universal AGENTS.md if enabled and not already covered
|
|
1393
1389
|
if (manifest.generateAgentsMd && !generatedFiles.has('AGENTS.md')) {
|
|
1394
1390
|
const summaryConfig = {
|
|
1395
|
-
installedStandards:
|
|
1391
|
+
installedStandards: (manifest.standards || []).map(s => basename(s)),
|
|
1396
1392
|
language: manifest.options?.display_language || 'en',
|
|
1397
1393
|
outputLanguage: manifest.options?.output_language || manifest.options?.commit_language || 'english',
|
|
1398
1394
|
standardOptions: manifest.options || {}
|
|
@@ -1422,7 +1418,7 @@ export function regenerateIntegrations(projectPath, manifest) {
|
|
|
1422
1418
|
* @param {string} projectPath - Project path
|
|
1423
1419
|
* @param {Object} manifest - Manifest object
|
|
1424
1420
|
*/
|
|
1425
|
-
async function updateIntegrationsOnly(projectPath, manifest) {
|
|
1421
|
+
async function updateIntegrationsOnly(projectPath, manifest, options = {}) {
|
|
1426
1422
|
const msg = t().commands.update;
|
|
1427
1423
|
|
|
1428
1424
|
console.log(chalk.cyan(msg.updatingIntegrationsOnly));
|
|
@@ -1436,6 +1432,59 @@ async function updateIntegrationsOnly(projectPath, manifest) {
|
|
|
1436
1432
|
return;
|
|
1437
1433
|
}
|
|
1438
1434
|
|
|
1435
|
+
// --plan:只比對、不寫任何檔(連 manifest 也不寫)。
|
|
1436
|
+
// 這一段刻意不呼叫 regenerateIntegrations——那個函式會寫檔,
|
|
1437
|
+
// 「先寫再還原」的作法在中途掛掉時會留下壞狀態,比沒有 dry-run 更糟。
|
|
1438
|
+
if (options.plan) {
|
|
1439
|
+
console.log(chalk.bold('=== Integration Plan (dry run — nothing is written) ==='));
|
|
1440
|
+
const wouldChange = [];
|
|
1441
|
+
const unchanged = [];
|
|
1442
|
+
const seen = new Set();
|
|
1443
|
+
for (const tool of aiTools) {
|
|
1444
|
+
const targetFile = getToolFilePath(tool);
|
|
1445
|
+
if (seen.has(targetFile)) continue;
|
|
1446
|
+
seen.add(targetFile);
|
|
1447
|
+
const savedMode = manifest.contentMode || 'auto';
|
|
1448
|
+
const resolvedMode = resolveContentModeForTool(tool, savedMode);
|
|
1449
|
+
const next = generateIntegrationContent({
|
|
1450
|
+
tool,
|
|
1451
|
+
categories: ['anti-hallucination', 'commit-standards', 'code-review'],
|
|
1452
|
+
language: (manifest.options?.output_language || manifest.options?.commit_language) === 'bilingual'
|
|
1453
|
+
? 'bilingual'
|
|
1454
|
+
: (manifest.options?.output_language || manifest.options?.commit_language) === 'traditional-chinese'
|
|
1455
|
+
? 'zh-tw' : 'en',
|
|
1456
|
+
installedStandards: manifest.standards?.map((x) => basename(x)) || [],
|
|
1457
|
+
contentMode: resolvedMode.contentMode,
|
|
1458
|
+
level: resolvedMode.level,
|
|
1459
|
+
outputLanguage: manifest.options?.output_language || manifest.options?.commit_language || 'english'
|
|
1460
|
+
});
|
|
1461
|
+
const full = join(projectPath, targetFile);
|
|
1462
|
+
const current = existsSync(full) ? readFileSync(full, 'utf8') : '';
|
|
1463
|
+
const format = targetFile.endsWith('.md') ? 'markdown' : 'plaintext';
|
|
1464
|
+
const cur = extractMarkedContent(current, format).content || '';
|
|
1465
|
+
// 比對正規化過的內容:只關心「受管區塊會不會變」,不關心尾端空白。
|
|
1466
|
+
if (cur.trim() === String(next).trim()) {
|
|
1467
|
+
unchanged.push(targetFile);
|
|
1468
|
+
} else {
|
|
1469
|
+
const delta = String(next).trim().length - cur.trim().length;
|
|
1470
|
+
wouldChange.push(`${targetFile} (${delta >= 0 ? '+' : ''}${delta} bytes in the UDS block)`);
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
if (wouldChange.length > 0) {
|
|
1474
|
+
console.log(chalk.yellow(` ~ Would update (${wouldChange.length}):`));
|
|
1475
|
+
for (const f of wouldChange) console.log(chalk.gray(` ${f}`));
|
|
1476
|
+
}
|
|
1477
|
+
if (unchanged.length > 0) {
|
|
1478
|
+
console.log(chalk.gray(` = Unchanged (${unchanged.length}): ${unchanged.join(', ')}`));
|
|
1479
|
+
}
|
|
1480
|
+
console.log();
|
|
1481
|
+
console.log(chalk.gray(' Also NOT written in plan mode: .standards/manifest.json'));
|
|
1482
|
+
console.log(chalk.gray(' (a real run rewrites it — version, fileHashes, integrationBlockHashes,'));
|
|
1483
|
+
console.log(chalk.gray(' and it normalises any standards entries stored as full paths)'));
|
|
1484
|
+
console.log();
|
|
1485
|
+
return;
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1439
1488
|
const spinner = ora(msg.regeneratingIntegrations).start();
|
|
1440
1489
|
|
|
1441
1490
|
// Use reusable regeneration function
|
|
@@ -1698,6 +1747,7 @@ async function updateSkillsOnly(projectPath, manifest, options) {
|
|
|
1698
1747
|
// Update manifest
|
|
1699
1748
|
manifest.skills.version = latestVersion;
|
|
1700
1749
|
manifest.skills.installations = skillsInstallations;
|
|
1750
|
+
manifest.skills.names = mergeInstalledNames(manifest.skills.names, result);
|
|
1701
1751
|
|
|
1702
1752
|
// Update skill hashes for integrity tracking
|
|
1703
1753
|
if (result.allFileHashes) {
|
|
@@ -1804,6 +1854,7 @@ async function updateCommandsOnly(projectPath, manifest, options) {
|
|
|
1804
1854
|
}
|
|
1805
1855
|
return inst;
|
|
1806
1856
|
});
|
|
1857
|
+
manifest.commands.names = mergeInstalledNames(manifest.commands.names, result);
|
|
1807
1858
|
|
|
1808
1859
|
// Update command hashes for integrity tracking
|
|
1809
1860
|
if (result.allFileHashes) {
|
|
@@ -147,11 +147,22 @@ export const AI_AGENT_PATHS = {
|
|
|
147
147
|
'codex': {
|
|
148
148
|
name: 'OpenAI Codex',
|
|
149
149
|
tier: 'partial',
|
|
150
|
+
// Verified against the official docs 2026-07-23 (developers.openai.com/codex/skills):
|
|
151
|
+
// Codex discovers skills in $CWD/.agents/skills, parent directories, $REPO_ROOT/.agents/skills
|
|
152
|
+
// and $HOME/.agents/skills. Note the plural `.agents/`.
|
|
153
|
+
//
|
|
154
|
+
// These were previously '.codex/skills/' and '~/.codex/skills', which Codex never reads —
|
|
155
|
+
// a directory UDS invented. Skills installed there were invisible, which is why a
|
|
156
|
+
// behavioural probe against Codex failed while Codex itself was behaving correctly
|
|
157
|
+
// (see integrations/verification/codex/2026-07-23.md).
|
|
150
158
|
skills: {
|
|
151
|
-
project: '.
|
|
152
|
-
user: join(homedir(), '.
|
|
159
|
+
project: '.agents/skills/',
|
|
160
|
+
user: join(homedir(), '.agents', 'skills')
|
|
153
161
|
},
|
|
154
162
|
commands: null, // Uses system commands
|
|
163
|
+
// NOT verified. The official skills docs describe `agents/openai.yaml` as a file *inside*
|
|
164
|
+
// a skill directory, not a separate top-level agents location. Left as-is rather than
|
|
165
|
+
// guessed at: an unverified path fails silently, which is the failure mode being fixed above.
|
|
155
166
|
agents: {
|
|
156
167
|
project: '.codex/agents/',
|
|
157
168
|
user: join(homedir(), '.codex', 'agents')
|
|
@@ -241,16 +252,27 @@ export const AI_AGENT_PATHS = {
|
|
|
241
252
|
'antigravity': {
|
|
242
253
|
name: 'Google Antigravity',
|
|
243
254
|
tier: 'minimal',
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
255
|
+
// Skills install path is UNVERIFIED against a real Antigravity CLI, so it is null:
|
|
256
|
+
// `supportsSkills && skills` is the install guard everywhere (init.js, init-flow.js,
|
|
257
|
+
// update.js, config.js), and a null `skills` makes it decline rather than write to a
|
|
258
|
+
// path the tool may never read.
|
|
259
|
+
//
|
|
260
|
+
// Two candidates conflict, and neither has been tested:
|
|
261
|
+
// a) ~/.gemini/antigravity-cli/plugins/<name>/skills/ -- official plugin docs
|
|
262
|
+
// b) .agent/skills/ + ~/.gemini/antigravity/skills -- UDS's own 2026-02 spec,
|
|
263
|
+
// written while Gemini CLI was still the product; Antigravity replaced it on
|
|
264
|
+
// 2026-06-18, so (b) inherits an assumption that may no longer hold.
|
|
265
|
+
//
|
|
266
|
+
// Installing to the wrong path fails SILENTLY -- the user sees a successful init and
|
|
267
|
+
// an assistant that never picks the skills up. Declining is the safer default until
|
|
268
|
+
// one candidate is confirmed. Tracked as XSPEC-355 OQ6.
|
|
269
|
+
skills: null,
|
|
248
270
|
commands: null,
|
|
249
271
|
agents: null,
|
|
250
272
|
workflows: null,
|
|
251
273
|
supportsMarketplace: false,
|
|
252
274
|
fallbackSkillsPath: null,
|
|
253
|
-
supportsSkills: true, //
|
|
275
|
+
supportsSkills: true, // The tool does support skills; only our path for it is unverified.
|
|
254
276
|
supportsTask: false,
|
|
255
277
|
supportsAgents: false
|
|
256
278
|
}
|
|
@@ -313,6 +335,24 @@ export function getCommandsDirForAgent(agent, level = 'project', projectPath = n
|
|
|
313
335
|
return null;
|
|
314
336
|
}
|
|
315
337
|
|
|
338
|
+
/**
|
|
339
|
+
* Get the on-disk file extension used for an agent's slash commands.
|
|
340
|
+
*
|
|
341
|
+
* The command *name* (`commit`) and the installed *file* (`commit.toml` for
|
|
342
|
+
* Gemini CLI, `commit.md` for everyone else) are not the same string. Any code
|
|
343
|
+
* that maps one to the other must go through here — a hard-coded `.md` strip
|
|
344
|
+
* silently drops every Gemini command on the floor, and because the mismatch
|
|
345
|
+
* produces a well-formed name that simply never matches, nothing errors.
|
|
346
|
+
* (XSPEC-343 R2: 30 of machine-setup's 86 proposed deletions were exactly this.)
|
|
347
|
+
*
|
|
348
|
+
* @param {string} agent - Agent identifier
|
|
349
|
+
* @returns {string} Extension including the leading dot
|
|
350
|
+
*/
|
|
351
|
+
export function getCommandFileExtension(agent) {
|
|
352
|
+
const config = AI_AGENT_PATHS[agent];
|
|
353
|
+
return config?.commandFormat === 'toml' ? '.toml' : '.md';
|
|
354
|
+
}
|
|
355
|
+
|
|
316
356
|
/**
|
|
317
357
|
* Get all agents that support skills installation
|
|
318
358
|
* @returns {string[]} Array of agent identifiers
|
package/src/core/constants.js
CHANGED
|
@@ -523,4 +523,64 @@ export default {
|
|
|
523
523
|
doesToolSupport,
|
|
524
524
|
getToolFormat,
|
|
525
525
|
getToolFileName
|
|
526
|
-
};
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* `manifest.integrations` has been written in two shapes by two code paths:
|
|
530
|
+
* tool keys (`claude-code`) and file paths (`CLAUDE.md`, or even absolute).
|
|
531
|
+
* Three consumers read it with two different assumptions —
|
|
532
|
+
* desired-state-calculator expects tool keys, integration-uninstaller expects
|
|
533
|
+
* file names — so normalising to one shape fixes one and breaks the other.
|
|
534
|
+
*
|
|
535
|
+
* These two resolvers let every consumer accept either shape. Measured
|
|
536
|
+
* 2026-07-30: 20 of 21 adopter repos stored file names. (XSPEC-343 R1)
|
|
537
|
+
*/
|
|
538
|
+
|
|
539
|
+
/** Entry (tool key OR file path) → tool key, or null. */
|
|
540
|
+
export function resolveToolKey(entry) {
|
|
541
|
+
if (typeof entry !== 'string' || entry.length === 0) return null;
|
|
542
|
+
if (SUPPORTED_AI_TOOLS[entry]) return entry;
|
|
543
|
+
const norm = entry.replace(/\\/g, '/');
|
|
544
|
+
for (const [key, cfg] of Object.entries(SUPPORTED_AI_TOOLS)) {
|
|
545
|
+
if (!cfg?.file) continue;
|
|
546
|
+
// Exact or path-suffix only. A bare basename match would let `MY-CLAUDE.md`
|
|
547
|
+
// or `CLAUDE.md.bak` pass as the managed integration file.
|
|
548
|
+
if (norm === cfg.file || norm.endsWith(`/${cfg.file}`)) return key;
|
|
549
|
+
}
|
|
550
|
+
return null;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/** Entry (tool key OR file path) → repo-relative integration file, or null. */
|
|
554
|
+
export function resolveIntegrationFile(entry) {
|
|
555
|
+
const key = resolveToolKey(entry);
|
|
556
|
+
return key ? SUPPORTED_AI_TOOLS[key].file : null;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* How the flat keys in `manifest.options` bind to registry option categories.
|
|
561
|
+
*
|
|
562
|
+
* The manifest stores selections flat — `{ workflow: 'github-flow',
|
|
563
|
+
* test_levels: [...] }` — while the registry nests them under a standard id and
|
|
564
|
+
* a category key, and the two names are not always the same word (`test_levels`
|
|
565
|
+
* vs `test_level`). The installer knew this mapping inline; the reconciler's
|
|
566
|
+
* desired-state calculator instead iterated `manifest.options` as if it were
|
|
567
|
+
* keyed by *standard id*, looked up a standard called `workflow`, found nothing,
|
|
568
|
+
* and skipped. It produced an empty desired option set for every adopter repo,
|
|
569
|
+
* so every installed option file diffed as "no longer in desired state" —
|
|
570
|
+
* including the ones the manifest explicitly selected. (XSPEC-343 R2)
|
|
571
|
+
*
|
|
572
|
+
* Keys with no entry here (`display_language`, `release_mode`, `coverage_model`)
|
|
573
|
+
* are behaviour settings that install no file.
|
|
574
|
+
*/
|
|
575
|
+
export const MANIFEST_OPTION_BINDINGS = [
|
|
576
|
+
{ manifestKeys: ['workflow'], standardId: 'git-workflow', categoryKey: 'workflow' },
|
|
577
|
+
{ manifestKeys: ['merge_strategy'], standardId: 'git-workflow', categoryKey: 'merge_strategy' },
|
|
578
|
+
{ manifestKeys: ['output_language', 'commit_language'], standardId: 'commit-message', categoryKey: 'output_language' },
|
|
579
|
+
{ manifestKeys: ['test_levels'], standardId: 'testing', categoryKey: 'test_level' }
|
|
580
|
+
];
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Option files are installed flat, not nested by standard/category.
|
|
584
|
+
* (`installers/standards-installer.js` copies into `.standards/options`.)
|
|
585
|
+
*/
|
|
586
|
+
export const OPTIONS_INSTALL_DIR = '.standards/options';
|