@msn-control/liftoff 0.10.1 → 0.10.3
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/DEVELOPER.md +8 -1
- package/README.md +11 -0
- package/dist/artifact-lifecycle.d.ts +18 -1
- package/dist/artifact-lifecycle.js +34 -3
- package/dist/artifact-lifecycle.js.map +1 -1
- package/dist/commands.js +100 -8
- package/dist/commands.js.map +1 -1
- package/dist/file-system.js +64 -31
- package/dist/file-system.js.map +1 -1
- package/dist/governance-activation/commands.js +127 -7
- package/dist/governance-activation/commands.js.map +1 -1
- package/dist/governance-activation/readiness.d.ts +1 -0
- package/dist/governance-activation/readiness.js +74 -11
- package/dist/governance-activation/readiness.js.map +1 -1
- package/dist/governance-activation/seed-lifecycle.d.ts +18 -0
- package/dist/governance-activation/seed-lifecycle.js +111 -4
- package/dist/governance-activation/seed-lifecycle.js.map +1 -1
- package/dist/governance-activation/transitions.d.ts +1 -0
- package/dist/governance-activation/transitions.js +25 -3
- package/dist/governance-activation/transitions.js.map +1 -1
- package/dist/power-apps-templates.js +1 -1
- package/dist/power-apps-templates.js.map +1 -1
- package/dist/reconcile.d.ts +1 -1
- package/dist/reconcile.js +42 -0
- package/dist/reconcile.js.map +1 -1
- package/dist/repository-governance.d.ts +0 -4
- package/dist/repository-governance.js +19 -55
- package/dist/repository-governance.js.map +1 -1
- package/dist/templates.js +28 -6
- package/dist/templates.js.map +1 -1
- package/docs/cli-reference.md +10 -8
- package/docs/configuration-and-manifests.md +3 -2
- package/docs/existing-repositories.md +2 -2
- package/docs/project-structure.md +4 -6
- package/docs/repository-governance.md +16 -9
- package/docs/safety-and-consent.md +6 -5
- package/docs/troubleshooting.md +3 -2
- package/docs/workloads.md +2 -2
- package/package.json +1 -1
package/DEVELOPER.md
CHANGED
|
@@ -82,6 +82,12 @@ changes in another.
|
|
|
82
82
|
declared capability specs, and must pass strict validation immediately.
|
|
83
83
|
- Copilot and Claude `/liftoff-setup` integrations must be behaviorally
|
|
84
84
|
equivalent, command-only, model-agnostic, and free of skill-version fields.
|
|
85
|
+
They must distinguish the read-only `apply-next --json` preview from
|
|
86
|
+
`apply-next --json --execute`, and execute only when approval status is
|
|
87
|
+
`not-required` or `reused`.
|
|
88
|
+
- Governance verification must report state consistency separately from setup
|
|
89
|
+
progress. A consistent `not-started` or `in-progress` result is not complete;
|
|
90
|
+
completion requires every phase to have a successful terminal state.
|
|
85
91
|
- Credential tests must prove PAT/App policy equivalence, exact workflow/job
|
|
86
92
|
allowlists, masked input, no command-argument/file/log/evidence leaks, and
|
|
87
93
|
compromised revoke/rotate guidance.
|
|
@@ -95,7 +101,8 @@ Run the smallest focused command while iterating:
|
|
|
95
101
|
```bash
|
|
96
102
|
npx vitest run tests/documentation.test.ts
|
|
97
103
|
npx vitest run tests/templates.test.ts tests/repository-governance.test.ts
|
|
98
|
-
npx vitest run tests/governance-activation.test.ts tests/governance-credentials.test.ts
|
|
104
|
+
npx vitest run tests/governance-activation.test.ts tests/governance-commands.test.ts tests/governance-credentials.test.ts
|
|
105
|
+
npx vitest run tests/seed-lifecycle.test.ts
|
|
99
106
|
npm run build
|
|
100
107
|
openspec validate add-deterministic-liftoff-setup --strict
|
|
101
108
|
```
|
package/README.md
CHANGED
|
@@ -52,6 +52,17 @@ environments, documentation, and infrastructure are project-owned after
|
|
|
52
52
|
generation and remain outside every update mode, including `--force`. Use
|
|
53
53
|
`liftoff update --check --json` for a read-only core-maintenance gate.
|
|
54
54
|
|
|
55
|
+
Projects generated before `0.10.2` may still display the retired
|
|
56
|
+
`/liftoff-repository-governance` command. Review `liftoff update --check`, then
|
|
57
|
+
remove that generated alias with:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
liftoff upgrade
|
|
61
|
+
liftoff update --force
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Reload the coding-agent session afterward so its command index refreshes.
|
|
65
|
+
|
|
55
66
|

