universal-dev-standards 6.9.0 → 6.11.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/bin/uds.js +2 -0
- 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 +49 -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/CHEATSHEET.md +1 -1
- package/bundled/locales/zh-CN/docs/CLI-INIT-OPTIONS.md +52 -5
- package/bundled/locales/zh-CN/docs/FEATURE-REFERENCE.md +3 -1
- package/bundled/locales/zh-CN/docs/MIGRATION-v6.md +8 -4
- package/bundled/locales/zh-TW/CHANGELOG.md +50 -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/CHEATSHEET.md +1 -1
- package/bundled/locales/zh-TW/docs/CLI-INIT-OPTIONS.md +52 -5
- package/bundled/locales/zh-TW/docs/FEATURE-REFERENCE.md +3 -1
- package/bundled/locales/zh-TW/docs/MIGRATION-v6.md +8 -4
- package/bundled/locales/zh-TW/integrations/claude-code/README.md +14 -5
- package/package.json +7 -6
- package/src/commands/check.js +413 -44
- package/src/commands/config.js +34 -28
- package/src/commands/init.js +37 -7
- package/src/commands/spec.js +2 -2
- package/src/commands/update.js +560 -74
- package/src/core/manifest.js +39 -1
- package/src/flows/init-flow.js +9 -1
- package/src/generators/layered-claudemd.js +13 -4
- package/src/i18n/messages.js +39 -3
- package/src/installers/integration-installer.js +17 -10
- package/src/installers/manifest-installer.js +4 -0
- 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/actual-state-scanner.js +29 -2
- package/src/reconciler/desired-state-calculator.js +51 -2
- package/src/reconciler/diff-engine.js +76 -5
- package/src/reconciler/plan-executor.js +48 -27
- package/src/utils/hasher.js +61 -5
- package/src/utils/integration-generator.js +431 -92
- package/src/utils/marker-locator.js +140 -0
- package/src/utils/reference-sync.js +156 -8
- package/src/utils/registry.js +57 -0
- package/src/utils/spinner.js +31 -0
- package/standards-registry.json +8 -8
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* Integration files use migrate_block: only the UDS marker block is replaced.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { isShippedFilename } from '../utils/registry.js';
|
|
18
|
+
|
|
17
19
|
/**
|
|
18
20
|
* @typedef {Object} PlanAction
|
|
19
21
|
* @property {'create'|'update'|'delete'|'migrate_block'|'patch_hook'} type
|
|
@@ -205,12 +207,18 @@ function diffCategory(desiredMap, actualMap, category, actions, warnings, summar
|
|
|
205
207
|
// Check actual entries not in desired → delete (only if UDS-managed)
|
|
206
208
|
for (const [key, actualEntry] of actualMap) {
|
|
207
209
|
if (!desiredMap.has(key)) {
|
|
208
|
-
if (
|
|
210
|
+
if (isUserConfigPath(actualEntry.relativePath)) {
|
|
211
|
+
// Said out loud rather than skipped quietly: a plan that silently omits
|
|
212
|
+
// things is how the previous round of deletion bugs stayed invisible.
|
|
213
|
+
warnings.push(
|
|
214
|
+
`Keeping ${actualEntry.relativePath}: it holds a setting you chose (uds config writes it), not content UDS ships`
|
|
215
|
+
);
|
|
216
|
+
} else if (isUDSManaged(actualEntry)) {
|
|
209
217
|
actions.push({
|
|
210
218
|
type: 'delete',
|
|
211
219
|
category,
|
|
212
220
|
path: actualEntry.relativePath,
|
|
213
|
-
reason:
|
|
221
|
+
reason: describeDeletion(actualEntry.relativePath, category),
|
|
214
222
|
details: {
|
|
215
223
|
currentHash: actualEntry.hash,
|
|
216
224
|
metadata: actualEntry.metadata
|
|
@@ -264,18 +272,34 @@ function diffIntegrations(desiredMap, actualMap, actions, warnings, summary, for
|
|
|
264
272
|
}
|
|
265
273
|
});
|
|
266
274
|
summary.migrate_block++;
|
|
275
|
+
} else if (desiredEntry.hash && actualEntry.hash && desiredEntry.hash === actualEntry.hash) {
|
|
276
|
+
// XSPEC adopter-report Q6: the block on disk already hashes the same
|
|
277
|
+
// as what generation would produce right now — nothing to do. Before
|
|
278
|
+
// this, every migrate_block fired unconditionally ("we always update
|
|
279
|
+
// integrations since content is generated dynamically"), so `--plan`
|
|
280
|
+
// reported `Migrate Block: N` forever, even immediately after
|
|
281
|
+
// `--apply` on an unchanged project. `desiredEntry.hash` is computed
|
|
282
|
+
// by desired-state-calculator.js actually generating the content
|
|
283
|
+
// ahead of time (see calculateIntegrations), the same generation
|
|
284
|
+
// `--apply` itself would run.
|
|
285
|
+
summary.unchanged++;
|
|
267
286
|
} else {
|
|
268
|
-
//
|
|
269
|
-
//
|
|
287
|
+
// Either the hashes genuinely differ, or one side has no hash to
|
|
288
|
+
// compare (generation failed, or an older/mocked desired state that
|
|
289
|
+
// never computed one) — in which case this falls back to the
|
|
290
|
+
// previous unconditional behavior rather than silently doing nothing.
|
|
270
291
|
actions.push({
|
|
271
292
|
type: 'migrate_block',
|
|
272
293
|
category: 'integration',
|
|
273
294
|
path: desiredEntry.relativePath,
|
|
274
|
-
reason:
|
|
295
|
+
reason: (desiredEntry.hash && actualEntry.hash)
|
|
296
|
+
? 'integration content differs from what would be generated'
|
|
297
|
+
: 'integration content may need update (no hash available for comparison)',
|
|
275
298
|
details: {
|
|
276
299
|
toolName: desiredEntry.metadata.toolName,
|
|
277
300
|
format: desiredEntry.metadata.format,
|
|
278
301
|
currentBlockHash: actualEntry.metadata?.blockHash?.blockHash,
|
|
302
|
+
desiredBlockHash: desiredEntry.hash,
|
|
279
303
|
metadata: desiredEntry.metadata
|
|
280
304
|
}
|
|
281
305
|
});
|
|
@@ -330,6 +354,53 @@ function diffIntegrations(desiredMap, actualMap, actions, warnings, summary, for
|
|
|
330
354
|
* Files in .standards/ are always UDS-managed.
|
|
331
355
|
* Skill/command entries from manifest installations are UDS-managed.
|
|
332
356
|
*/
|
|
357
|
+
/**
|
|
358
|
+
* Files under `.standards/` that hold a choice the user made, not content UDS
|
|
359
|
+
* ships.
|
|
360
|
+
*
|
|
361
|
+
* `release-config.yaml` is written by `uds init` from the release mode the user
|
|
362
|
+
* picked and rewritten by `uds config`. Nothing in the desired-state calculator
|
|
363
|
+
* models it, so it fell out of every diff as "no longer in desired state" and
|
|
364
|
+
* into the delete plan — a plan that discards a user setting and calls that
|
|
365
|
+
* reconciliation. Reported 2026-09-16 by an adopter who could not tell from the
|
|
366
|
+
* plan whether the file held anything of theirs. It did.
|
|
367
|
+
*
|
|
368
|
+
* @param {string} relativePath
|
|
369
|
+
* @returns {boolean}
|
|
370
|
+
*/
|
|
371
|
+
export function isUserConfigPath(relativePath) {
|
|
372
|
+
const normalized = String(relativePath || '').replace(/\\/g, '/');
|
|
373
|
+
return normalized === '.standards/release-config.yaml';
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Why this file is being deleted, in terms the adopter can act on.
|
|
378
|
+
*
|
|
379
|
+
* "no longer in desired state" was the reason given for a standard UDS retired
|
|
380
|
+
* upstream, an option the project deselected, and a path the desired state does
|
|
381
|
+
* not model — three different situations, three different right responses, one
|
|
382
|
+
* sentence. The distinction that matters most is the first one: if UDS still
|
|
383
|
+
* ships the file, the change came from this project and is reversible by
|
|
384
|
+
* re-selecting it; if UDS does not, it is gone upstream and re-selecting it will
|
|
385
|
+
* not bring it back.
|
|
386
|
+
*
|
|
387
|
+
* @param {string} relativePath
|
|
388
|
+
* @param {string} category - 'standard' | 'option' | 'skill' | 'command' | …
|
|
389
|
+
* @returns {string}
|
|
390
|
+
*/
|
|
391
|
+
export function describeDeletion(relativePath, category) {
|
|
392
|
+
const normalized = String(relativePath || '').replace(/\\/g, '/');
|
|
393
|
+
|
|
394
|
+
if (category === 'standard' || category === 'option') {
|
|
395
|
+
const fileName = normalized.split('/').pop();
|
|
396
|
+
return isShippedFilename(fileName)
|
|
397
|
+
? 'this project no longer selects it (UDS still ships it, so re-selecting restores it)'
|
|
398
|
+
: 'UDS no longer ships this file (re-selecting will not bring it back)';
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return `not part of what this project's manifest asks for (category: ${category})`;
|
|
402
|
+
}
|
|
403
|
+
|
|
333
404
|
function isUDSManaged(entry) {
|
|
334
405
|
// Files in .standards/ are always UDS-managed
|
|
335
406
|
if (entry.relativePath.startsWith('.standards/')) return true;
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
import { writeManifest } from '../core/manifest.js';
|
|
23
23
|
import { getRepositoryInfo } from '../utils/registry.js';
|
|
24
24
|
import { displayLanguageToLocale } from '../utils/locale.js';
|
|
25
|
-
import { computeFileHash } from '../utils/hasher.js';
|
|
25
|
+
import { computeFileHash, pruneIntegrationFileHashes } from '../utils/hasher.js';
|
|
26
26
|
import { createBackup, cleanupBackups } from './backup-manager.js';
|
|
27
27
|
|
|
28
28
|
/**
|
|
@@ -179,6 +179,11 @@ export async function executePlan(projectPath, plan, manifest, options = {}) {
|
|
|
179
179
|
// Write updated manifest
|
|
180
180
|
if (!dryRun) {
|
|
181
181
|
try {
|
|
182
|
+
// XSPEC-418 R6: catches any stale whole-file entry for an integration
|
|
183
|
+
// file this particular plan didn't touch (e.g. `--plan --skills` never
|
|
184
|
+
// reaches executeMigrateBlock), not just the one this run might
|
|
185
|
+
// otherwise have added.
|
|
186
|
+
pruneIntegrationFileHashes(updatedManifest);
|
|
182
187
|
writeManifest(updatedManifest, projectPath);
|
|
183
188
|
} catch (err) {
|
|
184
189
|
results.push({
|
|
@@ -351,21 +356,17 @@ function executeMigrateBlock(projectPath, action, manifest) {
|
|
|
351
356
|
if (result.blockHashInfo) {
|
|
352
357
|
manifest.integrationBlockHashes[result.path] = result.blockHashInfo;
|
|
353
358
|
}
|
|
354
|
-
//
|
|
355
|
-
//
|
|
356
|
-
//
|
|
357
|
-
//
|
|
358
|
-
//
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
installedAt: tracked.installedAt || new Date().toISOString()
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
}
|
|
359
|
+
// XSPEC-418 R6 supersedes the XSPEC-343 R2 fix this replaces. That fix
|
|
360
|
+
// refreshed a whole-file `fileHashes` entry here if one already existed,
|
|
361
|
+
// reasoning that leaving it stale would report the file "modified"
|
|
362
|
+
// forever. Refreshing it does stop that — but a whole-file hash for an
|
|
363
|
+
// integration file disagrees with reality the moment the adopter edits
|
|
364
|
+
// anything OUTSIDE the block, which the marker-based update above
|
|
365
|
+
// deliberately leaves alone; "modified" then fires for content UDS itself
|
|
366
|
+
// says it preserves, and contradicts the same run's block-integrity
|
|
367
|
+
// check. The correct fix is for this path not to be in `fileHashes` at
|
|
368
|
+
// all — it is tracked by the block hash above instead.
|
|
369
|
+
if (manifest.fileHashes) delete manifest.fileHashes[result.path];
|
|
369
370
|
return { action, success: true };
|
|
370
371
|
}
|
|
371
372
|
|
|
@@ -436,6 +437,22 @@ async function executeSkillBatch(projectPath, skillActions, manifest) {
|
|
|
436
437
|
return results;
|
|
437
438
|
}
|
|
438
439
|
|
|
440
|
+
/**
|
|
441
|
+
* Hash-map key for a command action: `<agent>/<file>`.
|
|
442
|
+
*
|
|
443
|
+
* `commandHashes` is keyed by agent and filename; a plan action carries a
|
|
444
|
+
* project-relative path (`.opencode/command/atdd.md`). Deleting by the wrong key
|
|
445
|
+
* leaves the entry behind and the next `check` reports the file missing.
|
|
446
|
+
*/
|
|
447
|
+
function commandHashKey(action) {
|
|
448
|
+
const meta = action.details?.metadata;
|
|
449
|
+
if (meta?.agent && meta?.commandName) return `${meta.agent}/${meta.commandName}.md`;
|
|
450
|
+
const parts = action.path.split('/').filter(Boolean);
|
|
451
|
+
const agent = parts[0] ? parts[0].replace(/^\./, '') : null;
|
|
452
|
+
const file = parts[parts.length - 1];
|
|
453
|
+
return agent && file ? `${agent}/${file}` : action.path;
|
|
454
|
+
}
|
|
455
|
+
|
|
439
456
|
/**
|
|
440
457
|
* Batch execute command installations.
|
|
441
458
|
*/
|
|
@@ -452,6 +469,9 @@ async function executeCommandBatch(projectPath, commandActions, manifest) {
|
|
|
452
469
|
if (existsSync(targetPath)) {
|
|
453
470
|
rmSync(targetPath, { recursive: true, force: true });
|
|
454
471
|
}
|
|
472
|
+
// A deleted file must also lose its hash entry, or the next check calls
|
|
473
|
+
// it missing for good.
|
|
474
|
+
delete manifest.commandHashes[commandHashKey(action)];
|
|
455
475
|
results.push({ action, success: true });
|
|
456
476
|
} catch (err) {
|
|
457
477
|
results.push({ action, success: false, error: err.message });
|
|
@@ -475,17 +495,18 @@ async function executeCommandBatch(projectPath, commandActions, manifest) {
|
|
|
475
495
|
);
|
|
476
496
|
|
|
477
497
|
if (installResult.allFileHashes) {
|
|
478
|
-
//
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
498
|
+
// Merge, exactly as the skill path above does.
|
|
499
|
+
//
|
|
500
|
+
// 🔴 This used to delete every key whose prefix matched an agent in the
|
|
501
|
+
// result before merging. That is correct only when the installer
|
|
502
|
+
// reinstalled ALL of that agent's commands — which `uds update`'s own
|
|
503
|
+
// call sites do (they pass `commandNames = null`). Here the plan names a
|
|
504
|
+
// subset, so the wipe threw away the entry for every command the plan did
|
|
505
|
+
// not mention. Measured on 6.9.0 (2026-09-16): a three-file drift turned
|
|
506
|
+
// 51 tracked commands into 3, the other 48 stayed on disk untracked, and
|
|
507
|
+
// `uds check` printed "All command files intact (3 files)" in the same run
|
|
508
|
+
// as "Commands: 51 installed" — tampering with one of the 48 changed
|
|
509
|
+
// neither line.
|
|
489
510
|
Object.assign(manifest.commandHashes, installResult.allFileHashes);
|
|
490
511
|
}
|
|
491
512
|
|
package/src/utils/hasher.js
CHANGED
|
@@ -4,6 +4,7 @@ import { join, relative } from 'path';
|
|
|
4
4
|
import { UDS_MARKERS } from '../core/constants.js';
|
|
5
5
|
import { resolveIntegrationFile } from '../core/constants.js';
|
|
6
6
|
import { isProvenanceEstablished } from '../core/manifest.js';
|
|
7
|
+
import { locateMarkerBlock, AmbiguousMarkerError } from './marker-locator.js';
|
|
7
8
|
|
|
8
9
|
// GitHub issue #155. `git config core.autocrlf true` (the common
|
|
9
10
|
// Windows default) rewrites LF to CRLF on checkout. The manifest's stored
|
|
@@ -472,13 +473,16 @@ function detectFormat(filePath) {
|
|
|
472
473
|
*/
|
|
473
474
|
function extractBlockContent(content, format) {
|
|
474
475
|
const markers = UDS_MARKERS[format] || UDS_MARKERS.markdown;
|
|
475
|
-
|
|
476
|
-
|
|
476
|
+
// XSPEC adopter-report Q5: locateMarkerBlock only counts a marker when it
|
|
477
|
+
// occupies a whole line by itself, not merely appears somewhere on one —
|
|
478
|
+
// see marker-locator.js for why raw indexOf broke on real files.
|
|
479
|
+
const block = locateMarkerBlock(content, markers);
|
|
477
480
|
|
|
478
|
-
if (
|
|
481
|
+
if (!block) {
|
|
479
482
|
return { before: content, blockContent: '', after: '' };
|
|
480
483
|
}
|
|
481
484
|
|
|
485
|
+
const { startIdx, endIdx } = block;
|
|
482
486
|
return {
|
|
483
487
|
before: content.substring(0, startIdx),
|
|
484
488
|
blockContent: content.substring(startIdx + markers.start.length, endIdx).trim(),
|
|
@@ -515,7 +519,15 @@ export function computeIntegrationBlockHash(filePath) {
|
|
|
515
519
|
fullHash: `sha256:${fullHash}`,
|
|
516
520
|
fullSize: Buffer.byteLength(content, 'utf-8')
|
|
517
521
|
};
|
|
518
|
-
} catch {
|
|
522
|
+
} catch (error) {
|
|
523
|
+
// XSPEC adopter-report Q5: an ambiguous marker pair (two real START or
|
|
524
|
+
// END lines) is a distinct, reportable condition — not "no markers
|
|
525
|
+
// found". Every other error (unreadable file, etc.) keeps the original
|
|
526
|
+
// silent-null behavior; callers that need to surface the ambiguity to a
|
|
527
|
+
// user must catch AmbiguousMarkerError explicitly (see check.js).
|
|
528
|
+
if (error instanceof AmbiguousMarkerError) {
|
|
529
|
+
throw error;
|
|
530
|
+
}
|
|
519
531
|
return null;
|
|
520
532
|
}
|
|
521
533
|
}
|
|
@@ -631,7 +643,14 @@ export function compareDirectoryHashes(dirPath, storedHashes, baseKey = '') {
|
|
|
631
643
|
|
|
632
644
|
/**
|
|
633
645
|
* Refresh all integrationBlockHashes in manifest by recalculating from disk
|
|
634
|
-
* Ensures manifest hashes always match actual file content
|
|
646
|
+
* Ensures manifest hashes always match actual file content.
|
|
647
|
+
*
|
|
648
|
+
* Also prunes any `fileHashes` entry for the same paths (XSPEC-418 R6) — every
|
|
649
|
+
* call site of this function is a "we just wrote/restored an integration file,
|
|
650
|
+
* about to persist the manifest" checkpoint, which is exactly where a stale
|
|
651
|
+
* whole-file hash for that same path (written by an older CLI, or a write path
|
|
652
|
+
* this fix missed) needs to stop existing. See `pruneIntegrationFileHashes`.
|
|
653
|
+
*
|
|
635
654
|
* @param {Object} manifest - Manifest object (mutated in place)
|
|
636
655
|
* @param {string} projectPath - Project root path
|
|
637
656
|
* @returns {Object} The updated manifest
|
|
@@ -652,5 +671,42 @@ export function refreshIntegrationBlockHashes(manifest, projectPath) {
|
|
|
652
671
|
}
|
|
653
672
|
}
|
|
654
673
|
|
|
674
|
+
pruneIntegrationFileHashes(manifest);
|
|
675
|
+
|
|
655
676
|
return manifest;
|
|
656
677
|
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Remove `fileHashes` entries for files UDS tracks by their UDS block instead
|
|
681
|
+
* (`integrationBlockHashes`) — CLAUDE.md, CLAUDE.local.md, AGENTS.md, GEMINI.md,
|
|
682
|
+
* etc. (XSPEC-418 R6).
|
|
683
|
+
*
|
|
684
|
+
* An integration file's whole-file hash and its block hash disagree the
|
|
685
|
+
* moment an adopter edits anything OUTSIDE the UDS block — exactly the
|
|
686
|
+
* customization UDS's marker-based update promises to preserve. Several write
|
|
687
|
+
* paths (`uds update`, `uds update --integrations-only`, `uds check
|
|
688
|
+
* --restore`, `uds check --migrate`) used to add a whole-file entry for these
|
|
689
|
+
* paths anyway, so `uds check --ci` could report "CLAUDE.md (modified)" from
|
|
690
|
+
* standards-file integrity in the same run its own block-integrity check said
|
|
691
|
+
* the block was intact — the two checks contradicted each other, and the one
|
|
692
|
+
* that failed was the one punishing content UDS says it preserves.
|
|
693
|
+
*
|
|
694
|
+
* `manifest.integrationBlockHashes` is the authoritative registry of which
|
|
695
|
+
* paths are integration files — every writer of it (`writeIntegrationFile`,
|
|
696
|
+
* `writeAgentsMdSummary`) sets an entry there and nowhere else, so keying off
|
|
697
|
+
* its keys needs no second list of "known" integration files to keep in sync.
|
|
698
|
+
*
|
|
699
|
+
* @param {Object} manifest - Manifest object (mutated in place)
|
|
700
|
+
* @returns {string[]} Paths whose stale whole-file hash was removed
|
|
701
|
+
*/
|
|
702
|
+
export function pruneIntegrationFileHashes(manifest) {
|
|
703
|
+
if (!manifest?.fileHashes || !manifest?.integrationBlockHashes) return [];
|
|
704
|
+
const removed = [];
|
|
705
|
+
for (const path of Object.keys(manifest.integrationBlockHashes)) {
|
|
706
|
+
if (path in manifest.fileHashes) {
|
|
707
|
+
delete manifest.fileHashes[path];
|
|
708
|
+
removed.push(path);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
return removed;
|
|
712
|
+
}
|