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.
- package/bin/uds.js +2 -0
- package/bundled/core/checkin-standards.md +39 -2
- package/bundled/locales/zh-CN/core/checkin-standards.md +42 -5
- package/bundled/locales/zh-TW/core/checkin-standards.md +42 -5
- package/bundled/skills/claude-code/commands/check.md +18 -0
- package/bundled/skills/claude-code/commands/config.md +14 -0
- package/bundled/skills/claude-code/commands/update.md +9 -1
- package/package.json +2 -1
- package/src/commands/check.js +455 -28
- package/src/commands/configure.js +64 -5
- package/src/commands/init.js +29 -3
- package/src/commands/update.js +227 -49
- package/src/i18n/messages.js +54 -6
- package/src/utils/hasher.js +193 -0
- package/src/utils/integration-generator.js +11 -1
- package/src/utils/skills-installer.js +39 -4
- package/standards-registry.json +3 -3
package/src/commands/check.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
887
|
-
//
|
|
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 =
|
|
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
|
|
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
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|