universal-dev-standards 3.5.1-beta.16 → 3.5.1-beta.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.
@@ -12,7 +12,8 @@ import {
12
12
  import {
13
13
  computeFileHash,
14
14
  compareFileHash,
15
- hasFileHashes
15
+ hasFileHashes,
16
+ compareIntegrationBlockHash
16
17
  } from '../utils/hasher.js';
17
18
  import { downloadFromGitHub, getMarketplaceSkillsInfo } from '../utils/github.js';
18
19
  import {
@@ -21,7 +22,9 @@ import {
21
22
  } from '../utils/skills-installer.js';
22
23
  import {
23
24
  getAgentConfig,
24
- getAgentDisplayName
25
+ getAgentDisplayName,
26
+ getSkillsDirForAgent,
27
+ getCommandsDirForAgent
25
28
  } from '../config/ai-agent-paths.js';
26
29
  import {
27
30
  parseReferences,
@@ -44,6 +47,12 @@ export async function checkCommand(options = {}) {
44
47
  const msg = t().commands.check;
45
48
  const common = t().commands.common;
46
49
 
50
+ // Handle --summary option (compact status for other commands)
51
+ if (options.summary) {
52
+ await displaySummary(projectPath, options);
53
+ return;
54
+ }
55
+
47
56
  console.log();
48
57
  console.log(chalk.bold(msg.title));
49
58
  console.log(chalk.gray('─'.repeat(50)));
@@ -198,6 +207,17 @@ export async function checkCommand(options = {}) {
198
207
  (fileStatus.noHash.length > 0 ? `, ${fileStatus.noHash.length} no hash` : '')));
199
208
  console.log();
200
209
 
210
+ // === Enhanced Integrity Checks (v3.3.0+) ===
211
+
212
+ // Check Skills integrity if skillHashes exist
213
+ checkSkillsIntegrity(manifest, projectPath, msg);
214
+
215
+ // Check Commands integrity if commandHashes exist
216
+ checkCommandsIntegrity(manifest, projectPath, msg);
217
+
218
+ // Check Integration blocks integrity if integrationBlockHashes exist
219
+ checkIntegrationBlocksIntegrity(manifest, projectPath, msg);
220
+
201
221
  // Handle --restore option
202
222
  if (options.restore) {
203
223
  await restoreFiles(projectPath, manifest, [...fileStatus.modified, ...fileStatus.missing]);
@@ -248,7 +268,7 @@ export async function checkCommand(options = {}) {
248
268
  const { missingSkills, missingCommands } = displaySkillsStatus(manifest, projectPath, msg);
249
269
 
250
270
  // Coverage report
251
- displayCoverageReport(manifest, msg, common);
271
+ displayCoverageReport(manifest, msg, common, projectPath);
252
272
 
253
273
  // Final status
254
274
  const allGood = fileStatus.missing.length === 0 &&
@@ -790,7 +810,7 @@ function displaySkillsStatus(manifest, projectPath, msg) {
790
810
  /**
791
811
  * Display coverage report
792
812
  */
793
- function displayCoverageReport(manifest, msg, _common) {
813
+ function displayCoverageReport(manifest, msg, _common, projectPath) {
794
814
  console.log(chalk.cyan(msg.coverageSummary));
795
815
  const expectedStandards = getStandardsByLevel(manifest.level);
796
816
  const skillStandards = expectedStandards.filter(s => s.skillName);
@@ -800,7 +820,19 @@ function displayCoverageReport(manifest, msg, _common) {
800
820
  console.log(chalk.gray(` ${msg.withSkills.replace('{count}', skillStandards.length)}`));
801
821
  console.log(chalk.gray(` ${msg.referenceDocs.replace('{count}', refStandards.length)}`));
802
822
 
803
- const coveredBySkills = manifest.skills.installed ? skillStandards.length : 0;
823
+ // Dynamically check if any AI tool has skills installed
824
+ let hasInstalledSkills = false;
825
+ if (manifest.aiTools && manifest.aiTools.length > 0) {
826
+ for (const tool of manifest.aiTools) {
827
+ const projectSkillsInfo = getInstalledSkillsInfoForAgent(tool, 'project', projectPath);
828
+ const userSkillsInfo = getInstalledSkillsInfoForAgent(tool, 'user', projectPath);
829
+ if (projectSkillsInfo?.installed || userSkillsInfo?.installed) {
830
+ hasInstalledSkills = true;
831
+ break;
832
+ }
833
+ }
834
+ }
835
+ const coveredBySkills = hasInstalledSkills ? skillStandards.length : 0;
804
836
  const coveredByDocs = manifest.standards.length;
805
837
 
806
838
  console.log(chalk.gray(` ${msg.yourCoverage}`));
@@ -824,22 +856,6 @@ function checkIntegrationFiles(manifest, projectPath, msg) {
824
856
  let hasIssues = false;
825
857
  let checkedCount = 0;
826
858
 
827
- // Standard filename to task mapping for coverage check
828
- const STANDARD_TASK_MAPPING = {
829
- 'anti-hallucination.md': 'AI collaboration',
830
- 'commit-message.ai.yaml': 'Writing commits',
831
- 'checkin-standards.md': 'Committing code',
832
- 'logging-standards.md': 'Adding logging',
833
- 'error-code-standards.md': 'Error handling',
834
- 'testing.ai.yaml': 'Writing tests',
835
- 'versioning.md': 'Version bumping',
836
- 'changelog-standards.md': 'Updating changelog',
837
- 'code-review-checklist.md': 'Code review',
838
- 'spec-driven-development.md': 'Feature development',
839
- 'test-completeness-dimensions.md': 'Test coverage',
840
- 'git-workflow.ai.yaml': 'Git workflow'
841
- };
842
-
843
859
  for (const tool of manifest.aiTools) {
844
860
  const toolFile = getToolFilePath(tool);
845
861
  if (!toolFile) continue;
@@ -883,21 +899,19 @@ function checkIntegrationFiles(manifest, projectPath, msg) {
883
899
 
884
900
  if (isReferenced) {
885
901
  referencedStandards.push(stdFile);
886
- } else if (STANDARD_TASK_MAPPING[stdFile]) {
887
- // Only report as missing if it's a known trackable standard
902
+ } else {
903
+ // Track all installed standards from manifest
888
904
  missingStandards.push(stdFile);
889
905
  }
890
906
  }
891
907
 
892
- // Report status
893
- const totalTrackable = Object.keys(STANDARD_TASK_MAPPING).filter(s =>
894
- standardsFiles.includes(s)
895
- ).length;
908
+ // Report status - use all installed standards as the total
909
+ const totalTrackable = standardsFiles.length;
896
910
 
897
911
  if (hasStandardsIndex && missingStandards.length === 0) {
898
912
  console.log(chalk.green(` ✓ ${toolFile}:`));
899
913
  console.log(chalk.gray(` ${msg.standardsIndexPresent}`));
900
- console.log(chalk.gray(` ${msg.standardsReferenced.replace('{count}', referencedStandards.length).replace('{total}', totalTrackable || standardsFiles.length)}`));
914
+ console.log(chalk.gray(` ${msg.standardsReferenced.replace('{count}', referencedStandards.length).replace('{total}', totalTrackable)}`));
901
915
  } else if (hasStandardsIndex) {
902
916
  console.log(chalk.yellow(` ⚠ ${toolFile}:`));
903
917
  console.log(chalk.gray(` ${msg.standardsIndexPresent}`));
@@ -1054,3 +1068,416 @@ async function checkCliVersion(bundledVersion) {
1054
1068
  // Silent failure - don't disrupt main functionality
1055
1069
  }
1056
1070
  }
1071
+
1072
+ // ============================================================
1073
+ // Enhanced Integrity Check Functions (v3.3.0+)
1074
+ // ============================================================
1075
+
1076
+ /**
1077
+ * Check Skills files integrity against stored hashes
1078
+ * @param {Object} manifest - Manifest object
1079
+ * @param {string} projectPath - Project root path
1080
+ * @param {Object} msg - Localized messages
1081
+ * @returns {Object} Status { unchanged: [], modified: [], missing: [] }
1082
+ */
1083
+ function checkSkillsIntegrity(manifest, projectPath, msg) {
1084
+ const skillHashes = manifest.skillHashes;
1085
+
1086
+ // Skip if no skill hashes tracked
1087
+ if (!skillHashes || Object.keys(skillHashes).length === 0) {
1088
+ return { unchanged: [], modified: [], missing: [], tracked: false };
1089
+ }
1090
+
1091
+ console.log(chalk.cyan(msg.skillsIntegrityCheck || 'Skills File Integrity'));
1092
+
1093
+ const status = { unchanged: [], modified: [], missing: [], tracked: true };
1094
+
1095
+ for (const [hashKey, hashInfo] of Object.entries(skillHashes)) {
1096
+ // Parse key format: agent/level/skillName/filename
1097
+ const keyParts = hashKey.split('/');
1098
+ if (keyParts.length < 3) continue;
1099
+
1100
+ const [agent, level] = keyParts;
1101
+ const relativePath = keyParts.slice(2).join('/');
1102
+
1103
+ // Get actual file path
1104
+ const skillsDir = getSkillsDirForAgent(agent, level, projectPath);
1105
+ if (!skillsDir) {
1106
+ status.missing.push(hashKey);
1107
+ continue;
1108
+ }
1109
+
1110
+ const fullPath = join(skillsDir, relativePath);
1111
+
1112
+ if (!existsSync(fullPath)) {
1113
+ status.missing.push(hashKey);
1114
+ console.log(chalk.red(` ✗ ${hashKey} (${msg.missing || 'missing'})`));
1115
+ continue;
1116
+ }
1117
+
1118
+ // Compare hash
1119
+ const currentHash = computeFileHash(fullPath);
1120
+ if (!currentHash) {
1121
+ status.missing.push(hashKey);
1122
+ continue;
1123
+ }
1124
+
1125
+ if (currentHash.hash === hashInfo.hash && currentHash.size === hashInfo.size) {
1126
+ status.unchanged.push(hashKey);
1127
+ } else {
1128
+ status.modified.push(hashKey);
1129
+ console.log(chalk.yellow(` ⚠ ${hashKey} (${msg.modified || 'modified'})`));
1130
+ }
1131
+ }
1132
+
1133
+ // Summary
1134
+ if (status.modified.length === 0 && status.missing.length === 0) {
1135
+ console.log(chalk.green(` ✓ ${msg.allSkillsIntact || 'All skill files intact'} (${status.unchanged.length} files)`));
1136
+ } else {
1137
+ console.log(chalk.gray(` ${(msg.skillsIntegritySummary || '{unchanged} unchanged, {modified} modified, {missing} missing')
1138
+ .replace('{unchanged}', status.unchanged.length)
1139
+ .replace('{modified}', status.modified.length)
1140
+ .replace('{missing}', status.missing.length)}`));
1141
+ }
1142
+
1143
+ console.log();
1144
+ return status;
1145
+ }
1146
+
1147
+ /**
1148
+ * Check Commands files integrity against stored hashes
1149
+ * @param {Object} manifest - Manifest object
1150
+ * @param {string} projectPath - Project root path
1151
+ * @param {Object} msg - Localized messages
1152
+ * @returns {Object} Status { unchanged: [], modified: [], missing: [] }
1153
+ */
1154
+ function checkCommandsIntegrity(manifest, projectPath, msg) {
1155
+ const commandHashes = manifest.commandHashes;
1156
+
1157
+ // Skip if no command hashes tracked
1158
+ if (!commandHashes || Object.keys(commandHashes).length === 0) {
1159
+ return { unchanged: [], modified: [], missing: [], tracked: false };
1160
+ }
1161
+
1162
+ console.log(chalk.cyan(msg.commandsIntegrityCheck || 'Commands File Integrity'));
1163
+
1164
+ const status = { unchanged: [], modified: [], missing: [], tracked: true };
1165
+
1166
+ for (const [hashKey, hashInfo] of Object.entries(commandHashes)) {
1167
+ // Parse key format: agent/filename.md
1168
+ const keyParts = hashKey.split('/');
1169
+ if (keyParts.length < 2) continue;
1170
+
1171
+ const agent = keyParts[0];
1172
+ const filename = keyParts.slice(1).join('/');
1173
+
1174
+ // Get actual file path
1175
+ const commandsDir = getCommandsDirForAgent(agent, projectPath);
1176
+ if (!commandsDir) {
1177
+ status.missing.push(hashKey);
1178
+ continue;
1179
+ }
1180
+
1181
+ const fullPath = join(commandsDir, filename);
1182
+
1183
+ if (!existsSync(fullPath)) {
1184
+ status.missing.push(hashKey);
1185
+ console.log(chalk.red(` ✗ ${hashKey} (${msg.missing || 'missing'})`));
1186
+ continue;
1187
+ }
1188
+
1189
+ // Compare hash
1190
+ const currentHash = computeFileHash(fullPath);
1191
+ if (!currentHash) {
1192
+ status.missing.push(hashKey);
1193
+ continue;
1194
+ }
1195
+
1196
+ if (currentHash.hash === hashInfo.hash && currentHash.size === hashInfo.size) {
1197
+ status.unchanged.push(hashKey);
1198
+ } else {
1199
+ status.modified.push(hashKey);
1200
+ console.log(chalk.yellow(` ⚠ ${hashKey} (${msg.modified || 'modified'})`));
1201
+ }
1202
+ }
1203
+
1204
+ // Summary
1205
+ if (status.modified.length === 0 && status.missing.length === 0) {
1206
+ console.log(chalk.green(` ✓ ${msg.allCommandsIntact || 'All command files intact'} (${status.unchanged.length} files)`));
1207
+ } else {
1208
+ console.log(chalk.gray(` ${(msg.commandsIntegritySummary || '{unchanged} unchanged, {modified} modified, {missing} missing')
1209
+ .replace('{unchanged}', status.unchanged.length)
1210
+ .replace('{modified}', status.modified.length)
1211
+ .replace('{missing}', status.missing.length)}`));
1212
+ }
1213
+
1214
+ console.log();
1215
+ return status;
1216
+ }
1217
+
1218
+ /**
1219
+ * Check Integration files' UDS block integrity against stored hashes
1220
+ * Only checks the UDS marker block content, not user customizations outside the block
1221
+ * @param {Object} manifest - Manifest object
1222
+ * @param {string} projectPath - Project root path
1223
+ * @param {Object} msg - Localized messages
1224
+ * @returns {Object} Status { unchanged: [], modified: [], missing: [], noMarkers: [] }
1225
+ */
1226
+ function checkIntegrationBlocksIntegrity(manifest, projectPath, msg) {
1227
+ const blockHashes = manifest.integrationBlockHashes;
1228
+
1229
+ // Skip if no block hashes tracked
1230
+ if (!blockHashes || Object.keys(blockHashes).length === 0) {
1231
+ return { unchanged: [], modified: [], missing: [], noMarkers: [], tracked: false };
1232
+ }
1233
+
1234
+ console.log(chalk.cyan(msg.integrationBlocksCheck || 'Integration UDS Block Integrity'));
1235
+
1236
+ const status = { unchanged: [], modified: [], missing: [], noMarkers: [], tracked: true };
1237
+
1238
+ for (const [filePath, hashInfo] of Object.entries(blockHashes)) {
1239
+ const fullPath = join(projectPath, filePath);
1240
+
1241
+ if (!existsSync(fullPath)) {
1242
+ status.missing.push(filePath);
1243
+ console.log(chalk.red(` ✗ ${filePath} (${msg.missing || 'missing'})`));
1244
+ continue;
1245
+ }
1246
+
1247
+ // Compare block hash
1248
+ const blockStatus = compareIntegrationBlockHash(fullPath, hashInfo);
1249
+
1250
+ switch (blockStatus) {
1251
+ case 'unchanged':
1252
+ status.unchanged.push(filePath);
1253
+ break;
1254
+ case 'modified':
1255
+ status.modified.push(filePath);
1256
+ console.log(chalk.yellow(` ⚠ ${filePath} (${msg.udsBlockModified || 'UDS block modified'})`));
1257
+ break;
1258
+ case 'no_markers':
1259
+ status.noMarkers.push(filePath);
1260
+ console.log(chalk.red(` ✗ ${filePath} (${msg.udsMarkersRemoved || 'UDS markers removed'})`));
1261
+ break;
1262
+ case 'missing':
1263
+ status.missing.push(filePath);
1264
+ console.log(chalk.red(` ✗ ${filePath} (${msg.missing || 'missing'})`));
1265
+ break;
1266
+ }
1267
+ }
1268
+
1269
+ // Summary
1270
+ if (status.modified.length === 0 && status.missing.length === 0 && status.noMarkers.length === 0) {
1271
+ console.log(chalk.green(` ✓ ${msg.allBlocksIntact || 'All UDS blocks intact'} (${status.unchanged.length} files)`));
1272
+ console.log(chalk.gray(` ${msg.userContentPreserved || 'User customizations outside UDS blocks are preserved'}`));
1273
+ } else {
1274
+ console.log(chalk.gray(` ${(msg.blocksIntegritySummary || '{unchanged} intact, {modified} modified, {missing} missing')
1275
+ .replace('{unchanged}', status.unchanged.length)
1276
+ .replace('{modified}', status.modified.length)
1277
+ .replace('{missing}', status.missing.length + status.noMarkers.length)}`));
1278
+
1279
+ if (status.modified.length > 0 || status.noMarkers.length > 0) {
1280
+ console.log(chalk.yellow(` ${msg.runUpdateIntegrations || 'Run "uds update --integrations-only" to restore UDS content'}`));
1281
+ }
1282
+ }
1283
+
1284
+ console.log();
1285
+ return status;
1286
+ }
1287
+
1288
+ // ============================================================
1289
+ // Summary Mode (--summary)
1290
+ // ============================================================
1291
+
1292
+ /**
1293
+ * Display compact status summary for use by other commands
1294
+ * @param {string} projectPath - Project root path
1295
+ * @param {Object} options - Command options
1296
+ */
1297
+ async function displaySummary(projectPath, _options = {}) {
1298
+ const msg = t().commands.check;
1299
+ const common = t().commands.common;
1300
+ const summaryMsg = msg.summary_mode || {};
1301
+
1302
+ console.log();
1303
+ console.log(chalk.bold(summaryMsg.title || 'UDS Status Summary'));
1304
+ console.log(chalk.gray('─'.repeat(50)));
1305
+
1306
+ // Check if initialized
1307
+ if (!isInitialized(projectPath)) {
1308
+ console.log(chalk.red(` ${summaryMsg.notInitialized || 'Not initialized'}`));
1309
+ console.log(chalk.gray(` ${common.runInit}`));
1310
+ console.log(chalk.gray('─'.repeat(50)));
1311
+ console.log();
1312
+ return;
1313
+ }
1314
+
1315
+ // Read manifest
1316
+ const manifest = readManifest(projectPath);
1317
+ if (!manifest) {
1318
+ console.log(chalk.red(` ${summaryMsg.manifestError || 'Manifest error'}`));
1319
+ console.log(chalk.gray('─'.repeat(50)));
1320
+ console.log();
1321
+ return;
1322
+ }
1323
+
1324
+ const levelInfo = getLevelInfo(manifest.level);
1325
+ const repoInfo = getRepositoryInfo();
1326
+ const lang = getLanguage();
1327
+
1328
+ // === Row 1: Version ===
1329
+ const currentVersion = manifest.upstream.version;
1330
+ const latestVersion = repoInfo.standards.version;
1331
+ const hasUpdate = currentVersion !== latestVersion;
1332
+
1333
+ if (hasUpdate) {
1334
+ console.log(chalk.yellow(` ${summaryMsg.version || 'Version'}: ${currentVersion} → ${latestVersion} ⚠`));
1335
+ } else {
1336
+ console.log(chalk.green(` ${summaryMsg.version || 'Version'}: ${currentVersion} ✓`));
1337
+ }
1338
+
1339
+ // === Row 2: Level ===
1340
+ const zhName = lang === 'zh-cn' ? levelInfo.nameZhCn : levelInfo.nameZh;
1341
+ const levelDisplay = lang === 'en'
1342
+ ? `${manifest.level} - ${levelInfo.name}`
1343
+ : `${manifest.level} - ${levelInfo.name} (${zhName})`;
1344
+ console.log(chalk.gray(` ${summaryMsg.level || 'Level'}: ${levelDisplay}`));
1345
+
1346
+ // === Row 3: Files Status ===
1347
+ const fileStatus = getFileStatusCounts(manifest, projectPath);
1348
+ const filesOk = fileStatus.modified === 0 && fileStatus.missing === 0;
1349
+ const filesDisplay = filesOk
1350
+ ? chalk.green(`${fileStatus.unchanged} ✓`)
1351
+ : `${chalk.green(fileStatus.unchanged + ' ✓')} ${chalk.yellow('| ' + fileStatus.modified + ' modified')} ${chalk.red('| ' + fileStatus.missing + ' missing')}`;
1352
+ console.log(` ${summaryMsg.files || 'Files'}: ${filesDisplay}`);
1353
+
1354
+ // === Row 4: Skills Status ===
1355
+ const aiTools = manifest.aiTools || [];
1356
+ if (aiTools.length > 0) {
1357
+ const skillsStatus = getSkillsStatusSummary(manifest, projectPath);
1358
+ console.log(` ${summaryMsg.skills || 'Skills'}: ${skillsStatus}`);
1359
+ }
1360
+
1361
+ // === Row 5: Commands Status (if applicable) ===
1362
+ const commandsStatus = getCommandsStatusSummary(manifest, projectPath);
1363
+ if (commandsStatus) {
1364
+ console.log(` ${summaryMsg.commands || 'Commands'}: ${commandsStatus}`);
1365
+ }
1366
+
1367
+ console.log(chalk.gray('─'.repeat(50)));
1368
+ console.log();
1369
+ }
1370
+
1371
+ /**
1372
+ * Get file status counts without logging
1373
+ * @param {Object} manifest - Manifest object
1374
+ * @param {string} projectPath - Project root path
1375
+ * @returns {{unchanged: number, modified: number, missing: number}}
1376
+ */
1377
+ function getFileStatusCounts(manifest, projectPath) {
1378
+ const counts = { unchanged: 0, modified: 0, missing: 0 };
1379
+
1380
+ if (hasFileHashes(manifest)) {
1381
+ for (const [relativePath, hashInfo] of Object.entries(manifest.fileHashes)) {
1382
+ const fullPath = join(projectPath, relativePath);
1383
+ const status = compareFileHash(fullPath, hashInfo);
1384
+
1385
+ switch (status) {
1386
+ case 'unchanged':
1387
+ counts.unchanged++;
1388
+ break;
1389
+ case 'modified':
1390
+ counts.modified++;
1391
+ break;
1392
+ case 'missing':
1393
+ counts.missing++;
1394
+ break;
1395
+ }
1396
+ }
1397
+ } else {
1398
+ // Legacy manifest - existence check only
1399
+ const allFiles = [
1400
+ ...manifest.standards.map(s => `.standards/${basename(s)}`),
1401
+ ...manifest.extensions.map(e => `.standards/${basename(e)}`),
1402
+ ...manifest.integrations
1403
+ ];
1404
+ for (const relativePath of allFiles) {
1405
+ const fullPath = join(projectPath, relativePath);
1406
+ if (existsSync(fullPath)) {
1407
+ counts.unchanged++;
1408
+ } else {
1409
+ counts.missing++;
1410
+ }
1411
+ }
1412
+ }
1413
+
1414
+ return counts;
1415
+ }
1416
+
1417
+ /**
1418
+ * Get skills status summary string
1419
+ * @param {Object} manifest - Manifest object
1420
+ * @param {string} projectPath - Project root path
1421
+ * @returns {string} Formatted skills status
1422
+ */
1423
+ function getSkillsStatusSummary(manifest, projectPath) {
1424
+ const aiTools = manifest.aiTools || [];
1425
+ const parts = [];
1426
+
1427
+ // Check for Marketplace installation (Claude Code specific)
1428
+ const location = manifest.skills?.location || '';
1429
+ const isMarketplace = location === 'marketplace' ||
1430
+ location.includes('plugins/cache') ||
1431
+ location.includes('plugins\\cache');
1432
+
1433
+ for (const tool of aiTools) {
1434
+ const config = getAgentConfig(tool);
1435
+ if (!config || !config.supportsSkills) continue;
1436
+
1437
+ const displayName = getAgentDisplayName(tool);
1438
+ const usingMarketplace = isMarketplace && tool === 'claude-code';
1439
+
1440
+ if (usingMarketplace) {
1441
+ parts.push(chalk.green(`${displayName} ✓`));
1442
+ continue;
1443
+ }
1444
+
1445
+ const projectSkillsInfo = getInstalledSkillsInfoForAgent(tool, 'project', projectPath);
1446
+ const userSkillsInfo = getInstalledSkillsInfoForAgent(tool, 'user', projectPath);
1447
+
1448
+ if (projectSkillsInfo?.installed || userSkillsInfo?.installed) {
1449
+ parts.push(chalk.green(`${displayName} ✓`));
1450
+ } else {
1451
+ parts.push(chalk.gray(`${displayName} ○`));
1452
+ }
1453
+ }
1454
+
1455
+ return parts.join(' | ') || chalk.gray('None configured');
1456
+ }
1457
+
1458
+ /**
1459
+ * Get commands status summary string
1460
+ * @param {Object} manifest - Manifest object
1461
+ * @param {string} projectPath - Project root path
1462
+ * @returns {string|null} Formatted commands status or null if no tools support commands
1463
+ */
1464
+ function getCommandsStatusSummary(manifest, projectPath) {
1465
+ const aiTools = manifest.aiTools || [];
1466
+ const parts = [];
1467
+
1468
+ for (const tool of aiTools) {
1469
+ const config = getAgentConfig(tool);
1470
+ if (!config || !config.commands) continue;
1471
+
1472
+ const displayName = getAgentDisplayName(tool);
1473
+ const commandsInfo = getInstalledCommandsForAgent(tool, projectPath);
1474
+
1475
+ if (commandsInfo?.installed) {
1476
+ parts.push(chalk.green(`${displayName} ✓`));
1477
+ } else {
1478
+ parts.push(chalk.gray(`${displayName} ○`));
1479
+ }
1480
+ }
1481
+
1482
+ return parts.length > 0 ? parts.join(' | ') : null;
1483
+ }
@@ -45,6 +45,7 @@ import {
45
45
  getToolFilePath
46
46
  } from '../utils/integration-generator.js';
47
47
  import { t } from '../i18n/messages.js';
48
+ import { regenerateIntegrations } from './update.js';
48
49
 
49
50
  /**
50
51
  * Configure command - modify options for initialized project
@@ -263,11 +264,13 @@ export async function configureCommand(options) {
263
264
  }
264
265
  console.log();
265
266
 
266
- // Confirm
267
- const confirmed = await promptConfirm(msg.applyChanges);
268
- if (!confirmed) {
269
- console.log(chalk.yellow(msg.configCancelled));
270
- process.exit(0);
267
+ // Confirm (skip if --yes flag is provided)
268
+ if (!options.yes) {
269
+ const confirmed = await promptConfirm(msg.applyChanges);
270
+ if (!confirmed) {
271
+ console.log(chalk.yellow(msg.configCancelled));
272
+ process.exit(0);
273
+ }
271
274
  }
272
275
 
273
276
  // Apply changes
@@ -440,6 +443,62 @@ export async function configureCommand(options) {
440
443
  }
441
444
  }
442
445
 
446
+ // Smart apply: offer to regenerate integrations if config changed but not already regenerated
447
+ // Skip for types that have their own flow or don't affect integrations
448
+ const skipApplyTypes = ['skills', 'commands', 'methodology'];
449
+ const alreadyRegenerated = results.generated.length > 0;
450
+ const shouldOfferApply = !skipApplyTypes.includes(configType) &&
451
+ newAITools.length > 0 &&
452
+ !alreadyRegenerated;
453
+
454
+ if (shouldOfferApply) {
455
+ console.log();
456
+
457
+ if (options.yes) {
458
+ // --yes flag: auto-apply without prompting
459
+ const applySpinner = ora(msg.applyingChanges).start();
460
+ const applyResults = regenerateIntegrations(projectPath, manifest);
461
+ applySpinner.succeed(msg.changesApplied || msg.regeneratedIntegrations.replace('{count}', applyResults.updated.length));
462
+
463
+ // Update manifest with new file hashes
464
+ writeManifest(manifest, projectPath);
465
+
466
+ if (applyResults.errors.length > 0) {
467
+ console.log(chalk.yellow(msg.errorsOccurred.replace('{count}', applyResults.errors.length)));
468
+ for (const err of applyResults.errors) {
469
+ console.log(chalk.gray(` ${err}`));
470
+ }
471
+ }
472
+ } else {
473
+ // Interactive mode: prompt user
474
+ const inquirer = await import('inquirer');
475
+ const { apply } = await inquirer.default.prompt([{
476
+ type: 'confirm',
477
+ name: 'apply',
478
+ message: msg.applyChangesNow,
479
+ default: true
480
+ }]);
481
+
482
+ if (apply) {
483
+ const applySpinner = ora(msg.applyingChanges).start();
484
+ const applyResults = regenerateIntegrations(projectPath, manifest);
485
+ applySpinner.succeed(msg.changesApplied || msg.regeneratedIntegrations.replace('{count}', applyResults.updated.length));
486
+
487
+ // Update manifest with new file hashes
488
+ writeManifest(manifest, projectPath);
489
+
490
+ if (applyResults.errors.length > 0) {
491
+ console.log(chalk.yellow(msg.errorsOccurred.replace('{count}', applyResults.errors.length)));
492
+ for (const err of applyResults.errors) {
493
+ console.log(chalk.gray(` ${err}`));
494
+ }
495
+ }
496
+ } else {
497
+ console.log(chalk.gray(msg.runUpdateLater));
498
+ }
499
+ }
500
+ }
501
+
443
502
  console.log();
444
503
 
445
504
  // Exit explicitly to prevent hanging due to inquirer's readline interface