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/update.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import
|
|
2
|
+
import { createSpinner } from '../utils/spinner.js';
|
|
3
3
|
import { select, confirm as inquirerConfirm, checkbox, Separator } from '@inquirer/prompts';
|
|
4
4
|
import { execSync } from 'child_process';
|
|
5
5
|
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'fs';
|
|
6
6
|
import { join, basename, dirname, relative } from 'path';
|
|
7
7
|
import { readManifest, writeManifest, copyStandard, isInitialized, getRepoRoot } from '../utils/copier.js';
|
|
8
|
-
import { getRepositoryInfo, getAllStandards, getStandardSource } from '../utils/registry.js';
|
|
8
|
+
import { getRepositoryInfo, getAllStandards, getShippableFilenames, getStandardSource } from '../utils/registry.js';
|
|
9
9
|
import { computeFileHash, planStandardsRemovals, refreshIntegrationBlockHashes } from '../utils/hasher.js';
|
|
10
10
|
import {
|
|
11
11
|
writeIntegrationFile,
|
|
@@ -56,7 +56,7 @@ import {
|
|
|
56
56
|
} from '../reconciler/index.js';
|
|
57
57
|
import { restoreSingleFile } from './check.js';
|
|
58
58
|
import { guardAgainstSelfAdoption } from '../utils/detect-self-adoption.js';
|
|
59
|
-
import { resolveIntegrationFile } from '../core/constants.js';
|
|
59
|
+
import { resolveIntegrationFile, SUPPORTED_AI_TOOLS } from '../core/constants.js';
|
|
60
60
|
import {
|
|
61
61
|
mergeInstalledNames,
|
|
62
62
|
recordFileProvenance,
|
|
@@ -274,7 +274,7 @@ function buildInstallCommand(pm, tag) {
|
|
|
274
274
|
*/
|
|
275
275
|
async function updateCliAndExit(useBeta = false) {
|
|
276
276
|
const msg = t().commands.update;
|
|
277
|
-
const spinner =
|
|
277
|
+
const spinner = createSpinner(msg.updatingCli).start();
|
|
278
278
|
|
|
279
279
|
try {
|
|
280
280
|
// Command is hardcoded - no user input, safe from injection
|
|
@@ -581,6 +581,23 @@ export async function updateCommand(options) {
|
|
|
581
581
|
if (versionComparison > 0) {
|
|
582
582
|
console.log(chalk.gray(` ${msg.newerVersion.replace('{version}', currentVersion)}`));
|
|
583
583
|
}
|
|
584
|
+
|
|
585
|
+
// Still forget records of files that are gone and no longer shipped. An
|
|
586
|
+
// adopter who followed MIGRATION-v6 §2 is on the current version by
|
|
587
|
+
// definition — telling them to run `uds update --prune` and then returning
|
|
588
|
+
// before anything is examined is how the complaint became unclearable.
|
|
589
|
+
const retiredOnLatest = pruneRetiredHashes(manifest, projectPath);
|
|
590
|
+
if (retiredOnLatest.length > 0) {
|
|
591
|
+
console.log();
|
|
592
|
+
console.log(chalk.gray(
|
|
593
|
+
` ${(msg.droppedRetiredHashes || 'Dropped {count} manifest record(s) for files UDS no longer ships and that are already gone:').replace('{count}', retiredOnLatest.length)}`
|
|
594
|
+
));
|
|
595
|
+
for (const path of retiredOnLatest) {
|
|
596
|
+
console.log(chalk.gray(` - ${path}`));
|
|
597
|
+
}
|
|
598
|
+
writeManifest(manifest, projectPath);
|
|
599
|
+
}
|
|
600
|
+
|
|
584
601
|
console.log();
|
|
585
602
|
return;
|
|
586
603
|
}
|
|
@@ -642,7 +659,7 @@ export async function updateCommand(options) {
|
|
|
642
659
|
|
|
643
660
|
// Perform update
|
|
644
661
|
console.log();
|
|
645
|
-
const spinner =
|
|
662
|
+
const spinner = createSpinner(msg.updatingStandards).start();
|
|
646
663
|
|
|
647
664
|
const results = {
|
|
648
665
|
updated: [],
|
|
@@ -714,7 +731,7 @@ export async function updateCommand(options) {
|
|
|
714
731
|
}
|
|
715
732
|
|
|
716
733
|
if (shouldInstallNew) {
|
|
717
|
-
const newSpinner =
|
|
734
|
+
const newSpinner = createSpinner(msg.installingNewStandards).start();
|
|
718
735
|
let newCount = 0;
|
|
719
736
|
|
|
720
737
|
for (const ns of newStandards) {
|
|
@@ -735,7 +752,7 @@ export async function updateCommand(options) {
|
|
|
735
752
|
|
|
736
753
|
// Update integrations (unless --standards-only)
|
|
737
754
|
if (!options.standardsOnly && manifest.integrations && manifest.integrations.length > 0) {
|
|
738
|
-
const intSpinner =
|
|
755
|
+
const intSpinner = createSpinner(msg.syncingIntegrations).start();
|
|
739
756
|
|
|
740
757
|
// Build installed standards list
|
|
741
758
|
// Raw, not basename()d. Resolution needs the registry (an ID is not a
|
|
@@ -813,6 +830,7 @@ export async function updateCommand(options) {
|
|
|
813
830
|
installedAt: new Date().toISOString()
|
|
814
831
|
};
|
|
815
832
|
}
|
|
833
|
+
refreshTrackedFileHash(manifest, projectPath, agentsMdResult.path);
|
|
816
834
|
}
|
|
817
835
|
}
|
|
818
836
|
|
|
@@ -989,6 +1007,22 @@ export async function updateCommand(options) {
|
|
|
989
1007
|
}
|
|
990
1008
|
}
|
|
991
1009
|
|
|
1010
|
+
// Records of files that are already gone and that UDS no longer ships. Not
|
|
1011
|
+
// part of the removal plan above: that plan walks what is on disk, and these
|
|
1012
|
+
// are precisely the ones that are not. Reported rather than done quietly,
|
|
1013
|
+
// because a manifest shrinking without explanation is the shape this whole
|
|
1014
|
+
// release is about.
|
|
1015
|
+
const retiredHashes = pruneRetiredHashes(manifest, projectPath);
|
|
1016
|
+
if (retiredHashes.length > 0) {
|
|
1017
|
+
console.log();
|
|
1018
|
+
console.log(chalk.gray(
|
|
1019
|
+
` ${(msg.droppedRetiredHashes || 'Dropped {count} manifest record(s) for files UDS no longer ships and that are already gone:').replace('{count}', retiredHashes.length)}`
|
|
1020
|
+
));
|
|
1021
|
+
for (const path of retiredHashes) {
|
|
1022
|
+
console.log(chalk.gray(` - ${path}`));
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
|
|
992
1026
|
// Provenance is only a complete account of what we own once a run has
|
|
993
1027
|
// written the whole installed set. Until then every unrecorded file is
|
|
994
1028
|
// `unknown` rather than `foreign`, and nothing gets deleted on the strength
|
|
@@ -1110,7 +1144,7 @@ export async function updateCommand(options) {
|
|
|
1110
1144
|
}
|
|
1111
1145
|
|
|
1112
1146
|
if (shouldRestore) {
|
|
1113
|
-
const restoreSpinner =
|
|
1147
|
+
const restoreSpinner = createSpinner((msg.restoringMissing || 'Restoring missing files...')).start();
|
|
1114
1148
|
const checkMsg = t().commands.check;
|
|
1115
1149
|
let restoredCount = 0;
|
|
1116
1150
|
|
|
@@ -1177,7 +1211,7 @@ export async function updateCommand(options) {
|
|
|
1177
1211
|
|
|
1178
1212
|
// Install Skills if user agreed
|
|
1179
1213
|
if (installSkills.length > 0) {
|
|
1180
|
-
const skillSpinner =
|
|
1214
|
+
const skillSpinner = createSpinner(msg.installingNewSkills || 'Installing Skills...').start();
|
|
1181
1215
|
const skillsLocale = resolveLocale(manifest, projectPath, options);
|
|
1182
1216
|
const skillResult = await installSkillsToMultipleAgents(installSkills, null, projectPath, skillsLocale);
|
|
1183
1217
|
|
|
@@ -1225,7 +1259,7 @@ export async function updateCommand(options) {
|
|
|
1225
1259
|
|
|
1226
1260
|
// Update outdated Skills if user agreed
|
|
1227
1261
|
if (updateSkills.length > 0) {
|
|
1228
|
-
const updateSpinner =
|
|
1262
|
+
const updateSpinner = createSpinner(msg.updatingSkills || 'Updating Skills...').start();
|
|
1229
1263
|
const updateLocale = resolveLocale(manifest, projectPath, options);
|
|
1230
1264
|
const updateResult = await installSkillsToMultipleAgents(updateSkills, null, projectPath, updateLocale);
|
|
1231
1265
|
|
|
@@ -1263,7 +1297,7 @@ export async function updateCommand(options) {
|
|
|
1263
1297
|
|
|
1264
1298
|
// Install Commands if user agreed
|
|
1265
1299
|
if (installCommands.length > 0) {
|
|
1266
|
-
const cmdSpinner =
|
|
1300
|
+
const cmdSpinner = createSpinner(msg.installingNewCommands || 'Installing commands...').start();
|
|
1267
1301
|
const cmdLocale = resolveLocale(manifest, projectPath, options);
|
|
1268
1302
|
const cmdResult = await installCommandsToMultipleAgents(installCommands, null, projectPath, cmdLocale);
|
|
1269
1303
|
|
|
@@ -1294,7 +1328,7 @@ export async function updateCommand(options) {
|
|
|
1294
1328
|
|
|
1295
1329
|
// Update outdated Commands if user agreed
|
|
1296
1330
|
if (updateCommands.length > 0) {
|
|
1297
|
-
const updateCmdSpinner =
|
|
1331
|
+
const updateCmdSpinner = createSpinner(msg.updatingCommands || 'Updating Commands...').start();
|
|
1298
1332
|
const updateCmdLocale = resolveLocale(manifest, projectPath, options);
|
|
1299
1333
|
const updateCmdResult = await installCommandsToMultipleAgents(updateCommands, null, projectPath, updateCmdLocale);
|
|
1300
1334
|
|
|
@@ -1370,7 +1404,7 @@ export async function updateCommand(options) {
|
|
|
1370
1404
|
let hasChanges = false;
|
|
1371
1405
|
|
|
1372
1406
|
if (missingSkills.length > 0) {
|
|
1373
|
-
const spinner =
|
|
1407
|
+
const spinner = createSpinner(msg.installingNewSkills || 'Installing Skills...').start();
|
|
1374
1408
|
const result = await installSkillsToMultipleAgents(missingSkills, null, projectPath, skillsLocale);
|
|
1375
1409
|
if (!manifest.skills) manifest.skills = {};
|
|
1376
1410
|
manifest.skills.installed = true;
|
|
@@ -1391,7 +1425,7 @@ export async function updateCommand(options) {
|
|
|
1391
1425
|
}
|
|
1392
1426
|
|
|
1393
1427
|
if (outdatedSkills.length > 0) {
|
|
1394
|
-
const spinner =
|
|
1428
|
+
const spinner = createSpinner(msg.updatingSkills || 'Updating Skills...').start();
|
|
1395
1429
|
const result = await installSkillsToMultipleAgents(outdatedSkills, null, projectPath, skillsLocale);
|
|
1396
1430
|
if (!manifest.skills) manifest.skills = {};
|
|
1397
1431
|
manifest.skills.version = repoInfo.skills.version;
|
|
@@ -1415,7 +1449,7 @@ export async function updateCommand(options) {
|
|
|
1415
1449
|
}
|
|
1416
1450
|
|
|
1417
1451
|
if (missingCommands.length > 0) {
|
|
1418
|
-
const spinner =
|
|
1452
|
+
const spinner = createSpinner(msg.installingNewCommands || 'Installing commands...').start();
|
|
1419
1453
|
const result = await installCommandsToMultipleAgents(missingCommands, null, projectPath, skillsLocale);
|
|
1420
1454
|
if (!manifest.commands) manifest.commands = {};
|
|
1421
1455
|
manifest.commands.installed = true;
|
|
@@ -1435,7 +1469,7 @@ export async function updateCommand(options) {
|
|
|
1435
1469
|
}
|
|
1436
1470
|
|
|
1437
1471
|
if (outdatedCommands.length > 0) {
|
|
1438
|
-
const spinner =
|
|
1472
|
+
const spinner = createSpinner(msg.updatingCommands || 'Updating Commands...').start();
|
|
1439
1473
|
const result = await installCommandsToMultipleAgents(outdatedCommands, null, projectPath, skillsLocale);
|
|
1440
1474
|
if (!manifest.commands) manifest.commands = {};
|
|
1441
1475
|
manifest.commands.version = repoInfo.skills.version;
|
|
@@ -1688,6 +1722,7 @@ export function regenerateIntegrations(projectPath, manifest) {
|
|
|
1688
1722
|
installedAt: now
|
|
1689
1723
|
};
|
|
1690
1724
|
}
|
|
1725
|
+
refreshTrackedFileHash(manifest, projectPath, agentsMdResult.path);
|
|
1691
1726
|
}
|
|
1692
1727
|
}
|
|
1693
1728
|
|
|
@@ -1811,7 +1846,7 @@ async function updateIntegrationsOnly(projectPath, manifest, options = {}) {
|
|
|
1811
1846
|
return;
|
|
1812
1847
|
}
|
|
1813
1848
|
|
|
1814
|
-
const spinner =
|
|
1849
|
+
const spinner = createSpinner(msg.regeneratingIntegrations).start();
|
|
1815
1850
|
|
|
1816
1851
|
// Use reusable regeneration function
|
|
1817
1852
|
const results = regenerateIntegrations(projectPath, manifest);
|
|
@@ -1840,6 +1875,188 @@ async function updateIntegrationsOnly(projectPath, manifest, options = {}) {
|
|
|
1840
1875
|
process.exit(0);
|
|
1841
1876
|
}
|
|
1842
1877
|
|
|
1878
|
+
/**
|
|
1879
|
+
* Rebuild `manifest.integrationConfigs` from what the manifest already records.
|
|
1880
|
+
*
|
|
1881
|
+
* 🔴 Only `uds init`'s interactive flow and `--sync-refs` itself ever wrote this
|
|
1882
|
+
* key; no update or reconcile path backfills it, so a project created with
|
|
1883
|
+
* `uds init -y` carries `{}` from the first day (reproduced on a clean 6.9.0
|
|
1884
|
+
* project, 2026-09-16). `--sync-refs` then refuses to run — and `uds check` was
|
|
1885
|
+
* recommending exactly that command. Everything the config holds is derivable
|
|
1886
|
+
* from the manifest, so derive it instead of demanding the user re-init.
|
|
1887
|
+
*
|
|
1888
|
+
* Existing entries are never overwritten: they may hold choices (categories,
|
|
1889
|
+
* content mode) that this derivation cannot know.
|
|
1890
|
+
*
|
|
1891
|
+
* @param {Object} manifest - Manifest object (mutated)
|
|
1892
|
+
* @returns {number} How many entries were added
|
|
1893
|
+
*/
|
|
1894
|
+
export function backfillIntegrationConfigs(manifest) {
|
|
1895
|
+
if (!manifest.integrationConfigs) manifest.integrationConfigs = {};
|
|
1896
|
+
|
|
1897
|
+
const tools = manifest.aiTools?.length ? manifest.aiTools : [];
|
|
1898
|
+
const byFile = new Map();
|
|
1899
|
+
for (const tool of tools) {
|
|
1900
|
+
const file = resolveIntegrationFile(tool) || getToolFilePath(tool);
|
|
1901
|
+
if (file && !byFile.has(file)) byFile.set(file, tool);
|
|
1902
|
+
}
|
|
1903
|
+
// Integrations can be recorded as file names (`CLAUDE.md`) or tool keys.
|
|
1904
|
+
for (const entry of manifest.integrations || []) {
|
|
1905
|
+
const file = resolveIntegrationFile(entry) || (String(entry).includes('.') ? String(entry) : null);
|
|
1906
|
+
if (!file) continue;
|
|
1907
|
+
if (!byFile.has(file)) {
|
|
1908
|
+
const tool = getToolFromPath(file) || resolveToolKeyFromEntry(entry);
|
|
1909
|
+
if (tool) byFile.set(file, tool);
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
let added = 0;
|
|
1914
|
+
for (const [file, tool] of byFile) {
|
|
1915
|
+
if (manifest.integrationConfigs[file]) continue;
|
|
1916
|
+
manifest.integrationConfigs[file] = {
|
|
1917
|
+
...buildToolIntegrationConfig(manifest, tool),
|
|
1918
|
+
format: manifest.format || 'ai',
|
|
1919
|
+
backfilledAt: new Date().toISOString()
|
|
1920
|
+
};
|
|
1921
|
+
added++;
|
|
1922
|
+
}
|
|
1923
|
+
return added;
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
function resolveToolKeyFromEntry(entry) {
|
|
1927
|
+
const key = String(entry).replace(/\.[^.]+$/, '').toLowerCase();
|
|
1928
|
+
return SUPPORTED_AI_TOOLS?.[key] ? key : null;
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
/**
|
|
1932
|
+
* If the manifest already tracks this file, make its record describe what is on
|
|
1933
|
+
* disk now.
|
|
1934
|
+
*
|
|
1935
|
+
* Every path that writes AGENTS.md recorded `integrationBlockHashes` and none
|
|
1936
|
+
* recorded `fileHashes`, so a project whose manifest tracked the file (an older
|
|
1937
|
+
* install, or one where AGENTS.md was written as a tool's integration file) got
|
|
1938
|
+
* `⚠ AGENTS.md (modified)` from `uds check` the moment UDS itself regenerated
|
|
1939
|
+
* it. UDS wrote the file and did not record what it wrote.
|
|
1940
|
+
*
|
|
1941
|
+
* Conditional on purpose. `uds init` does not enrol AGENTS.md in `fileHashes`,
|
|
1942
|
+
* and adopters are expected to extend that summary — a path that started
|
|
1943
|
+
* tracking it would convert every legitimate edit into a reported fault. This
|
|
1944
|
+
* only ever refreshes a record that already exists.
|
|
1945
|
+
*
|
|
1946
|
+
* @param {Object} manifest - Project manifest (mutated)
|
|
1947
|
+
* @param {string} projectPath - Project root
|
|
1948
|
+
* @param {string} relativePath - Path as UDS reports it
|
|
1949
|
+
* @returns {boolean} Whether a record was refreshed
|
|
1950
|
+
*/
|
|
1951
|
+
export function refreshTrackedFileHash(manifest, projectPath, relativePath) {
|
|
1952
|
+
if (!manifest?.fileHashes || !relativePath) return false;
|
|
1953
|
+
const normalized = String(relativePath).replace(/\\/g, '/');
|
|
1954
|
+
if (!(normalized in manifest.fileHashes)) return false;
|
|
1955
|
+
|
|
1956
|
+
const hashInfo = computeFileHash(join(projectPath, normalized));
|
|
1957
|
+
if (!hashInfo) return false;
|
|
1958
|
+
|
|
1959
|
+
manifest.fileHashes[normalized] = { ...hashInfo, installedAt: new Date().toISOString() };
|
|
1960
|
+
return true;
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
/**
|
|
1964
|
+
* Forget `fileHashes` records for files that are gone AND that UDS no longer
|
|
1965
|
+
* ships.
|
|
1966
|
+
*
|
|
1967
|
+
* `--prune` deletes files it finds while walking `.standards/`. A file the
|
|
1968
|
+
* adopter already removed by hand is not in that walk, so its record survived
|
|
1969
|
+
* every prune — and the record is what made `uds check` report the completed
|
|
1970
|
+
* migration as seven faults. Following MIGRATION-v6 §2 therefore produced a
|
|
1971
|
+
* complaint that no command could clear.
|
|
1972
|
+
*
|
|
1973
|
+
* This is not a deletion. The file is already absent and no registry entry can
|
|
1974
|
+
* ever resolve to it again, so the record cannot refer to anything. Both facts
|
|
1975
|
+
* are required: a missing file UDS still ships is a real fault and stays; a
|
|
1976
|
+
* descoped standard still on disk is `--prune`'s business, gated on ownership,
|
|
1977
|
+
* and is left alone here.
|
|
1978
|
+
*
|
|
1979
|
+
* @param {Object} manifest - Project manifest (mutated)
|
|
1980
|
+
* @param {string} projectPath - Project root
|
|
1981
|
+
* @param {{shippable?: Set<string>}} [deps] - Injection point for tests
|
|
1982
|
+
* @returns {string[]} Paths whose records were dropped, sorted
|
|
1983
|
+
*/
|
|
1984
|
+
export function pruneRetiredHashes(manifest, projectPath, deps = {}) {
|
|
1985
|
+
if (!manifest?.fileHashes) return [];
|
|
1986
|
+
|
|
1987
|
+
let shippable;
|
|
1988
|
+
try {
|
|
1989
|
+
shippable = deps.shippable || getShippableFilenames();
|
|
1990
|
+
} catch {
|
|
1991
|
+
return [];
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
// A shippable set this small means the registry did not load, not that UDS
|
|
1995
|
+
// stopped shipping everything. Without this arm a broken install would empty
|
|
1996
|
+
// the manifest and every downstream check would go quiet at the same time.
|
|
1997
|
+
if (!shippable || shippable.size < 50) return [];
|
|
1998
|
+
|
|
1999
|
+
const dropped = [];
|
|
2000
|
+
for (const recorded of Object.keys(manifest.fileHashes)) {
|
|
2001
|
+
const normalized = recorded.replace(/\\/g, '/');
|
|
2002
|
+
if (!normalized.startsWith('.standards/')) continue;
|
|
2003
|
+
const fileName = normalized.split('/').pop();
|
|
2004
|
+
if (shippable.has(fileName)) continue;
|
|
2005
|
+
if (existsSync(join(projectPath, recorded))) continue;
|
|
2006
|
+
delete manifest.fileHashes[recorded];
|
|
2007
|
+
dropped.push(recorded);
|
|
2008
|
+
}
|
|
2009
|
+
return dropped.sort();
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
/**
|
|
2013
|
+
* Build the config `--sync-refs` regenerates an integration file from.
|
|
2014
|
+
*
|
|
2015
|
+
* The stored `integrationConfigs[file]` is a snapshot taken when the file was
|
|
2016
|
+
* installed. Its `categories` are the thing sync-refs exists to update, and its
|
|
2017
|
+
* `installedStandards` is the thing that must never be trusted: it is not
|
|
2018
|
+
* refreshed by any path, so on a project upgraded across majors it still lists
|
|
2019
|
+
* standards that stopped shipping, and whatever it lists becomes the file's
|
|
2020
|
+
* content. That is how a manifest holding 73 standards produced a CLAUDE.md
|
|
2021
|
+
* announcing 76, which `uds check` then flagged against the same manifest.
|
|
2022
|
+
*
|
|
2023
|
+
* So: manifest-derived fields win, the caller's freshly computed categories win
|
|
2024
|
+
* over both, and anything the manifest knows nothing about (timestamps, fields
|
|
2025
|
+
* a future version adds) is carried through untouched.
|
|
2026
|
+
*
|
|
2027
|
+
* `outputLanguage` is the one place the stored value still gets a say — a
|
|
2028
|
+
* manifest with no `output_language` should not silently reset a project that
|
|
2029
|
+
* chose one before the option was recorded there.
|
|
2030
|
+
*
|
|
2031
|
+
* @param {Object} manifest - Project manifest
|
|
2032
|
+
* @param {string} toolName - Tool key (e.g. 'claude-code')
|
|
2033
|
+
* @param {Object} storedConfig - The existing integrationConfigs entry
|
|
2034
|
+
* @param {string[]} expectedCategories - Categories computed from current standards
|
|
2035
|
+
* @returns {Object} Config for writeIntegrationFile
|
|
2036
|
+
*/
|
|
2037
|
+
export function refreshIntegrationConfig(manifest, toolName, storedConfig = {}, expectedCategories = []) {
|
|
2038
|
+
const outputLanguage =
|
|
2039
|
+
manifest.options?.output_language
|
|
2040
|
+
|| manifest.options?.commit_language
|
|
2041
|
+
|| storedConfig.outputLanguage
|
|
2042
|
+
|| storedConfig.commitLanguage
|
|
2043
|
+
|| 'english';
|
|
2044
|
+
|
|
2045
|
+
const fresh = buildToolIntegrationConfig(
|
|
2046
|
+
{ ...manifest, options: { ...(manifest.options || {}), output_language: outputLanguage } },
|
|
2047
|
+
toolName
|
|
2048
|
+
);
|
|
2049
|
+
|
|
2050
|
+
return {
|
|
2051
|
+
...storedConfig,
|
|
2052
|
+
...fresh,
|
|
2053
|
+
tool: toolName,
|
|
2054
|
+
categories: expectedCategories,
|
|
2055
|
+
outputLanguage,
|
|
2056
|
+
format: manifest.format || storedConfig.format || 'ai'
|
|
2057
|
+
};
|
|
2058
|
+
}
|
|
2059
|
+
|
|
1843
2060
|
/**
|
|
1844
2061
|
* Sync integration file references based on manifest standards
|
|
1845
2062
|
* @param {string} projectPath - Project path
|
|
@@ -1851,6 +2068,16 @@ async function syncIntegrationReferences(projectPath, manifest, { plan = false }
|
|
|
1851
2068
|
console.log(chalk.cyan(msg.syncingRefs));
|
|
1852
2069
|
console.log();
|
|
1853
2070
|
|
|
2071
|
+
// Rebuild the config when it is absent rather than sending the user away —
|
|
2072
|
+
// the manifest holds everything it is derived from.
|
|
2073
|
+
if (!manifest.integrationConfigs || Object.keys(manifest.integrationConfigs).length === 0) {
|
|
2074
|
+
const filled = backfillIntegrationConfigs(manifest);
|
|
2075
|
+
if (filled > 0) {
|
|
2076
|
+
console.log(chalk.gray(` ${(msg.integrationConfigsBackfilled || 'Rebuilt integration config for {count} file(s) from the manifest.').replace('{count}', filled)}`));
|
|
2077
|
+
console.log();
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
|
|
1854
2081
|
// Check if integrationConfigs exists
|
|
1855
2082
|
if (!manifest.integrationConfigs || Object.keys(manifest.integrationConfigs).length === 0) {
|
|
1856
2083
|
console.log(chalk.yellow(msg.noIntegrationConfigs));
|
|
@@ -1903,14 +2130,9 @@ async function syncIntegrationReferences(projectPath, manifest, { plan = false }
|
|
|
1903
2130
|
continue;
|
|
1904
2131
|
}
|
|
1905
2132
|
|
|
1906
|
-
// Regenerate the integration file with updated categories
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
tool: toolName,
|
|
1910
|
-
categories: expectedCategories,
|
|
1911
|
-
// Pass output_language for dynamic commit standards generation
|
|
1912
|
-
outputLanguage: manifest.options?.output_language || manifest.options?.commit_language || config.outputLanguage || config.commitLanguage || 'english'
|
|
1913
|
-
};
|
|
2133
|
+
// Regenerate the integration file with updated categories, from the
|
|
2134
|
+
// manifest rather than from the snapshot stored beside it.
|
|
2135
|
+
const newConfig = refreshIntegrationConfig(manifest, toolName, config, expectedCategories);
|
|
1914
2136
|
|
|
1915
2137
|
if (plan) {
|
|
1916
2138
|
console.log(chalk.yellow(` ~ ${integrationPath}: categories would change to ${expectedCategories.join(', ') || '(none)'} (dry run — nothing is written)`));
|
|
@@ -2159,7 +2381,7 @@ async function updateSkillsOnly(projectPath, manifest, options) {
|
|
|
2159
2381
|
return;
|
|
2160
2382
|
}
|
|
2161
2383
|
|
|
2162
|
-
const spinner =
|
|
2384
|
+
const spinner = createSpinner(msg.installingSkills || 'Installing Skills...').start();
|
|
2163
2385
|
|
|
2164
2386
|
const skillsLocaleForUpdate = resolveLocale(manifest, projectPath, options);
|
|
2165
2387
|
const result = await installSkillsToMultipleAgents(
|
|
@@ -2283,7 +2505,7 @@ async function updateCommandsOnly(projectPath, manifest, options) {
|
|
|
2283
2505
|
}
|
|
2284
2506
|
console.log();
|
|
2285
2507
|
|
|
2286
|
-
const spinner =
|
|
2508
|
+
const spinner = createSpinner(msg.installingCommands || 'Installing commands...').start();
|
|
2287
2509
|
|
|
2288
2510
|
const commandsLocale = resolveLocale(manifest, projectPath, options);
|
|
2289
2511
|
const result = await installCommandsToMultipleAgents(
|
|
@@ -2780,7 +3002,7 @@ async function handleRollback(projectPath) {
|
|
|
2780
3002
|
* Handle --plan: show what the reconciler would do without executing.
|
|
2781
3003
|
*/
|
|
2782
3004
|
async function handlePlan(projectPath, options) {
|
|
2783
|
-
const spinner =
|
|
3005
|
+
const spinner = createSpinner('Calculating reconciliation plan...').start();
|
|
2784
3006
|
|
|
2785
3007
|
const result = await reconcilerPlan(projectPath, { force: false });
|
|
2786
3008
|
|
|
@@ -2855,7 +3077,7 @@ async function handleReconcile(projectPath, options, { force }) {
|
|
|
2855
3077
|
}
|
|
2856
3078
|
|
|
2857
3079
|
// Execute
|
|
2858
|
-
const spinner =
|
|
3080
|
+
const spinner = createSpinner('Applying reconciliation plan...').start();
|
|
2859
3081
|
|
|
2860
3082
|
const result = await reconcile(projectPath, {
|
|
2861
3083
|
force,
|
package/src/i18n/messages.js
CHANGED
|
@@ -743,6 +743,9 @@ export const messages = {
|
|
|
743
743
|
unchanged: 'unchanged',
|
|
744
744
|
modified: 'modified',
|
|
745
745
|
missing: 'missing',
|
|
746
|
+
retired: 'no longer shipped by UDS',
|
|
747
|
+
retiredHeader: '{count} tracked file(s) are gone because UDS stopped shipping them. That is the migration completed, not a fault:',
|
|
748
|
+
retiredHint: 'Run `uds update` to drop their manifest records. `--restore` cannot bring them back — there is no source upstream.',
|
|
746
749
|
existsNoHash: 'exists, no hash',
|
|
747
750
|
summary: 'Summary: {unchanged} unchanged, {modified} modified, {missing} missing',
|
|
748
751
|
// Actions available
|
|
@@ -841,6 +844,13 @@ export const messages = {
|
|
|
841
844
|
refsInSync: '{path}: references in sync ({count} refs)',
|
|
842
845
|
noIntegrationRefs: 'No integration files with standard references found.',
|
|
843
846
|
runSyncRefs: 'Run `uds update --sync-refs` to fix reference issues.',
|
|
847
|
+
runIntegrationsOnly: 'Run `uds update --integrations-only` to regenerate integration files.',
|
|
848
|
+
danglingRefs: 'references files that are not on disk:',
|
|
849
|
+
brokenMentions: 'mentions paths that do not exist in this project:',
|
|
850
|
+
brokenMentionsFix: 'These are usually outside the UDS block, left by an older install. UDS does not rewrite text it did not write — correct them by hand or delete the line.',
|
|
851
|
+
danglingRefsFix: 'Run `uds update --integrations-only` to regenerate them.',
|
|
852
|
+
commandsUntracked: '{count} installed command file(s) are covered by no hash — integrity is not checked for them',
|
|
853
|
+
commandsUntrackedFix: 'Run `uds update --commands` to record them again.',
|
|
844
854
|
// CLI update
|
|
845
855
|
checkingCliUpdates: 'Checking for CLI updates...',
|
|
846
856
|
couldNotCheckUpdates: 'ℹ Could not check for CLI updates (offline mode)',
|
|
@@ -1020,6 +1030,7 @@ export const messages = {
|
|
|
1020
1030
|
syncingIntegrations: 'Syncing integration files...',
|
|
1021
1031
|
syncedIntegrations: 'Synced {count} integration files',
|
|
1022
1032
|
prunedOrphanedBlockHashes: 'Pruned {count} orphaned integration hash(es): {files}',
|
|
1033
|
+
droppedRetiredHashes: 'Dropped {count} manifest record(s) for files UDS no longer ships and that are already gone:',
|
|
1023
1034
|
// Success
|
|
1024
1035
|
updateSuccess: '✓ Standards updated successfully!',
|
|
1025
1036
|
versionUpdated: 'Version: {current} → {latest}',
|
|
@@ -1047,6 +1058,7 @@ export const messages = {
|
|
|
1047
1058
|
// Sync refs
|
|
1048
1059
|
syncingRefs: 'Syncing integration references...',
|
|
1049
1060
|
noIntegrationConfigs: '⚠ No integration configurations found in manifest.',
|
|
1061
|
+
integrationConfigsBackfilled: 'Rebuilt integration config for {count} file(s) from the manifest.',
|
|
1050
1062
|
integrationConfigsRequired: 'Integration configs are required for reference sync.',
|
|
1051
1063
|
thisHappensWhen: 'This happens when:',
|
|
1052
1064
|
oldVersion: '- The project was initialized with an older version of UDS',
|
|
@@ -1971,6 +1983,9 @@ export const messages = {
|
|
|
1971
1983
|
unchanged: '未變更',
|
|
1972
1984
|
modified: '已修改',
|
|
1973
1985
|
missing: '遺失',
|
|
1986
|
+
retired: 'UDS 已不再出貨',
|
|
1987
|
+
retiredHeader: '有 {count} 個被追蹤的檔案不見了,原因是 UDS 已不再出貨。那是遷移完成的樣子,不是故障:',
|
|
1988
|
+
retiredHint: '執行 `uds update` 清掉它們在 manifest 的紀錄。`--restore` 救不回來——上游已經沒有來源。',
|
|
1974
1989
|
existsNoHash: '存在,無 hash',
|
|
1975
1990
|
summary: '摘要:{unchanged} 未變更,{modified} 已修改,{missing} 遺失',
|
|
1976
1991
|
// Actions available
|
|
@@ -2069,6 +2084,13 @@ export const messages = {
|
|
|
2069
2084
|
refsInSync: '{path}:參考已同步({count} 個參考)',
|
|
2070
2085
|
noIntegrationRefs: '找不到有標準參考的整合檔案。',
|
|
2071
2086
|
runSyncRefs: '執行 `uds update --sync-refs` 修復參考問題。',
|
|
2087
|
+
runIntegrationsOnly: '執行 `uds update --integrations-only` 重新產生整合檔案。',
|
|
2088
|
+
danglingRefs: '引用了磁碟上不存在的檔案:',
|
|
2089
|
+
brokenMentions: '提到了本專案裡不存在的路徑:',
|
|
2090
|
+
brokenMentionsFix: '這些通常在 UDS 區塊之外,是舊版安裝留下的。UDS 不會改寫不是它寫的文字——請手動更正或刪掉那一行。',
|
|
2091
|
+
danglingRefsFix: '執行 `uds update --integrations-only` 重新產生。',
|
|
2092
|
+
commandsUntracked: '{count} 個已安裝的斜線命令沒有任何雜湊涵蓋——這些檔案的完整性沒有被檢查',
|
|
2093
|
+
commandsUntrackedFix: '執行 `uds update --commands` 重新記錄。',
|
|
2072
2094
|
// CLI update
|
|
2073
2095
|
checkingCliUpdates: '正在檢查 CLI 更新...',
|
|
2074
2096
|
couldNotCheckUpdates: 'ℹ 無法檢查 CLI 更新(離線模式)',
|
|
@@ -2245,6 +2267,7 @@ export const messages = {
|
|
|
2245
2267
|
syncingIntegrations: '同步整合檔案中...',
|
|
2246
2268
|
syncedIntegrations: '已同步 {count} 個整合檔案',
|
|
2247
2269
|
prunedOrphanedBlockHashes: '已清除 {count} 個孤兒整合雜湊:{files}',
|
|
2270
|
+
droppedRetiredHashes: '已清掉 {count} 筆 manifest 紀錄——這些檔案 UDS 已不再出貨,而且本來就已經不在了:',
|
|
2248
2271
|
// Success
|
|
2249
2272
|
updateSuccess: '✓ 標準更新成功!',
|
|
2250
2273
|
versionUpdated: '版本:{current} → {latest}',
|
|
@@ -2272,6 +2295,7 @@ export const messages = {
|
|
|
2272
2295
|
// Sync refs
|
|
2273
2296
|
syncingRefs: '同步整合參考中...',
|
|
2274
2297
|
noIntegrationConfigs: '⚠ manifest 中找不到整合設定。',
|
|
2298
|
+
integrationConfigsBackfilled: '已從 manifest 重建 {count} 個整合檔的設定。',
|
|
2275
2299
|
integrationConfigsRequired: '參考同步需要整合設定。',
|
|
2276
2300
|
thisHappensWhen: '這會發生在:',
|
|
2277
2301
|
oldVersion: '- 專案使用較舊版本的 UDS 初始化',
|
|
@@ -3211,6 +3235,9 @@ export const messages = {
|
|
|
3211
3235
|
unchanged: '未更改',
|
|
3212
3236
|
modified: '已修改',
|
|
3213
3237
|
missing: '缺失',
|
|
3238
|
+
retired: 'UDS 已不再发布',
|
|
3239
|
+
retiredHeader: '有 {count} 个被追踪的文件不见了,原因是 UDS 已不再发布。那是迁移完成的样子,不是故障:',
|
|
3240
|
+
retiredHint: '执行 `uds update` 清掉它们在 manifest 的记录。`--restore` 救不回来——上游已经没有来源。',
|
|
3214
3241
|
existsNoHash: '存在,无哈希',
|
|
3215
3242
|
summary: '摘要:{unchanged} 未更改,{modified} 已修改,{missing} 缺失',
|
|
3216
3243
|
// Actions available
|
|
@@ -3309,6 +3336,13 @@ export const messages = {
|
|
|
3309
3336
|
refsInSync: '{path}:引用已同步({count} 个引用)',
|
|
3310
3337
|
noIntegrationRefs: '未找到包含标准引用的集成文件。',
|
|
3311
3338
|
runSyncRefs: '执行 `uds update --sync-refs` 修复引用问题。',
|
|
3339
|
+
runIntegrationsOnly: '执行 `uds update --integrations-only` 重新生成集成文件。',
|
|
3340
|
+
danglingRefs: '引用了磁盘上不存在的文件:',
|
|
3341
|
+
brokenMentions: '提到了本项目里不存在的路径:',
|
|
3342
|
+
brokenMentionsFix: '这些通常在 UDS 区块之外,是旧版安装留下的。UDS 不会改写不是它写的文字——请手动更正或删掉那一行。',
|
|
3343
|
+
danglingRefsFix: '执行 `uds update --integrations-only` 重新生成。',
|
|
3344
|
+
commandsUntracked: '{count} 个已安装的斜杠命令没有任何哈希覆盖——这些文件的完整性没有被检查',
|
|
3345
|
+
commandsUntrackedFix: '执行 `uds update --commands` 重新记录。',
|
|
3312
3346
|
// CLI update
|
|
3313
3347
|
checkingCliUpdates: '检查 CLI 更新...',
|
|
3314
3348
|
couldNotCheckUpdates: 'ℹ 无法检查 CLI 更新(离线模式)',
|
|
@@ -3398,6 +3432,7 @@ export const messages = {
|
|
|
3398
3432
|
syncingIntegrations: '正在同步集成文件...',
|
|
3399
3433
|
syncedIntegrations: '已同步 {count} 个集成文件',
|
|
3400
3434
|
prunedOrphanedBlockHashes: '已清除 {count} 个孤儿集成哈希:{files}',
|
|
3435
|
+
droppedRetiredHashes: '已清掉 {count} 笔 manifest 记录——这些文件 UDS 已不再发布,而且本来就已经不在了:',
|
|
3401
3436
|
// Success
|
|
3402
3437
|
updateSuccess: '✓ 标准更新成功!',
|
|
3403
3438
|
versionUpdated: '版本:{current} → {latest}',
|
|
@@ -3425,6 +3460,7 @@ export const messages = {
|
|
|
3425
3460
|
// Sync refs
|
|
3426
3461
|
syncingRefs: '正在同步集成引用...',
|
|
3427
3462
|
noIntegrationConfigs: '⚠ manifest 中未找到集成配置。',
|
|
3463
|
+
integrationConfigsBackfilled: '已从 manifest 重建 {count} 个集成文件的配置。',
|
|
3428
3464
|
integrationConfigsRequired: '引用同步需要集成配置。',
|
|
3429
3465
|
thisHappensWhen: '发生这种情况时:',
|
|
3430
3466
|
oldVersion: '- 项目使用旧版 UDS 初始化',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { createSpinner } from '../utils/spinner.js';
|
|
2
2
|
import { copyIntegration } from '../utils/copier.js';
|
|
3
3
|
import {
|
|
4
4
|
writeIntegrationFile,
|
|
@@ -99,7 +99,7 @@ export async function installIntegrations(config, projectPath) {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
// Start installation spinner
|
|
102
|
-
const intSpinner =
|
|
102
|
+
const intSpinner = createSpinner(msg.generatingIntegrations).start();
|
|
103
103
|
|
|
104
104
|
// Track generated files to handle AGENTS.md sharing (codex + opencode)
|
|
105
105
|
const generatedFiles = new Set();
|
|
@@ -224,7 +224,7 @@ export async function generateClaudeMd(config, projectPath) {
|
|
|
224
224
|
return { path: null, error: null };
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
-
const claudeSpinner =
|
|
227
|
+
const claudeSpinner = createSpinner(msg.generatingClaudeMd).start();
|
|
228
228
|
|
|
229
229
|
// Derive categories from installedStandards to avoid referencing non-installed standards
|
|
230
230
|
const categories = installedStandards.length > 0
|
|
@@ -285,7 +285,7 @@ export async function generateUniversalAgentsMd(config, integrationResults, proj
|
|
|
285
285
|
}
|
|
286
286
|
|
|
287
287
|
const msg = t().commands.init;
|
|
288
|
-
const spinner =
|
|
288
|
+
const spinner = createSpinner(msg.generatingAgentsMd || 'Generating AGENTS.md...').start();
|
|
289
289
|
|
|
290
290
|
const outputLanguage = config.outputLanguage || config.commitLanguage ||
|
|
291
291
|
config.standardOptions?.output_language || config.standardOptions?.commit_language ||
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* @version 1.0.0
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import
|
|
16
|
+
import { createSpinner } from '../utils/spinner.js';
|
|
17
17
|
import chalk from 'chalk';
|
|
18
18
|
import {
|
|
19
19
|
installSkillsToMultipleAgents,
|
|
@@ -115,7 +115,7 @@ function getSkillFiles() {
|
|
|
115
115
|
export async function installSkills(skillsConfig, projectPath, messages, results) {
|
|
116
116
|
// Modern unified multi-agent installation
|
|
117
117
|
if (skillsConfig.needsInstall && skillsConfig.skillsInstallations?.length > 0) {
|
|
118
|
-
const skillSpinner =
|
|
118
|
+
const skillSpinner = createSpinner(messages.installingSkills).start();
|
|
119
119
|
|
|
120
120
|
// Use new unified installer for multi-agent support
|
|
121
121
|
const installResult = await installSkillsToMultipleAgents(
|
|
@@ -186,7 +186,7 @@ export async function installSkills(skillsConfig, projectPath, messages, results
|
|
|
186
186
|
* @returns {Promise<void>}
|
|
187
187
|
*/
|
|
188
188
|
async function installSkillsLegacy(skillsConfig, projectPath, messages, results) {
|
|
189
|
-
const skillSpinner =
|
|
189
|
+
const skillSpinner = createSpinner(messages.installingSkills).start();
|
|
190
190
|
|
|
191
191
|
const skillFiles = getSkillFiles();
|
|
192
192
|
let successCount = 0;
|
|
@@ -255,7 +255,7 @@ export async function installCommands(skillsConfig, projectPath, messages, resul
|
|
|
255
255
|
return;
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
-
const cmdSpinner =
|
|
258
|
+
const cmdSpinner = createSpinner(messages.installingCommands || 'Installing slash commands...').start();
|
|
259
259
|
|
|
260
260
|
const cmdResult = await installCommandsToMultipleAgents(
|
|
261
261
|
skillsConfig.commandsInstallations,
|