@msn-control/liftoff 0.10.1 → 0.10.2

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.
@@ -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', 'repository-governance-copilot-launcher', 'repository-governance-claude-launcher'];
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;IACtB,wCAAwC;IACxC,uCAAuC;CAC/B,CAAC;AAEX,MAAM,yBAAyB,GAAG,IAAI,GAAG,CAAS,uBAAuB,CAAC,CAAC;AAE3E,MAAM,UAAU,wBAAwB,CAAC,WAAmB;IAC1D,OAAO,yBAAyB,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC;IACnC,kBAAkB;IAClB,eAAe;IACf,gBAAgB;IAChB,eAAe;IACf,cAAc;IACd,sBAAsB;IACtB,iBAAiB;IACjB,sBAAsB;IACtB,0BAA0B;IAC1B,qBAAqB;CACtB,CAAC,CAAC;AAEH,MAAM,6BAA6B,GAAG,IAAI,GAAG,EAAoC,CAAC;AAClF,KAAK,MAAM,WAAW,IAAI,YAAY,EAAE,CAAC;IACvC,MAAM,KAAK,GAAG,eAAe,WAAW,CAAC,EAAE,EAAW,CAAC;IACvD,6BAA6B,CAAC,GAAG,CAAC,eAAe,WAAW,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC;IAClF,6BAA6B,CAAC,GAAG,CAAC,eAAe,WAAW,CAAC,EAAE,YAAY,EAAE,KAAK,CAAC,CAAC;IACpF,6BAA6B,CAAC,GAAG,CAAC,YAAY,WAAW,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;AAChF,CAAC;AAED,MAAM,UAAU,uBAAuB,CACrC,WAAmB;IAEnB,IAAI,oBAAoB,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;QAC1C,OAAO,UAAU,CAAC;IACpB,CAAC;IACD,OAAO,6BAA6B,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,MAAM,CAAC;AAClE,CAAC"}
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 = { new: 0, missing: 0, upgrade: 0, conflict: 0, moved: 0, orphan: 0, refresh: 0, unchanged: 0 };
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
- presentation.status('warning', 'Liftoff core maintenance available', `${toWrite} to write, ${summary.conflict} core conflict(s), ${summary.orphan} core orphan(s), ${summary.unchanged} core unchanged`);
1639
- if (toWrite > 0) {
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) => ({ path: manifestDisplayPath(entry.pathParts), reason: entry.reason })),
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) => `skipped ${entryDisplay(entry)} ${entry.reason}${force ? '' : ' (use --force to overwrite)'}`));
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`]