universal-dev-standards 6.9.0 → 6.10.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/core/agent-communication-protocol.md +8 -0
- package/bundled/core/branch-completion.md +8 -0
- package/bundled/core/change-batching-standards.md +8 -0
- package/bundled/core/execution-history.md +8 -0
- package/bundled/core/pipeline-integration-standards.md +8 -0
- package/bundled/core/workflow-enforcement.md +8 -0
- package/bundled/core/workflow-state-protocol.md +8 -0
- package/bundled/locales/zh-CN/CHANGELOG.md +29 -3
- package/bundled/locales/zh-CN/README.md +1 -1
- package/bundled/locales/zh-CN/SECURITY.md +1 -1
- package/bundled/locales/zh-CN/core/agent-communication-protocol.md +7 -0
- package/bundled/locales/zh-CN/core/branch-completion.md +7 -0
- package/bundled/locales/zh-CN/core/change-batching-standards.md +7 -0
- package/bundled/locales/zh-CN/core/execution-history.md +7 -0
- package/bundled/locales/zh-CN/core/pipeline-integration-standards.md +7 -0
- package/bundled/locales/zh-CN/core/workflow-enforcement.md +7 -0
- package/bundled/locales/zh-CN/core/workflow-state-protocol.md +7 -0
- package/bundled/locales/zh-CN/docs/MIGRATION-v6.md +8 -4
- package/bundled/locales/zh-TW/CHANGELOG.md +30 -3
- package/bundled/locales/zh-TW/README.md +1 -1
- package/bundled/locales/zh-TW/SECURITY.md +1 -1
- package/bundled/locales/zh-TW/core/agent-communication-protocol.md +7 -0
- package/bundled/locales/zh-TW/core/branch-completion.md +7 -0
- package/bundled/locales/zh-TW/core/change-batching-standards.md +7 -0
- package/bundled/locales/zh-TW/core/execution-history.md +7 -0
- package/bundled/locales/zh-TW/core/pipeline-integration-standards.md +7 -0
- package/bundled/locales/zh-TW/core/workflow-enforcement.md +7 -0
- package/bundled/locales/zh-TW/core/workflow-state-protocol.md +7 -0
- package/bundled/locales/zh-TW/docs/MIGRATION-v6.md +8 -4
- package/package.json +7 -6
- package/src/commands/check.js +161 -24
- package/src/commands/config.js +19 -19
- package/src/commands/init.js +13 -4
- package/src/commands/spec.js +2 -2
- package/src/commands/update.js +251 -29
- package/src/i18n/messages.js +36 -0
- package/src/installers/integration-installer.js +4 -4
- package/src/installers/skills-installer.js +4 -4
- package/src/installers/standards-installer.js +3 -3
- package/src/prompts/init.js +33 -4
- package/src/reconciler/diff-engine.js +57 -2
- package/src/reconciler/plan-executor.js +31 -11
- package/src/utils/integration-generator.js +209 -81
- package/src/utils/reference-sync.js +103 -7
- package/src/utils/registry.js +57 -0
- package/src/utils/spinner.js +31 -0
- package/standards-registry.json +8 -8
package/src/commands/check.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { select } from '@inquirer/prompts';
|
|
3
|
-
import
|
|
3
|
+
import { createSpinner } from '../utils/spinner.js';
|
|
4
4
|
import { existsSync, readFileSync } from 'fs';
|
|
5
5
|
import { join, basename } from 'path';
|
|
6
6
|
import { execSync } from 'child_process';
|
|
7
7
|
import { readManifest, writeManifest, isInitialized, copyStandard, copyIntegration } from '../utils/copier.js';
|
|
8
8
|
import {
|
|
9
9
|
getAllStandards,
|
|
10
|
-
getRepositoryInfo, resolveStandardFilename, resolveStandardSourcePath } from '../utils/registry.js';
|
|
10
|
+
getRepositoryInfo, isShippedFilename, resolveStandardFilename, resolveStandardSourcePath } from '../utils/registry.js';
|
|
11
11
|
import {
|
|
12
12
|
computeFileHash,
|
|
13
13
|
compareFileHash,
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
} from '../config/ai-agent-paths.js';
|
|
28
28
|
import {
|
|
29
29
|
parseReferences,
|
|
30
|
+
findBrokenPathMentions,
|
|
30
31
|
compareStandardsWithReferences
|
|
31
32
|
} from '../utils/reference-sync.js';
|
|
32
33
|
import { extractMarkedContent, getToolFilePath, parseStandardsIndexCount, writeIntegrationFile } from '../utils/integration-generator.js';
|
|
@@ -60,6 +61,19 @@ function displayFileIntegritySummary(fileStatus, msg) {
|
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
// Reported apart from `missing`, and in grey rather than red, because the
|
|
65
|
+
// adopter who sees this did what the migration guide asked. The remedy named
|
|
66
|
+
// here is the one that works: `--restore` cannot resolve a source for any of
|
|
67
|
+
// them, and used to be the only thing offered.
|
|
68
|
+
if (fileStatus.retired?.length > 0) {
|
|
69
|
+
console.log();
|
|
70
|
+
console.log(chalk.gray(` ${(msg.retiredHeader || '{count} tracked file(s) are no longer shipped by UDS:').replace('{count}', fileStatus.retired.length)}`));
|
|
71
|
+
for (const file of fileStatus.retired) {
|
|
72
|
+
console.log(chalk.gray(` - ${file} (${msg.retired || 'no longer shipped by UDS'})`));
|
|
73
|
+
}
|
|
74
|
+
console.log(chalk.gray(` ${msg.retiredHint || 'Run `uds update --prune` to drop their manifest entries.'}`));
|
|
75
|
+
}
|
|
76
|
+
|
|
63
77
|
if (fileStatus.noHash.length > 0) {
|
|
64
78
|
for (const file of fileStatus.noHash) {
|
|
65
79
|
console.log(chalk.gray(` ? ${file} (${msg.existsNoHash})`));
|
|
@@ -71,10 +85,39 @@ function displayFileIntegritySummary(fileStatus, msg) {
|
|
|
71
85
|
.replace('{unchanged}', fileStatus.unchanged.length)
|
|
72
86
|
.replace('{modified}', fileStatus.modified.length)
|
|
73
87
|
.replace('{missing}', fileStatus.missing.length)}` +
|
|
88
|
+
(fileStatus.retired?.length > 0 ? `, ${fileStatus.retired.length} ${msg.retired || 'no longer shipped'}` : '') +
|
|
74
89
|
(fileStatus.noHash.length > 0 ? `, ${fileStatus.noHash.length} no hash` : '')));
|
|
75
90
|
console.log();
|
|
76
91
|
}
|
|
77
92
|
|
|
93
|
+
/**
|
|
94
|
+
* A file `fileHashes` tracks has gone. Is that a problem, or is it the migration
|
|
95
|
+
* guide's instruction carried out?
|
|
96
|
+
*
|
|
97
|
+
* MIGRATION-v6 §2 told adopters to delete the seven descoped `.ai.yaml` copies
|
|
98
|
+
* by hand. Doing so left the hash entries behind, and this check called each one
|
|
99
|
+
* `missing` and offered `uds check --restore` — a restore that then failed on
|
|
100
|
+
* every one with "could not determine source", because there has been no source
|
|
101
|
+
* upstream since 6.0.0. The tool asked the user to undo its own documentation,
|
|
102
|
+
* using a command it already knew could not work.
|
|
103
|
+
*
|
|
104
|
+
* The distinguishing question is whether UDS still ships a file by that name,
|
|
105
|
+
* which is the registry's to answer, not the manifest's. Anything outside
|
|
106
|
+
* `.standards/` is never retired: integration files are generated, not shipped
|
|
107
|
+
* under a standard's name, so a missing CLAUDE.md is exactly what it looks like.
|
|
108
|
+
*
|
|
109
|
+
* @param {string} relativePath - Path as recorded in fileHashes
|
|
110
|
+
* @param {Object} manifest - Project manifest (unused today; kept so callers
|
|
111
|
+
* need not know whether the answer depends on project state)
|
|
112
|
+
* @returns {'missing'|'retired'}
|
|
113
|
+
*/
|
|
114
|
+
export function classifyMissingFile(relativePath, manifest) { // eslint-disable-line no-unused-vars
|
|
115
|
+
const normalized = String(relativePath).replace(/\\/g, '/');
|
|
116
|
+
if (!normalized.startsWith('.standards/')) return 'missing';
|
|
117
|
+
const fileName = normalized.split('/').pop();
|
|
118
|
+
return isShippedFilename(fileName) ? 'missing' : 'retired';
|
|
119
|
+
}
|
|
120
|
+
|
|
78
121
|
/**
|
|
79
122
|
* Perform integrity check for standards and integration files
|
|
80
123
|
* @returns {Object} File status object
|
|
@@ -84,6 +127,10 @@ function performFileIntegrityCheck(projectPath, manifest, msg) {
|
|
|
84
127
|
unchanged: [],
|
|
85
128
|
modified: [],
|
|
86
129
|
missing: [],
|
|
130
|
+
// Tracked, gone, and gone on purpose: UDS stopped shipping it. Counted
|
|
131
|
+
// apart from `missing` so the summary line stops calling a completed
|
|
132
|
+
// migration a fault.
|
|
133
|
+
retired: [],
|
|
87
134
|
noHash: []
|
|
88
135
|
};
|
|
89
136
|
|
|
@@ -101,7 +148,7 @@ function performFileIntegrityCheck(projectPath, manifest, msg) {
|
|
|
101
148
|
fileStatus.modified.push(relativePath);
|
|
102
149
|
break;
|
|
103
150
|
case 'missing':
|
|
104
|
-
fileStatus.
|
|
151
|
+
fileStatus[classifyMissingFile(relativePath, manifest)].push(relativePath);
|
|
105
152
|
break;
|
|
106
153
|
}
|
|
107
154
|
}
|
|
@@ -1151,19 +1198,37 @@ function checkIntegrationFiles(manifest, projectPath, msg) {
|
|
|
1151
1198
|
// After migrateStandardsPathsToIds(), manifest.standards contains IDs, so a plain
|
|
1152
1199
|
// content.includes(id) check would fail for these mismatched entries.
|
|
1153
1200
|
const allRegistryStds = getAllStandards();
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1201
|
+
// 🔴 `source` is a string for some entries and an object for others. Reading
|
|
1202
|
+
// `s.source.ai` on a string yields undefined, so `zh-tw-locale`
|
|
1203
|
+
// (source: 'extensions/locales/zh-tw.md') fell out of this map and the file it
|
|
1204
|
+
// installs — `.standards/zh-tw.md` — was never recognised: "67/68 項標準已參考,
|
|
1205
|
+
// 缺少: zh-tw-locale" on a project whose index lists it (reported 2026-09-16).
|
|
1206
|
+
const idToFilenames = new Map(
|
|
1207
|
+
allRegistryStds.map(s => {
|
|
1208
|
+
const sources = typeof s.source === 'string'
|
|
1209
|
+
? [s.source]
|
|
1210
|
+
: [s.source?.ai, s.source?.human].filter(Boolean);
|
|
1211
|
+
return [s.id, sources.map(src => basename(src))];
|
|
1212
|
+
})
|
|
1158
1213
|
);
|
|
1159
1214
|
|
|
1160
1215
|
let hasIssues = false;
|
|
1161
1216
|
let checkedCount = 0;
|
|
1162
1217
|
|
|
1218
|
+
// codex and opencode both write AGENTS.md, so iterating tools printed the same
|
|
1219
|
+
// file twice with identical verdicts (reported 2026-09-16). One file, one
|
|
1220
|
+
// verdict — the tools that share it are named on the line instead.
|
|
1221
|
+
const toolsByFile = new Map();
|
|
1163
1222
|
for (const tool of manifest.aiTools) {
|
|
1164
|
-
const
|
|
1165
|
-
if (!
|
|
1223
|
+
const file = getToolFilePath(tool);
|
|
1224
|
+
if (!file) continue;
|
|
1225
|
+
if (!toolsByFile.has(file)) toolsByFile.set(file, []);
|
|
1226
|
+
toolsByFile.get(file).push(tool);
|
|
1227
|
+
}
|
|
1166
1228
|
|
|
1229
|
+
for (const [toolFile, sharingTools] of toolsByFile) {
|
|
1230
|
+
const tool = sharingTools[0];
|
|
1231
|
+
const sharedSuffix = sharingTools.length > 1 ? ` (${sharingTools.join(', ')})` : '';
|
|
1167
1232
|
const fullPath = join(projectPath, toolFile);
|
|
1168
1233
|
|
|
1169
1234
|
// Check if file exists
|
|
@@ -1201,9 +1266,9 @@ function checkIntegrationFiles(manifest, projectPath, msg) {
|
|
|
1201
1266
|
// migration. For standards where the ID doesn't match the .ai.yaml basename
|
|
1202
1267
|
// (e.g. ID "error-code-standards" → file "error-codes.ai.yaml"), we must
|
|
1203
1268
|
// also check the actual filename so those aren't falsely reported as missing.
|
|
1204
|
-
const
|
|
1269
|
+
const filenames = idToFilenames.get(stdFile) || [];
|
|
1205
1270
|
const isReferenced = content.includes(stdFile) ||
|
|
1206
|
-
(
|
|
1271
|
+
filenames.some(name => name !== stdFile && content.includes(name)) ||
|
|
1207
1272
|
content.includes(`.standards/${stdFile}`) ||
|
|
1208
1273
|
content.includes(`standards/${stdFile}`);
|
|
1209
1274
|
|
|
@@ -1224,7 +1289,7 @@ function checkIntegrationFiles(manifest, projectPath, msg) {
|
|
|
1224
1289
|
const declaredCount = parseStandardsIndexCount(content);
|
|
1225
1290
|
if (declaredCount !== null) {
|
|
1226
1291
|
if (declaredCount === totalTrackable) {
|
|
1227
|
-
console.log(chalk.green(` ✓ ${toolFile}:`));
|
|
1292
|
+
console.log(chalk.green(` ✓ ${toolFile}${sharedSuffix}:`));
|
|
1228
1293
|
console.log(chalk.gray(` ${msg.standardsIndexPresent}`));
|
|
1229
1294
|
console.log(chalk.gray(` ${msg.standardsIndexCount
|
|
1230
1295
|
? msg.standardsIndexCount.replace('{count}', declaredCount)
|
|
@@ -1404,9 +1469,12 @@ function checkReferenceSync(manifest, projectPath, msg) {
|
|
|
1404
1469
|
}
|
|
1405
1470
|
|
|
1406
1471
|
const references = parseReferences(content);
|
|
1407
|
-
|
|
1472
|
+
// Whole-file scan, separate from reference sync: the paths that matter here
|
|
1473
|
+
// are the ones outside the UDS block, which no generation ever revisits.
|
|
1474
|
+
const brokenMentions = findBrokenPathMentions(content, projectPath);
|
|
1475
|
+
if (references.length === 0 && brokenMentions.length === 0) continue;
|
|
1408
1476
|
|
|
1409
|
-
results.push({ integrationPath, references });
|
|
1477
|
+
results.push({ integrationPath, references, brokenMentions });
|
|
1410
1478
|
}
|
|
1411
1479
|
|
|
1412
1480
|
// Skip entire section if no files have references
|
|
@@ -1416,14 +1484,42 @@ function checkReferenceSync(manifest, projectPath, msg) {
|
|
|
1416
1484
|
|
|
1417
1485
|
let hasIssues = false;
|
|
1418
1486
|
|
|
1419
|
-
for (const { integrationPath, references } of results) {
|
|
1487
|
+
for (const { integrationPath, references, brokenMentions } of results) {
|
|
1488
|
+
// Paths mentioned anywhere in the file that resolve to nothing. Reported
|
|
1489
|
+
// first and separately from reference sync, because these are usually
|
|
1490
|
+
// outside the UDS markers — left by an older `uds init`, never revisited by
|
|
1491
|
+
// any regeneration, and followed by an AI tool that then finds nothing.
|
|
1492
|
+
if (brokenMentions?.length > 0) {
|
|
1493
|
+
hasIssues = true;
|
|
1494
|
+
console.log(chalk.red(` ✗ ${integrationPath}:`));
|
|
1495
|
+
console.log(chalk.red(` ${msg.brokenMentions || 'mentions paths that do not exist in this project:'}`));
|
|
1496
|
+
for (const path of brokenMentions) {
|
|
1497
|
+
console.log(chalk.red(` - ${path}`));
|
|
1498
|
+
}
|
|
1499
|
+
console.log(chalk.gray(` ${msg.brokenMentionsFix || 'These are usually outside the UDS block and must be corrected by hand; UDS does not rewrite text it did not write.'}`));
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1420
1502
|
// Compare with manifest standards
|
|
1421
|
-
const { orphanedRefs, missingRefs, syncedRefs } = compareStandardsWithReferences(
|
|
1503
|
+
const { orphanedRefs, missingRefs, syncedRefs, danglingRefs } = compareStandardsWithReferences(
|
|
1422
1504
|
manifest.standards,
|
|
1423
|
-
references
|
|
1505
|
+
references,
|
|
1506
|
+
{ projectPath, options: manifest.options }
|
|
1424
1507
|
);
|
|
1425
1508
|
|
|
1426
1509
|
// Report results
|
|
1510
|
+
// 🔴 A reference to a file that is not on disk is the one failure an AI tool
|
|
1511
|
+
// cannot work around: it follows the link and finds nothing. Reported before
|
|
1512
|
+
// the manifest-level findings because it needs no interpretation.
|
|
1513
|
+
if (danglingRefs.length > 0) {
|
|
1514
|
+
hasIssues = true;
|
|
1515
|
+
console.log(chalk.red(` ✗ ${integrationPath}:`));
|
|
1516
|
+
console.log(chalk.red(` ${msg.danglingRefs || 'references files that do not exist:'}`));
|
|
1517
|
+
for (const ref of danglingRefs) {
|
|
1518
|
+
console.log(chalk.red(` - .standards/${ref}`));
|
|
1519
|
+
}
|
|
1520
|
+
console.log(chalk.gray(` ${msg.danglingRefsFix || 'Run `uds update --integrations-only` to regenerate them.'}`));
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1427
1523
|
if (orphanedRefs.length > 0) {
|
|
1428
1524
|
hasIssues = true;
|
|
1429
1525
|
console.log(chalk.yellow(` ⚠ ${integrationPath}:`));
|
|
@@ -1442,14 +1538,21 @@ function checkReferenceSync(manifest, projectPath, msg) {
|
|
|
1442
1538
|
}
|
|
1443
1539
|
}
|
|
1444
1540
|
|
|
1445
|
-
if (orphanedRefs.length === 0 && missingRefs.length === 0) {
|
|
1541
|
+
if (orphanedRefs.length === 0 && missingRefs.length === 0 && danglingRefs.length === 0) {
|
|
1446
1542
|
console.log(chalk.green(` ✓ ${msg.refsInSync.replace('{path}', integrationPath).replace('{count}', syncedRefs.length)}`));
|
|
1447
1543
|
}
|
|
1448
1544
|
}
|
|
1449
1545
|
|
|
1450
1546
|
if (hasIssues) {
|
|
1451
1547
|
console.log();
|
|
1452
|
-
|
|
1548
|
+
// `uds update --sync-refs` refuses to run without `integrationConfigs`, and a
|
|
1549
|
+
// project initialised non-interactively never had that key — so this hint
|
|
1550
|
+
// named a command that could not work (reproduced on a clean 6.9.0 project,
|
|
1551
|
+
// 2026-09-16). Name the one that regenerates from the manifest instead.
|
|
1552
|
+
const canSyncRefs = manifest.integrationConfigs && Object.keys(manifest.integrationConfigs).length > 0;
|
|
1553
|
+
console.log(chalk.yellow(` ${canSyncRefs
|
|
1554
|
+
? msg.runSyncRefs
|
|
1555
|
+
: (msg.runIntegrationsOnly || 'Run `uds update --integrations-only` to regenerate integration files.')}`));
|
|
1453
1556
|
}
|
|
1454
1557
|
|
|
1455
1558
|
console.log();
|
|
@@ -1461,7 +1564,7 @@ function checkReferenceSync(manifest, projectPath, msg) {
|
|
|
1461
1564
|
*/
|
|
1462
1565
|
async function checkCliVersion(bundledVersion) {
|
|
1463
1566
|
const msg = t().commands.check;
|
|
1464
|
-
const spinner =
|
|
1567
|
+
const spinner = createSpinner({ text: msg.checkingCliUpdates, spinner: 'dots' }).start();
|
|
1465
1568
|
|
|
1466
1569
|
try {
|
|
1467
1570
|
const result = await checkForUpdates(bundledVersion, {
|
|
@@ -1582,17 +1685,17 @@ function checkSkillsIntegrity(manifest, projectPath, msg) {
|
|
|
1582
1685
|
* @param {Object} msg - Localized messages
|
|
1583
1686
|
* @returns {Object} Status { unchanged: [], modified: [], missing: [] }
|
|
1584
1687
|
*/
|
|
1585
|
-
function checkCommandsIntegrity(manifest, projectPath, msg) {
|
|
1688
|
+
export function checkCommandsIntegrity(manifest, projectPath, msg) {
|
|
1586
1689
|
const commandHashes = manifest.commandHashes;
|
|
1587
1690
|
|
|
1588
1691
|
// Skip if no command hashes tracked
|
|
1589
1692
|
if (!commandHashes || Object.keys(commandHashes).length === 0) {
|
|
1590
|
-
return { unchanged: [], modified: [], missing: [], tracked: false };
|
|
1693
|
+
return { unchanged: [], modified: [], missing: [], untracked: [], tracked: false };
|
|
1591
1694
|
}
|
|
1592
1695
|
|
|
1593
1696
|
console.log(chalk.cyan(msg.commandsIntegrityCheck || 'Commands File Integrity'));
|
|
1594
1697
|
|
|
1595
|
-
const status = { unchanged: [], modified: [], missing: [], tracked: true };
|
|
1698
|
+
const status = { unchanged: [], modified: [], missing: [], untracked: [], tracked: true };
|
|
1596
1699
|
|
|
1597
1700
|
for (const [hashKey, hashInfo] of Object.entries(commandHashes)) {
|
|
1598
1701
|
// Parse key format: agent/filename.md
|
|
@@ -1632,8 +1735,42 @@ function checkCommandsIntegrity(manifest, projectPath, msg) {
|
|
|
1632
1735
|
}
|
|
1633
1736
|
}
|
|
1634
1737
|
|
|
1738
|
+
// 🔴 The loop above measures only what the manifest happens to track. When a
|
|
1739
|
+
// partial apply dropped 48 of 51 entries (6.9.0, fixed in plan-executor.js),
|
|
1740
|
+
// this section printed "All command files intact (3 files)" while the adoption
|
|
1741
|
+
// summary printed "Commands: 51 installed" from a directory listing — two
|
|
1742
|
+
// numbers, two sources, never compared, so the gap read as a pass.
|
|
1743
|
+
const trackedKeys = new Set(Object.keys(commandHashes));
|
|
1744
|
+
const agentsToScan = new Set(Object.keys(commandHashes).map(k => k.split('/')[0]));
|
|
1745
|
+
for (const entry of manifest.commands?.installations || []) {
|
|
1746
|
+
const agentName = typeof entry === 'string' ? entry : entry?.agent;
|
|
1747
|
+
if (agentName) agentsToScan.add(agentName);
|
|
1748
|
+
}
|
|
1749
|
+
for (const agentName of agentsToScan) {
|
|
1750
|
+
// Reuse the installer's own listing so this file filter cannot drift from it.
|
|
1751
|
+
const info = getInstalledCommandsForAgent(agentName, 'project', projectPath);
|
|
1752
|
+
if (!info?.installed) continue;
|
|
1753
|
+
const ext = agentName === 'gemini-cli' ? '.toml' : '.md';
|
|
1754
|
+
for (const name of info.commands || []) {
|
|
1755
|
+
const key = `${agentName}/${name}${ext}`;
|
|
1756
|
+
if (!trackedKeys.has(key)) status.untracked.push(key);
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
if (status.untracked.length > 0) {
|
|
1760
|
+
console.log(chalk.yellow(` ⚠ ${(msg.commandsUntracked || '{count} installed command file(s) are covered by no hash — integrity is not checked for them')
|
|
1761
|
+
.replace('{count}', status.untracked.length)}`));
|
|
1762
|
+
for (const key of status.untracked.slice(0, 5)) {
|
|
1763
|
+
console.log(chalk.yellow(` - ${key}`));
|
|
1764
|
+
}
|
|
1765
|
+
if (status.untracked.length > 5) {
|
|
1766
|
+
console.log(chalk.yellow(` … +${status.untracked.length - 5}`));
|
|
1767
|
+
}
|
|
1768
|
+
console.log(chalk.gray(` ${msg.commandsUntrackedFix || 'Run `uds update --commands` to record them again.'}`));
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
|
|
1635
1772
|
// Summary
|
|
1636
|
-
if (status.modified.length === 0 && status.missing.length === 0) {
|
|
1773
|
+
if (status.modified.length === 0 && status.missing.length === 0 && status.untracked.length === 0) {
|
|
1637
1774
|
console.log(chalk.green(` ✓ ${msg.allCommandsIntact || 'All command files intact'} (${status.unchanged.length} files)`));
|
|
1638
1775
|
} else {
|
|
1639
1776
|
console.log(chalk.gray(` ${(msg.commandsIntegritySummary || '{unchanged} unchanged, {modified} modified, {missing} missing')
|
package/src/commands/config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { select, confirm as inquirerConfirm } from '@inquirer/prompts';
|
|
3
|
-
import
|
|
3
|
+
import { createSpinner } from '../utils/spinner.js';
|
|
4
4
|
import { unlinkSync, existsSync, writeFileSync, mkdirSync } from 'fs';
|
|
5
5
|
import { join, basename } from 'path';
|
|
6
6
|
import { config } from '../utils/config-manager.js';
|
|
@@ -381,12 +381,12 @@ async function handleDisplayLanguageChange() {
|
|
|
381
381
|
if (confirmReinstall) {
|
|
382
382
|
const cmdLocale = displayLanguageToLocale(newLang);
|
|
383
383
|
if (hasSkillInstalls) {
|
|
384
|
-
const spinner =
|
|
384
|
+
const spinner = createSpinner(t('config.reinstallingSkills', 'Reinstalling Skills...')).start();
|
|
385
385
|
await installSkillsToMultipleAgents(manifest.skills.installations, null, projectPath, cmdLocale);
|
|
386
386
|
spinner.succeed(t('config.skillsReinstalled', 'Skills reinstalled'));
|
|
387
387
|
}
|
|
388
388
|
if (hasCommandInstalls) {
|
|
389
|
-
const spinner =
|
|
389
|
+
const spinner = createSpinner(t('config.reinstallingCommands', 'Reinstalling Commands...')).start();
|
|
390
390
|
await installCommandsToMultipleAgents(manifest.commands.installations, null, projectPath, cmdLocale);
|
|
391
391
|
spinner.succeed(t('config.commandsReinstalled', 'Commands reinstalled'));
|
|
392
392
|
}
|
|
@@ -401,7 +401,7 @@ async function handleDisplayLanguageChange() {
|
|
|
401
401
|
});
|
|
402
402
|
|
|
403
403
|
if (confirmRegen) {
|
|
404
|
-
const spinner =
|
|
404
|
+
const spinner = createSpinner(t('config.applyingPreset', 'Applying...')).start();
|
|
405
405
|
regenerateIntegrations(projectPath, manifest);
|
|
406
406
|
spinner.succeed(t('config.integrationsRegenerated', 'Integrations regenerated'));
|
|
407
407
|
}
|
|
@@ -665,7 +665,7 @@ export async function runProjectConfiguration(options) {
|
|
|
665
665
|
newAITools = newAITools.filter(tool => !result.tools.includes(tool));
|
|
666
666
|
|
|
667
667
|
// Remove integration files for removed tools
|
|
668
|
-
const spinner =
|
|
668
|
+
const spinner = createSpinner(msgObj.removingIntegrations).start();
|
|
669
669
|
for (const tool of result.tools) {
|
|
670
670
|
const filePath = join(projectPath, getToolFilePath(tool));
|
|
671
671
|
if (existsSync(filePath)) {
|
|
@@ -854,7 +854,7 @@ export async function runProjectConfiguration(options) {
|
|
|
854
854
|
}
|
|
855
855
|
|
|
856
856
|
// Apply changes
|
|
857
|
-
const spinner =
|
|
857
|
+
const spinner = createSpinner(msgObj.updatingConfig).start();
|
|
858
858
|
|
|
859
859
|
const results = {
|
|
860
860
|
copied: [],
|
|
@@ -914,7 +914,7 @@ export async function runProjectConfiguration(options) {
|
|
|
914
914
|
|
|
915
915
|
// Regenerate integration files if needed
|
|
916
916
|
if (needsIntegrationRegeneration && newAITools.length > 0) {
|
|
917
|
-
const intSpinner =
|
|
917
|
+
const intSpinner = createSpinner(msgObj.regeneratingIntegrations).start();
|
|
918
918
|
|
|
919
919
|
// Build installed standards list
|
|
920
920
|
// Raw, not basename()d. Resolution needs the registry (an ID is not a
|
|
@@ -989,7 +989,7 @@ export async function runProjectConfiguration(options) {
|
|
|
989
989
|
|
|
990
990
|
if (confirmSkills) {
|
|
991
991
|
const newInstallations = skillCapableTools.map(agent => ({ agent, level }));
|
|
992
|
-
const spinner =
|
|
992
|
+
const spinner = createSpinner(t('config.installingSkillsForNewTools', 'Installing Skills for new tools...')).start();
|
|
993
993
|
const skillResult = await installSkillsToMultipleAgents(newInstallations, null, projectPath, cmdLocale);
|
|
994
994
|
spinner.succeed(t('config.skillsInstalledForNewTools', 'Skills installed for new tools'));
|
|
995
995
|
|
|
@@ -1026,7 +1026,7 @@ export async function runProjectConfiguration(options) {
|
|
|
1026
1026
|
|
|
1027
1027
|
if (confirmCmds) {
|
|
1028
1028
|
const newCmdInstallations = commandCapableTools.map(agent => ({ agent, level }));
|
|
1029
|
-
const spinner =
|
|
1029
|
+
const spinner = createSpinner(t('config.installingCommandsForNewTools', 'Installing Commands for new tools...')).start();
|
|
1030
1030
|
const cmdResult = await installCommandsToMultipleAgents(newCmdInstallations, null, projectPath, cmdLocale);
|
|
1031
1031
|
spinner.succeed(t('config.commandsInstalledForNewTools', 'Commands installed for new tools'));
|
|
1032
1032
|
|
|
@@ -1124,7 +1124,7 @@ export async function runProjectConfiguration(options) {
|
|
|
1124
1124
|
|
|
1125
1125
|
if (options.yes) {
|
|
1126
1126
|
// --yes flag: auto-apply without prompting
|
|
1127
|
-
const applySpinner =
|
|
1127
|
+
const applySpinner = createSpinner(msgObj.applyingChanges).start();
|
|
1128
1128
|
const applyResults = regenerateIntegrations(projectPath, manifest);
|
|
1129
1129
|
applySpinner.succeed(msgObj.changesApplied || msgObj.regeneratedIntegrations.replace('{count}', applyResults.updated.length));
|
|
1130
1130
|
|
|
@@ -1146,7 +1146,7 @@ export async function runProjectConfiguration(options) {
|
|
|
1146
1146
|
});
|
|
1147
1147
|
|
|
1148
1148
|
if (apply) {
|
|
1149
|
-
const applySpinner =
|
|
1149
|
+
const applySpinner = createSpinner(msgObj.applyingChanges).start();
|
|
1150
1150
|
const applyResults = regenerateIntegrations(projectPath, manifest);
|
|
1151
1151
|
applySpinner.succeed(msgObj.changesApplied || msgObj.regeneratedIntegrations.replace('{count}', applyResults.updated.length));
|
|
1152
1152
|
|
|
@@ -1172,12 +1172,12 @@ export async function runProjectConfiguration(options) {
|
|
|
1172
1172
|
if (hasSkillInstalls || hasCommandInstalls) {
|
|
1173
1173
|
const cmdLocale = displayLanguageToLocale(newOptions.display_language);
|
|
1174
1174
|
if (hasSkillInstalls) {
|
|
1175
|
-
const skillSpinner =
|
|
1175
|
+
const skillSpinner = createSpinner(t('config.reinstallingSkills', 'Reinstalling Skills...')).start();
|
|
1176
1176
|
await installSkillsToMultipleAgents(manifest.skills.installations, null, projectPath, cmdLocale);
|
|
1177
1177
|
skillSpinner.succeed(t('config.skillsReinstalled', 'Skills reinstalled'));
|
|
1178
1178
|
}
|
|
1179
1179
|
if (hasCommandInstalls) {
|
|
1180
|
-
const cmdSpinner =
|
|
1180
|
+
const cmdSpinner = createSpinner(t('config.reinstallingCommands', 'Reinstalling Commands...')).start();
|
|
1181
1181
|
await installCommandsToMultipleAgents(manifest.commands.installations, null, projectPath, cmdLocale);
|
|
1182
1182
|
cmdSpinner.succeed(t('config.commandsReinstalled', 'Commands reinstalled'));
|
|
1183
1183
|
}
|
|
@@ -1222,7 +1222,7 @@ async function handleSkillsConfiguration(manifest, projectPath, msgObj, common,
|
|
|
1222
1222
|
|
|
1223
1223
|
// Install to specified level (defaults to project)
|
|
1224
1224
|
const installations = [{ agent: specificTool, level }];
|
|
1225
|
-
const spinner =
|
|
1225
|
+
const spinner = createSpinner(`Installing Skills for ${getAgentDisplayName(specificTool)} (${level} level)...`).start();
|
|
1226
1226
|
const result = await installSkillsToMultipleAgents(installations, null, projectPath);
|
|
1227
1227
|
spinner.stop();
|
|
1228
1228
|
|
|
@@ -1359,7 +1359,7 @@ async function handleSkillsConfiguration(manifest, projectPath, msgObj, common,
|
|
|
1359
1359
|
}));
|
|
1360
1360
|
|
|
1361
1361
|
// Install Skills
|
|
1362
|
-
const spinner =
|
|
1362
|
+
const spinner = createSpinner(msgObj.installingSkills || 'Installing Skills...').start();
|
|
1363
1363
|
const result = await installSkillsToMultipleAgents(installations, null, projectPath);
|
|
1364
1364
|
spinner.stop();
|
|
1365
1365
|
|
|
@@ -1394,7 +1394,7 @@ async function handleSkillsConfiguration(manifest, projectPath, msgObj, common,
|
|
|
1394
1394
|
}
|
|
1395
1395
|
|
|
1396
1396
|
// Install Skills
|
|
1397
|
-
const spinner =
|
|
1397
|
+
const spinner = createSpinner(msgObj.installingSkills || 'Installing Skills...').start();
|
|
1398
1398
|
const result = await installSkillsToMultipleAgents(installations, null, projectPath);
|
|
1399
1399
|
spinner.stop();
|
|
1400
1400
|
|
|
@@ -1453,7 +1453,7 @@ async function handleCommandsConfiguration(manifest, projectPath, msgObj, common
|
|
|
1453
1453
|
|
|
1454
1454
|
// Install to selected level
|
|
1455
1455
|
const installations = [{ agent: specificTool, level: commandsLevel }];
|
|
1456
|
-
const spinner =
|
|
1456
|
+
const spinner = createSpinner(`Installing Commands for ${getAgentDisplayName(specificTool)} (${commandsLevel} level)...`).start();
|
|
1457
1457
|
const cmdLocale = displayLanguageToLocale(manifest.options?.display_language);
|
|
1458
1458
|
const result = await installCommandsToMultipleAgents(installations, null, projectPath, cmdLocale);
|
|
1459
1459
|
spinner.stop();
|
|
@@ -1559,7 +1559,7 @@ async function handleCommandsConfiguration(manifest, projectPath, msgObj, common
|
|
|
1559
1559
|
}
|
|
1560
1560
|
|
|
1561
1561
|
// Install Commands
|
|
1562
|
-
const spinner =
|
|
1562
|
+
const spinner = createSpinner(msgObj.installingCommands || 'Installing Commands...').start();
|
|
1563
1563
|
const cmdLocale = displayLanguageToLocale(manifest.options?.display_language);
|
|
1564
1564
|
const result = await installCommandsToMultipleAgents(declinedCommandsWithSupport, null, projectPath, cmdLocale);
|
|
1565
1565
|
spinner.stop();
|
|
@@ -1595,7 +1595,7 @@ async function handleCommandsConfiguration(manifest, projectPath, msgObj, common
|
|
|
1595
1595
|
}
|
|
1596
1596
|
|
|
1597
1597
|
// Install Commands
|
|
1598
|
-
const spinner =
|
|
1598
|
+
const spinner = createSpinner(msgObj.installingCommands || 'Installing Commands...').start();
|
|
1599
1599
|
const cmdLocale = displayLanguageToLocale(manifest.options?.display_language);
|
|
1600
1600
|
const result = await installCommandsToMultipleAgents(selectedAgents, null, projectPath, cmdLocale);
|
|
1601
1601
|
spinner.stop();
|
package/src/commands/init.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import
|
|
2
|
+
import { createSpinner } from '../utils/spinner.js';
|
|
3
3
|
import { execSync } from 'child_process';
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
5
|
-
import {
|
|
5
|
+
import { join } from 'path';
|
|
6
6
|
import {
|
|
7
7
|
manifestExists as isInitialized,
|
|
8
8
|
normalizeContentMode,
|
|
@@ -55,7 +55,7 @@ export async function initCommand(options) {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
// STEP 2: Detect project characteristics
|
|
58
|
-
const spinner =
|
|
58
|
+
const spinner = createSpinner(msg.detectingProject).start();
|
|
59
59
|
const detected = detectAll(projectPath);
|
|
60
60
|
spinner.succeed(msg.analysisComplete);
|
|
61
61
|
|
|
@@ -123,7 +123,16 @@ export async function initCommand(options) {
|
|
|
123
123
|
apply: async () => {
|
|
124
124
|
// 1. Install Standards
|
|
125
125
|
const standardsResults = await installStandards(config, projectPath);
|
|
126
|
-
|
|
126
|
+
// Paths, not basenames. Everything downstream classifies an option
|
|
127
|
+
// by an `/options/` segment, so reducing to basenames here is not a
|
|
128
|
+
// simplification — it deletes the only signal that distinguishes the
|
|
129
|
+
// two, and `contentLayout: flat` installs option files with no
|
|
130
|
+
// directory to recover it from. The index block consequently said
|
|
131
|
+
// "options 0" on every project for two releases, while the manifest
|
|
132
|
+
// right beside it listed seven. `buildToolIntegrationConfig` (the
|
|
133
|
+
// update path's equivalent) has carried a comment warning against
|
|
134
|
+
// exactly this reduction the whole time; only this call site did it.
|
|
135
|
+
config.installedStandards = standardsResults.standards;
|
|
127
136
|
|
|
128
137
|
// 1.5. Generate release-config.yaml if non-default mode selected
|
|
129
138
|
if (config.releaseMode && config.releaseMode !== 'ci-cd') {
|
package/src/commands/spec.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import chalk from 'chalk';
|
|
12
|
-
import
|
|
12
|
+
import { createSpinner } from '../utils/spinner.js';
|
|
13
13
|
import { select, confirm as inquirerConfirm } from '@inquirer/prompts';
|
|
14
14
|
import { existsSync, readFileSync } from 'node:fs';
|
|
15
15
|
import { join } from 'node:path';
|
|
@@ -48,7 +48,7 @@ export async function specCreateCommand(intent, options = {}) {
|
|
|
48
48
|
output: options.output
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
const spinner =
|
|
51
|
+
const spinner = createSpinner({
|
|
52
52
|
text: t('spec.generating', 'Generating micro-spec...'),
|
|
53
53
|
color: 'cyan'
|
|
54
54
|
}).start();
|