|
|
56
67
|
|
|
57
68
|
## One flow, three workloads
|
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
import type { ProjectProvisioningGroup } from './types.js';
|
|
2
|
-
export declare const managedCoreLogicalNames: readonly ['repository-governance-policy', 'repository-governance-context', 'repository-governance-guide', 'repository-governance-phase-graph', 'repository-governance-compatibility', 'repository-governance-credential-policy-schema', 'liftoff-setup-copilot', 'liftoff-setup-claude'
|
|
2
|
+
export declare const managedCoreLogicalNames: readonly ['repository-governance-policy', 'repository-governance-context', 'repository-governance-guide', 'repository-governance-phase-graph', 'repository-governance-compatibility', 'repository-governance-credential-policy-schema', 'liftoff-setup-copilot', 'liftoff-setup-claude'];
|
|
3
3
|
export declare function isManagedCoreLogicalName(logicalName: string): boolean;
|
|
4
|
+
export declare const retiredManagedCoreIdentities: readonly [{
|
|
5
|
+
readonly logicalName: 'repository-governance-copilot-launcher';
|
|
6
|
+
readonly category: 'governance';
|
|
7
|
+
readonly pathParts: readonly ['.github', 'prompts', 'liftoff-repository-governance.prompt.md'];
|
|
8
|
+
readonly replacementLogicalName: 'liftoff-setup-copilot';
|
|
9
|
+
}, {
|
|
10
|
+
readonly logicalName: 'repository-governance-claude-launcher';
|
|
11
|
+
readonly category: 'governance';
|
|
12
|
+
readonly pathParts: readonly ['.claude', 'commands', 'liftoff-repository-governance.md'];
|
|
13
|
+
readonly replacementLogicalName: 'liftoff-setup-claude';
|
|
14
|
+
}];
|
|
15
|
+
export type RetiredManagedCoreIdentity = (typeof retiredManagedCoreIdentities)[number];
|
|
16
|
+
export declare const retiredManagedCoreIdentityMap: ReadonlyMap<string, RetiredManagedCoreIdentity>;
|
|
17
|
+
export declare function isRetiredManagedCoreLogicalName(logicalName: string): boolean;
|
|
18
|
+
export declare function isUnknownRetiredManagedCoreAliasLogicalName(logicalName: string): boolean;
|
|
19
|
+
export declare function retiredManagedCoreIdentityFor(logicalName: string): RetiredManagedCoreIdentity | undefined;
|
|
20
|
+
export declare function isRetiredManagedCoreArtifactIdentity(logicalName: string, category: string, pathParts: readonly string[]): boolean;
|
|
4
21
|
export declare function legacyProvisioningGroup(logicalName: string): ProjectProvisioningGroup;
|
|
@@ -7,14 +7,45 @@ export const managedCoreLogicalNames = [
|
|
|
7
7
|
'repository-governance-compatibility',
|
|
8
8
|
'repository-governance-credential-policy-schema',
|
|
9
9
|
'liftoff-setup-copilot',
|
|
10
|
-
'liftoff-setup-claude'
|
|
11
|
-
'repository-governance-copilot-launcher',
|
|
12
|
-
'repository-governance-claude-launcher'
|
|
10
|
+
'liftoff-setup-claude'
|
|
13
11
|
];
|
|
14
12
|
const managedCoreLogicalNameSet = new Set(managedCoreLogicalNames);
|
|
15
13
|
export function isManagedCoreLogicalName(logicalName) {
|
|
16
14
|
return managedCoreLogicalNameSet.has(logicalName);
|
|
17
15
|
}
|
|
16
|
+
export const retiredManagedCoreIdentities = [
|
|
17
|
+
{
|
|
18
|
+
logicalName: 'repository-governance-copilot-launcher',
|
|
19
|
+
category: 'governance',
|
|
20
|
+
pathParts: ['.github', 'prompts', 'liftoff-repository-governance.prompt.md'],
|
|
21
|
+
replacementLogicalName: 'liftoff-setup-copilot'
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
logicalName: 'repository-governance-claude-launcher',
|
|
25
|
+
category: 'governance',
|
|
26
|
+
pathParts: ['.claude', 'commands', 'liftoff-repository-governance.md'],
|
|
27
|
+
replacementLogicalName: 'liftoff-setup-claude'
|
|
28
|
+
}
|
|
29
|
+
];
|
|
30
|
+
export const retiredManagedCoreIdentityMap = new Map(retiredManagedCoreIdentities.map((identity) => [identity.logicalName, identity]));
|
|
31
|
+
const retiredManagedCoreLogicalNameSet = new Set(retiredManagedCoreIdentities.map((identity) => identity.logicalName));
|
|
32
|
+
export function isRetiredManagedCoreLogicalName(logicalName) {
|
|
33
|
+
return retiredManagedCoreLogicalNameSet.has(logicalName);
|
|
34
|
+
}
|
|
35
|
+
export function isUnknownRetiredManagedCoreAliasLogicalName(logicalName) {
|
|
36
|
+
return (/^repository-governance(?:-.+)?-launcher$/.test(logicalName) ||
|
|
37
|
+
logicalName === 'liftoff-repository-governance') &&
|
|
38
|
+
!isRetiredManagedCoreLogicalName(logicalName);
|
|
39
|
+
}
|
|
40
|
+
export function retiredManagedCoreIdentityFor(logicalName) {
|
|
41
|
+
return retiredManagedCoreIdentityMap.get(logicalName);
|
|
42
|
+
}
|
|
43
|
+
export function isRetiredManagedCoreArtifactIdentity(logicalName, category, pathParts) {
|
|
44
|
+
const identity = retiredManagedCoreIdentityFor(logicalName);
|
|
45
|
+
return identity !== undefined &&
|
|
46
|
+
category === identity.category &&
|
|
47
|
+
pathParts.join('\0') === identity.pathParts.join('\0');
|
|
48
|
+
}
|
|
18
49
|
const frontendLogicalNames = new Set([
|
|
19
50
|
'frontend-package',
|
|
20
51
|
'frontend-lock',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"artifact-lifecycle.js","sourceRoot":"","sources":["../src/artifact-lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAG7C,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,8BAA8B;IAC9B,+BAA+B;IAC/B,6BAA6B;IAC7B,mCAAmC;IACnC,qCAAqC;IACrC,gDAAgD;IAChD,uBAAuB;IACvB,sBAAsB;
|
|
1
|
+
{"version":3,"file":"artifact-lifecycle.js","sourceRoot":"","sources":["../src/artifact-lifecycle.ts"],"names":[],"mappings":"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"}
|
package/dist/commands.js
CHANGED
|
@@ -860,7 +860,20 @@ async function migrateCommand(parsed, context) {
|
|
|
860
860
|
return withUnchangedMigrationSource(sourceRoot, () => executeMigration(parsed, context, sourceRoot));
|
|
861
861
|
}
|
|
862
862
|
function summarizeEntries(entries) {
|
|
863
|
-
const summary = {
|
|
863
|
+
const summary = {
|
|
864
|
+
new: 0,
|
|
865
|
+
missing: 0,
|
|
866
|
+
upgrade: 0,
|
|
867
|
+
conflict: 0,
|
|
868
|
+
moved: 0,
|
|
869
|
+
orphan: 0,
|
|
870
|
+
retired: 0,
|
|
871
|
+
retiredRemoved: 0,
|
|
872
|
+
retiredAbsent: 0,
|
|
873
|
+
retiredConflict: 0,
|
|
874
|
+
refresh: 0,
|
|
875
|
+
unchanged: 0
|
|
876
|
+
};
|
|
864
877
|
for (const entry of entries) {
|
|
865
878
|
if (entry.status === 'unchanged') {
|
|
866
879
|
if (entry.refreshHash) {
|
|
@@ -871,6 +884,20 @@ function summarizeEntries(entries) {
|
|
|
871
884
|
}
|
|
872
885
|
continue;
|
|
873
886
|
}
|
|
887
|
+
if (entry.status === 'retired-conflict') {
|
|
888
|
+
summary.retiredConflict += 1;
|
|
889
|
+
continue;
|
|
890
|
+
}
|
|
891
|
+
if (entry.status === 'retired') {
|
|
892
|
+
summary.retired += 1;
|
|
893
|
+
if (entry.destinationOccupied === false) {
|
|
894
|
+
summary.retiredAbsent += 1;
|
|
895
|
+
}
|
|
896
|
+
else {
|
|
897
|
+
summary.retiredRemoved += 1;
|
|
898
|
+
}
|
|
899
|
+
continue;
|
|
900
|
+
}
|
|
874
901
|
if (entry.status === 'moved' && !entry.cleanMove) {
|
|
875
902
|
summary.conflict += 1;
|
|
876
903
|
continue;
|
|
@@ -891,7 +918,10 @@ function entryMarker(entry) {
|
|
|
891
918
|
case 'moved':
|
|
892
919
|
return entry.cleanMove ? '>' : '!';
|
|
893
920
|
case 'orphan':
|
|
921
|
+
case 'retired':
|
|
894
922
|
return '-';
|
|
923
|
+
case 'retired-conflict':
|
|
924
|
+
return '!';
|
|
895
925
|
default:
|
|
896
926
|
return '~';
|
|
897
927
|
}
|
|
@@ -1061,9 +1091,14 @@ async function preflightUpdate(projectRoot, entries, force) {
|
|
|
1061
1091
|
entry.status === 'upgrade' ||
|
|
1062
1092
|
entry.status === 'moved' && (entry.cleanMove === true || force) ||
|
|
1063
1093
|
entry.status === 'conflict' && force;
|
|
1094
|
+
const deletesDestination = entry.status === 'retired' ||
|
|
1095
|
+
entry.status === 'retired-conflict' && force;
|
|
1064
1096
|
if (writesDestination) {
|
|
1065
1097
|
await resolveProjectPath(projectRoot, entry.pathParts);
|
|
1066
1098
|
}
|
|
1099
|
+
if (deletesDestination) {
|
|
1100
|
+
await resolveProjectPath(projectRoot, entry.pathParts);
|
|
1101
|
+
}
|
|
1067
1102
|
if (entry.previousPathParts &&
|
|
1068
1103
|
(entry.status === 'moved' && (entry.cleanMove === true || force) || entry.status === 'conflict' && force)) {
|
|
1069
1104
|
await resolveProjectPath(projectRoot, entry.previousPathParts);
|
|
@@ -1487,6 +1522,7 @@ async function updateCommand(parsed, context) {
|
|
|
1487
1522
|
cliVersion: liftoffVersion,
|
|
1488
1523
|
projectVersion: manifest.liftoffVersion,
|
|
1489
1524
|
entries: [],
|
|
1525
|
+
removed: [],
|
|
1490
1526
|
provisioning: [],
|
|
1491
1527
|
ownershipMigrationPending: false,
|
|
1492
1528
|
manifestChanges: plannedManifestChanges,
|
|
@@ -1540,6 +1576,7 @@ async function updateCommand(parsed, context) {
|
|
|
1540
1576
|
cliVersion: liftoffVersion,
|
|
1541
1577
|
projectVersion: manifest.liftoffVersion,
|
|
1542
1578
|
written: [],
|
|
1579
|
+
removed: [],
|
|
1543
1580
|
skipped: [],
|
|
1544
1581
|
provisioning: [],
|
|
1545
1582
|
ownershipMigrationPending,
|
|
@@ -1573,6 +1610,9 @@ async function updateCommand(parsed, context) {
|
|
|
1573
1610
|
status: entry.status,
|
|
1574
1611
|
path: manifestDisplayPath(entry.pathParts),
|
|
1575
1612
|
previousPath: entry.previousPathParts ? manifestDisplayPath(entry.previousPathParts) : undefined,
|
|
1613
|
+
fileDeleted: entry.status === 'retired' || entry.status === 'retired-conflict'
|
|
1614
|
+
? entry.destinationOccupied !== false
|
|
1615
|
+
: undefined,
|
|
1576
1616
|
reason: entry.reason
|
|
1577
1617
|
})),
|
|
1578
1618
|
provisioning: provisioningJson(provisioningPlans),
|
|
@@ -1635,11 +1675,12 @@ async function updateCommand(parsed, context) {
|
|
|
1635
1675
|
: group.entries.filter((entry) => entry.status === 'create').length), 0) +
|
|
1636
1676
|
(manifestRewritePending ? 1 : 0) +
|
|
1637
1677
|
stateMigration.mutations.length;
|
|
1638
|
-
|
|
1639
|
-
|
|
1678
|
+
const toRetire = summary.retired;
|
|
1679
|
+
presentation.status('warning', 'Liftoff core maintenance available', `${toWrite} to write, ${toRetire} retired alias ownership record(s) to remove (${summary.retiredRemoved} file deletion(s)), ${summary.conflict + summary.retiredConflict} core conflict(s), ${summary.orphan} core orphan(s), ${summary.unchanged} core unchanged`);
|
|
1680
|
+
if (toWrite > 0 || toRetire > 0) {
|
|
1640
1681
|
presentation.command('liftoff update');
|
|
1641
1682
|
}
|
|
1642
|
-
if (summary.conflict > 0) {
|
|
1683
|
+
if (summary.conflict > 0 || summary.retiredConflict > 0) {
|
|
1643
1684
|
presentation.command('liftoff update --force');
|
|
1644
1685
|
}
|
|
1645
1686
|
return 2;
|
|
@@ -1658,6 +1699,7 @@ async function updateCommand(parsed, context) {
|
|
|
1658
1699
|
await preflightUpdate(projectRoot, entries, force);
|
|
1659
1700
|
maybeInjectUpdateFailure(context.env, 'after-preflight');
|
|
1660
1701
|
const written = [];
|
|
1702
|
+
const retired = [];
|
|
1661
1703
|
const skipped = [];
|
|
1662
1704
|
const mutations = [];
|
|
1663
1705
|
for (const entry of entries) {
|
|
@@ -1688,6 +1730,19 @@ async function updateCommand(parsed, context) {
|
|
|
1688
1730
|
skipped.push(entry);
|
|
1689
1731
|
}
|
|
1690
1732
|
break;
|
|
1733
|
+
case 'retired':
|
|
1734
|
+
mutations.push({ type: 'delete', pathParts: entry.pathParts });
|
|
1735
|
+
retired.push(entry);
|
|
1736
|
+
break;
|
|
1737
|
+
case 'retired-conflict':
|
|
1738
|
+
if (force) {
|
|
1739
|
+
mutations.push({ type: 'delete', pathParts: entry.pathParts });
|
|
1740
|
+
retired.push(entry);
|
|
1741
|
+
}
|
|
1742
|
+
else {
|
|
1743
|
+
skipped.push(entry);
|
|
1744
|
+
}
|
|
1745
|
+
break;
|
|
1691
1746
|
case 'conflict':
|
|
1692
1747
|
if (force) {
|
|
1693
1748
|
mutations.push({
|
|
@@ -1732,6 +1787,7 @@ async function updateCommand(parsed, context) {
|
|
|
1732
1787
|
const hasUnrecordedGovernanceConflict = skipped.some((entry) => entry.status === 'conflict' &&
|
|
1733
1788
|
entry.rendered?.category === 'governance' &&
|
|
1734
1789
|
!oldByName.has(entry.logicalName));
|
|
1790
|
+
const hasProtectedRetiredAlias = skipped.some((entry) => entry.status === 'retired-conflict');
|
|
1735
1791
|
const nextProjectArtifacts = appendProvisionedProjectArtifacts(manifest.projectArtifacts, provisioningPlans);
|
|
1736
1792
|
const nextManifest = buildManifest(renderPlan, render.filter((artifact) => artifact.logicalName !== 'manifest'), {
|
|
1737
1793
|
frameworkState: manifest.framework.state,
|
|
@@ -1746,7 +1802,7 @@ async function updateCommand(parsed, context) {
|
|
|
1746
1802
|
else {
|
|
1747
1803
|
delete nextManifest.project.defaultAgent;
|
|
1748
1804
|
}
|
|
1749
|
-
if (hasUnrecordedGovernanceConflict &&
|
|
1805
|
+
if ((hasUnrecordedGovernanceConflict || hasProtectedRetiredAlias) &&
|
|
1750
1806
|
nextManifest.governance.profile !== 'none') {
|
|
1751
1807
|
nextManifest.governance.state = 'handoff-partial';
|
|
1752
1808
|
}
|
|
@@ -1769,6 +1825,14 @@ async function updateCommand(parsed, context) {
|
|
|
1769
1825
|
}
|
|
1770
1826
|
nextManifest.managedArtifacts.push(previous);
|
|
1771
1827
|
}
|
|
1828
|
+
if (entry.status === 'retired-conflict' && !force) {
|
|
1829
|
+
const previous = oldByName.get(entry.logicalName);
|
|
1830
|
+
if (plan.governanceProfile.id === 'none' &&
|
|
1831
|
+
previous.category === 'governance') {
|
|
1832
|
+
continue;
|
|
1833
|
+
}
|
|
1834
|
+
nextManifest.managedArtifacts.push(previous);
|
|
1835
|
+
}
|
|
1772
1836
|
}
|
|
1773
1837
|
mutations.push({
|
|
1774
1838
|
type: 'write',
|
|
@@ -1791,7 +1855,7 @@ async function updateCommand(parsed, context) {
|
|
|
1791
1855
|
schemaVersion: 2,
|
|
1792
1856
|
mode: 'apply',
|
|
1793
1857
|
scope: 'managed-core',
|
|
1794
|
-
status: blockedProvisioning.length > 0
|
|
1858
|
+
status: blockedProvisioning.length > 0 || skipped.length > 0
|
|
1795
1859
|
? 'partial'
|
|
1796
1860
|
: reconciliation.status === 'reconciliation-required'
|
|
1797
1861
|
? 'reconciliation-required'
|
|
@@ -1799,8 +1863,24 @@ async function updateCommand(parsed, context) {
|
|
|
1799
1863
|
cliVersion: liftoffVersion,
|
|
1800
1864
|
projectVersion: manifest.liftoffVersion,
|
|
1801
1865
|
written: written.map((entry) => manifestDisplayPath(entry.pathParts)),
|
|
1866
|
+
removed: retired.map((entry) => ({
|
|
1867
|
+
logicalName: entry.logicalName,
|
|
1868
|
+
status: entry.status === 'retired-conflict'
|
|
1869
|
+
? 'force-retired'
|
|
1870
|
+
: entry.destinationOccupied === false
|
|
1871
|
+
? 'retired-absent'
|
|
1872
|
+
: 'retired',
|
|
1873
|
+
path: manifestDisplayPath(entry.pathParts),
|
|
1874
|
+
fileDeleted: entry.destinationOccupied !== false,
|
|
1875
|
+
reason: entry.reason
|
|
1876
|
+
})),
|
|
1802
1877
|
stateWritten: stateMigration.mutations.map((mutation) => manifestDisplayPath(mutation.pathParts)),
|
|
1803
|
-
skipped: skipped.map((entry) => ({
|
|
1878
|
+
skipped: skipped.map((entry) => ({
|
|
1879
|
+
logicalName: entry.logicalName,
|
|
1880
|
+
status: entry.status,
|
|
1881
|
+
path: manifestDisplayPath(entry.pathParts),
|
|
1882
|
+
reason: entry.reason
|
|
1883
|
+
})),
|
|
1804
1884
|
provisioning: provisioningJson(provisioningPlans),
|
|
1805
1885
|
ownershipMigrationPending,
|
|
1806
1886
|
manifestChanges: manifestChanges(manifest, nextManifest),
|
|
@@ -1816,6 +1896,14 @@ async function updateCommand(parsed, context) {
|
|
|
1816
1896
|
if (written.length > 0) {
|
|
1817
1897
|
presentation.bullets('Applied Liftoff core changes', written.map((entry) => `wrote ${entryDisplay(entry)}`));
|
|
1818
1898
|
}
|
|
1899
|
+
const retiredDeleted = retired.filter((entry) => entry.destinationOccupied !== false);
|
|
1900
|
+
const retiredAbsent = retired.filter((entry) => entry.destinationOccupied === false);
|
|
1901
|
+
if (retiredDeleted.length > 0) {
|
|
1902
|
+
presentation.bullets('Removed retired Liftoff aliases', retiredDeleted.map((entry) => `removed ${entryDisplay(entry)} ${entry.reason}`));
|
|
1903
|
+
}
|
|
1904
|
+
if (retiredAbsent.length > 0) {
|
|
1905
|
+
presentation.bullets('Retired absent Liftoff alias ownership', retiredAbsent.map((entry) => `removed manifest ownership for ${entryDisplay(entry)} ${entry.reason}`));
|
|
1906
|
+
}
|
|
1819
1907
|
if (stateMigration.status === 'migrate') {
|
|
1820
1908
|
presentation.bullets('Migrated activation state', stateMigration.mutations.map((mutation) => `wrote ${manifestDisplayPath(mutation.pathParts)}; evidence bytes preserved`));
|
|
1821
1909
|
}
|
|
@@ -1828,7 +1916,9 @@ async function updateCommand(parsed, context) {
|
|
|
1828
1916
|
.map((entry) => `${group.group}: preserved ${manifestDisplayPath(entry.rendered.pathParts)}; --force cannot overwrite project-owned bytes`)));
|
|
1829
1917
|
}
|
|
1830
1918
|
if (skipped.length > 0) {
|
|
1831
|
-
presentation.bullets('Skipped Liftoff core conflicts', skipped.map((entry) =>
|
|
1919
|
+
presentation.bullets('Skipped Liftoff core conflicts', skipped.map((entry) => entry.status === 'retired-conflict'
|
|
1920
|
+
? `protected retired alias ${entryDisplay(entry)} ${entry.reason}${force ? '' : ' (delete it manually or use --force to remove)'}`
|
|
1921
|
+
: `skipped ${entryDisplay(entry)} ${entry.reason}${force ? '' : ' (use --force to overwrite)'}`));
|
|
1832
1922
|
}
|
|
1833
1923
|
const orphans = entries.filter((entry) => entry.status === 'orphan');
|
|
1834
1924
|
if (orphans.length > 0) {
|
|
@@ -1843,6 +1933,8 @@ async function updateCommand(parsed, context) {
|
|
|
1843
1933
|
}
|
|
1844
1934
|
presentation.completion('Updated project', [
|
|
1845
1935
|
`${written.length} core written`,
|
|
1936
|
+
`${retired.length} retired ownership removed`,
|
|
1937
|
+
`${retiredDeleted.length} retired file deleted`,
|
|
1846
1938
|
`${provisioned.length} project provisioned`,
|
|
1847
1939
|
...(blockedProvisioning.length > 0
|
|
1848
1940
|
? [`${blockedProvisioning.length} provisioning group(s) blocked`]
|