agent-skillboard 0.3.1 → 0.3.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.3.2 — 2026-07-14
6
+
7
+ ### Changed
8
+
9
+ - Made global npm updates and `skillboard setup` automatically upgrade a valid
10
+ version 1 user policy when every migration choice is already understood.
11
+ - Kept the explicit `migrate v2` preview, apply, and rollback commands for
12
+ project policies, audits, and recovery.
13
+
14
+ ### Fixed
15
+
16
+ - Removed the hidden manual migration step left after a 0.3.1 update while
17
+ preserving explicit terminal denials as disabled and review-only quarantine
18
+ states as enabled, agent-local skills.
19
+ - Kept automatic migration non-mutating for unknown future ambiguity kinds or
20
+ policy skills that are not currently observed, and restored exact version 1
21
+ bytes after any transactional failure.
22
+ - Restored invoking-user ownership for migration backups, manifests, and
23
+ generated inventory during sudo-driven global updates.
24
+
5
25
  ## 0.3.1 — 2026-07-14
6
26
 
7
27
  ### Added
package/README.md CHANGED
@@ -9,7 +9,7 @@ Valid installed skills default to enabled and agent-local. Sharing is opt-in per
9
9
  skill. Optional preference ranks enabled skills installed for the current agent
10
10
  and never changes availability or copies files.
11
11
 
12
- Status: public alpha. Package 0.3.1 writes policy schema v2.
12
+ Status: public alpha. Package 0.3.2 writes policy schema v2.
13
13
 
14
14
  <p align="center">
15
15
  <img src="https://raw.githubusercontent.com/NyXXiR/skillboard/main/skillboard.png" alt="SkillBoard architecture diagram" width="100%">
@@ -56,6 +56,14 @@ prefix, activate the Node environment that owns each stale copy, and run
56
56
  uninstall another prefix. Restart or refresh agents after an update because
57
57
  some agents cache user skills.
58
58
 
59
+ When an update finds a valid version 1 user policy, setup automatically migrates
60
+ it only when all reported choices are understood. It creates an adjacent exact
61
+ backup, keeps explicit terminal denials disabled, and maps review-only
62
+ quarantine states to enabled and agent-local. Unknown future ambiguity leaves
63
+ the policy unchanged and prints the explicit preview command instead. The same
64
+ review path is used when a v1 policy skill is not currently observed, avoiding
65
+ an unhealthy generated inventory or silently forgetting a denial.
66
+
59
67
  `setup` is safe to rerun. It refreshes managed guidance and inventory, discovers
60
68
  late standard agent homes and Hermes profiles, and fills already-selected
61
69
  `shared: true` skills into newly active roots. It does not make every skill
@@ -157,9 +165,16 @@ skillboard migrate v2 --config <path> --yes --json
157
165
  skillboard migrate v2 --config <path> --rollback <backup> --json
158
166
  ```
159
167
 
160
- Setup and package updates keep v1 bytes unchanged and print the preview command.
161
- SkillBoard does not automatically migrate version 1. All v1 mutations refuse
162
- and point to migration. v0.4.0 removes the v1 reader.
168
+ Starting in 0.3.2, setup and global package updates automatically migrate a
169
+ valid version 1 user policy when the report contains only understood choices.
170
+ The transaction creates an adjacent exact backup. Explicit terminal denials
171
+ remain disabled; review-only quarantine becomes enabled and agent-local. An
172
+ unknown future ambiguity leaves the policy unchanged and prints the preview
173
+ command. A policy skill that is not currently observed also stays on v1 for an
174
+ explicit decision, because forgetting a disabled entry could enable it if it
175
+ later reappears. The explicit commands remain available for project policies,
176
+ audits, and rollback. Other v1 mutations still refuse and point to migration.
177
+ v0.4.0 removes the v1 reader.
163
178
 
164
179
  ## Cleanup
165
180
 
@@ -45,6 +45,11 @@ inventory, and materializes missing copies only for skills already marked
45
45
  discovery state. They must not introduce project scope, source trust gates, or
46
46
  another authorization layer.
47
47
 
48
+ When setup encounters version 1 user policy, it may converge that policy to v2
49
+ only if every migration ambiguity is a known review-only quarantine mapping.
50
+ It must preserve terminal denials, create an exact recoverable backup, and leave
51
+ unknown future ambiguity unchanged for an explicit decision.
52
+
48
53
  ## Development rule
49
54
 
50
55
  Every route, brief, guard, dashboard, generated bridge, and policy mutation must
package/docs/install.md CHANGED
@@ -35,6 +35,15 @@ keep. Activate the Node environment that owns each stale prefix, confirm it with
35
35
  SkillBoard does not automatically uninstall another prefix or request elevated
36
36
  permissions for cleanup.
37
37
 
38
+ From 0.3.2, setup and global postinstall automatically migrate a valid version
39
+ 1 user policy when every reported migration choice is understood. Migration
40
+ creates an adjacent exact backup, preserves explicit terminal denials as
41
+ disabled, and makes review-only quarantine states enabled and agent-local. An
42
+ unknown future ambiguity leaves the policy unchanged and prints the explicit
43
+ preview command; no migration files are changed. A v1 policy skill that is not
44
+ currently observed uses the same review path so setup neither invents inventory
45
+ nor silently forgets its policy.
46
+
38
47
  Run setup later when lifecycle scripts were skipped, another agent or Hermes
39
48
  profile was added, or a managed root needs to be repaired:
40
49
 
@@ -104,10 +113,12 @@ skillboard migrate v2 --config <path> --yes --json
104
113
  skillboard migrate v2 --config <path> --rollback <backup> --json
105
114
  ```
106
115
 
107
- Version 1 is read-only during v0.3.x. v0.4.0 removes the v1 reader.
108
- Setup and postinstall preserve an existing v1 policy byte-for-byte and suggest
109
- the preview command only. SkillBoard does not automatically migrate version 1;
110
- apply and rollback remain explicit user actions.
116
+ Version 1 is read-only during v0.3.x. v0.4.0 removes the v1 reader. Setup and
117
+ global postinstall automatically migrate an understood version 1 user policy
118
+ through the same backup and validation transaction. Unknown future ambiguity
119
+ or a policy skill that is not currently observed leaves it unchanged and prints
120
+ the preview command. Explicit preview, apply, and rollback remain available for
121
+ project policies, audits, and recovery.
111
122
 
112
123
  ## Uninstall
113
124
 
@@ -71,3 +71,10 @@ skillboard migrate v2 --config <path> --rollback <backup> --json
71
71
  ```
72
72
 
73
73
  v0.4.0 removes the v1 reader.
74
+
75
+ Setup and global postinstall automatically upgrade a valid version 1 user
76
+ policy when its migration report contains only understood choices. The
77
+ transaction keeps an exact adjacent backup. An unknown future ambiguity leaves
78
+ the policy unchanged and requires the explicit preview/apply flow. Policy skills
79
+ that are not currently observed also require review rather than being silently
80
+ forgotten.
package/docs/reference.md CHANGED
@@ -153,8 +153,13 @@ Preview changes no bytes. Apply creates an adjacent byte-for-byte backup and
153
153
  writes v2 policy plus generated inventory atomically. Rollback restores the
154
154
  selected backup. v0.4.0 removes the v1 reader.
155
155
 
156
- Setup and npm postinstall preserve an existing v1 policy and print only the
157
- preview form. SkillBoard does not automatically migrate version 1.
156
+ Setup and global npm postinstall automatically migrate a valid version 1 user
157
+ policy when all reported choices are understood. They create an adjacent exact
158
+ backup, preserve terminal denials as disabled, and map review-only quarantine
159
+ to enabled and agent-local. An unknown future ambiguity leaves the policy
160
+ unchanged, changes no migration files, and prints the preview form. The same
161
+ review path names policy skills that are not currently observed. Explicit
162
+ commands remain available for project policies, audit, and recovery.
158
163
 
159
164
  Legacy fields are interpreted only by migration and never become hidden v2
160
165
  authorization. Primary examples are `examples/v2-multi-source.config.yaml` and
package/docs/user-flow.md CHANGED
@@ -111,4 +111,8 @@ skillboard migrate v2 --config <path> --rollback <backup> --json
111
111
  ```
112
112
 
113
113
  Version 1 remains readable but immutable for the v0.3.x one-release read-only
114
- window. v0.4.0 removes the v1 reader. No ordinary command migrates implicitly.
114
+ window. Setup and global package updates automatically migrate an understood
115
+ version 1 user policy with an exact adjacent backup. Unknown future ambiguity
116
+ or a policy skill that is not currently observed leaves it unchanged and prints
117
+ the preview command. Project policies and other ordinary commands still require
118
+ explicit migration. v0.4.0 removes the v1 reader.
@@ -15,7 +15,8 @@ node --test test/v2-agent-sharing.test.mjs test/v2-onboarding-behavior.test.mjs
15
15
  - Optional preference changes ranking without changing availability.
16
16
  - Source and provenance audits do not change guard results.
17
17
  - Commands produce the same result from different working directories.
18
- - Version 1 is read-only until explicit migration.
18
+ - Version 1 project policy remains read-only until explicit migration; setup
19
+ automatically migrates an understood user policy with an exact backup.
19
20
 
20
21
  ## Reproduce the user surface
21
22
 
@@ -22,9 +22,13 @@ Preview is non-mutating, apply creates an adjacent byte-for-byte backup, and
22
22
  rollback restores the selected backup. Old action ids, hooks, and lock
23
23
  projections are invalid after migration.
24
24
 
25
- Package install, update, setup, and doctor never automatically migrate a v1
26
- policy. Setup preserves its bytes and prints the preview command so the policy
27
- decision stays separate from package maintenance.
25
+ Starting in 0.3.2, setup and global package updates automatically migrate a
26
+ valid version 1 user policy when all migration choices are understood. They use
27
+ the same exact-backup, atomic-write, validation, and recovery transaction as
28
+ the explicit command. Unknown future ambiguity leaves the policy unchanged and
29
+ prints the preview command. A policy skill that is not currently observed also
30
+ requires review, preserving future denial semantics. Doctor and project-local
31
+ commands remain non-mutating.
28
32
 
29
33
  ## Versioned v2 contract
30
34
 
@@ -39,7 +43,8 @@ schema.
39
43
  1. Run `npm run check`.
40
44
  2. Run `npm pack --dry-run --json` and inspect public contents.
41
45
  3. Install the tarball into an isolated prefix and run
42
- `skillboard doctor --summary`; verify fresh setup, v1 preservation, and a
46
+ `skillboard doctor --summary`; verify fresh setup, safe v1 auto-migration,
47
+ exact backup preservation, and a
43
48
  late-agent shared-skill reconciliation.
44
49
  4. Confirm `CHANGELOG.md` includes the package version.
45
50
  5. Push `main` and wait for the complete cross-platform check matrix.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-skillboard",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Keep agent skills broadly available while routing overlaps consistently.",
5
5
  "keywords": [
6
6
  "ai-agent",
@@ -1,6 +1,7 @@
1
- import { isAbsolute, relative, resolve } from "node:path";
1
+ import { resolve } from "node:path";
2
2
  import { mkdir } from "node:fs/promises";
3
3
  import { createInterface } from "node:readline";
4
+ import { commandPrefix, shellQuote } from "./agent-integration-command.mjs";
4
5
  import { installAgentIntegration, uninstallAgentIntegration } from "./agent-integration-files.mjs";
5
6
  import { applyOwnership, applyOwnershipTree, resolveSetupHome, setupOwnership } from "./agent-integration-home.mjs";
6
7
  import { setupAgentSkillTargets, supportedAgentNames } from "./agent-skill-roots.mjs";
@@ -13,6 +14,7 @@ import {
13
14
  } from "./agent-root-registry.mjs";
14
15
  import { refreshAgentInventory } from "./inventory-refresh.mjs";
15
16
  import { reconcileSharedSkills } from "./shared-skill-reconcile.mjs";
17
+ import { upgradeLegacyUserPolicy } from "./setup-policy-migration.mjs";
16
18
 
17
19
  export async function runSetupCommand(options, stdout, runtime = defaultRuntime()) {
18
20
  assertSetupOptions(options);
@@ -52,7 +54,21 @@ export async function runSetupCommand(options, stdout, runtime = defaultRuntime(
52
54
  preserveLegacyPolicy: true
53
55
  });
54
56
  const configPath = resolve(home, inventory.configPath);
55
- const inventoryPath = inventory.inventoryPath === null ? null : resolve(home, inventory.inventoryPath);
57
+ let inventoryPath = inventory.inventoryPath === null ? null : resolve(home, inventory.inventoryPath);
58
+ const policyUpgrade = await upgradeLegacyUserPolicy({
59
+ home,
60
+ configPath,
61
+ inventoryPath,
62
+ observedSkillIds: inventory.observedSkillIds,
63
+ failpoint: runtime.migrationFailpoint
64
+ });
65
+ if (policyUpgrade.status === "upgraded") {
66
+ inventoryPath = policyUpgrade.inventoryPath;
67
+ await applyOwnership(configPath, ownership);
68
+ await applyOwnership(inventoryPath, ownership);
69
+ for (const artifact of policyUpgrade.artifacts) await applyOwnership(artifact, ownership);
70
+ inventory = await refreshAgentInventory({ root: home, home, env, registeredRoots });
71
+ }
56
72
  const shared = inventoryPath === null
57
73
  ? { created: [], unchanged: [], preserved: [], blocked: [] }
58
74
  : await reconcileSharedSkills({ home, env, targets, configPath, inventoryPath });
@@ -80,14 +96,24 @@ export async function runSetupCommand(options, stdout, runtime = defaultRuntime(
80
96
  writeList(stdout, "Blocked shared copies", shared.blocked.map(formatBlockedEntry));
81
97
  stdout.write(`User policy: ${inventory.configPath}\n`);
82
98
  stdout.write(`Observed skills: ${inventory.scan.scannedSkills}\n`);
83
- if (inventory.inventoryPath === null) {
84
- stdout.write("Policy version 1 remains read-only during SkillBoard v0.3.x.\n");
99
+ if (policyUpgrade.status === "upgraded") {
100
+ stdout.write("User policy upgraded automatically to version 2.\n");
101
+ stdout.write(`Backup: ${policyUpgrade.backupPath}\n`);
102
+ } else if (policyUpgrade.status === "decision-required") {
103
+ stdout.write("Policy version 1 needs review before migration; no migration files were changed.\n");
104
+ if (policyUpgrade.unobservedSkillIds.length > 0) {
105
+ stdout.write(`Policy skills not currently observed: ${formatList(policyUpgrade.unobservedSkillIds)}\n`);
106
+ }
85
107
  stdout.write(`Preview migration: ${commandPrefix(runtime)} migrate v2 --config ${shellQuote(configPath, runtime.platform)} --json\n`);
86
108
  }
87
109
  stdout.write("Next:\n");
88
110
  stdout.write("- Restart or refresh agents that cache user skills.\n");
89
111
  stdout.write("- Run skillboard doctor --summary to check policy and executable paths.\n");
90
- stdout.write("- User-level policy and inventory were refreshed; no project was initialized.\n");
112
+ if (policyUpgrade.status === "decision-required") {
113
+ stdout.write("- User-level policy was preserved and installed skills were rescanned; no project was initialized.\n");
114
+ } else {
115
+ stdout.write("- User-level policy and inventory were refreshed; no project was initialized.\n");
116
+ }
91
117
  stdout.write('- Ask the agent in a workspace: "Review this plan and point out weak assumptions."\n');
92
118
  stdout.write("- SkillBoard will step in when skills overlap, routing is ambiguous, or you ask for a skill decision.\n");
93
119
  return 0;
@@ -222,46 +248,3 @@ function formatSharedEntry(entry) {
222
248
  function formatBlockedEntry(entry) {
223
249
  return `${entry.agent ?? "unknown"}:${entry.skill}:${entry.reason}`;
224
250
  }
225
-
226
- function commandPrefix(runtime) {
227
- const entrypoint = runtime.entrypointPath ?? "";
228
- const normalized = entrypoint.replace(/\\/g, "/");
229
- if (normalized.includes("/_npx/")) {
230
- const packageSpec = runtime.packageSpec ?? "agent-skillboard";
231
- return `npx --yes --package ${shellQuote(packageSpec, runtime.platform)} skillboard`;
232
- }
233
- if (isSourceTreeEntrypoint(entrypoint)) {
234
- return `node ${sourceTreeEntrypoint(entrypoint, runtime.cwd ?? process.cwd(), runtime.platform)}`;
235
- }
236
- return "skillboard";
237
- }
238
-
239
- function isSourceTreeEntrypoint(entrypoint) {
240
- if (entrypoint === "") {
241
- return false;
242
- }
243
- const normalized = entrypoint.replace(/\\/g, "/");
244
- return (normalized === "bin/skillboard.mjs" || normalized.endsWith("/bin/skillboard.mjs"))
245
- && !normalized.includes("/node_modules/")
246
- && !normalized.includes("/_npx/")
247
- && !normalized.includes("/.npm/");
248
- }
249
-
250
- function sourceTreeEntrypoint(entrypoint, cwd, platform) {
251
- const absoluteEntrypoint = isAbsolute(entrypoint) ? entrypoint : resolve(cwd, entrypoint);
252
- const relativeEntrypoint = relative(cwd, absoluteEntrypoint).replace(/\\/g, "/");
253
- if (!relativeEntrypoint.startsWith("../") && relativeEntrypoint !== ".." && !isAbsolute(relativeEntrypoint)) {
254
- return shellQuote(relativeEntrypoint, platform);
255
- }
256
- return shellQuote(absoluteEntrypoint, platform);
257
- }
258
-
259
- function shellQuote(value, platform = process.platform) {
260
- if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value)) {
261
- return value;
262
- }
263
- if (platform === "win32") {
264
- return `"${value.replace(/"/g, '""')}"`;
265
- }
266
- return `'${value.replace(/'/g, "'\\''")}'`;
267
- }
@@ -0,0 +1,38 @@
1
+ import { isAbsolute, relative, resolve } from "node:path";
2
+
3
+ export function commandPrefix(runtime) {
4
+ const entrypoint = runtime.entrypointPath ?? "";
5
+ const normalized = entrypoint.replace(/\\/g, "/");
6
+ if (normalized.includes("/_npx/")) {
7
+ const packageSpec = runtime.packageSpec ?? "agent-skillboard";
8
+ return `npx --yes --package ${shellQuote(packageSpec, runtime.platform)} skillboard`;
9
+ }
10
+ if (isSourceTreeEntrypoint(entrypoint)) {
11
+ return `node ${sourceTreeEntrypoint(entrypoint, runtime.cwd ?? process.cwd(), runtime.platform)}`;
12
+ }
13
+ return "skillboard";
14
+ }
15
+
16
+ function isSourceTreeEntrypoint(entrypoint) {
17
+ if (entrypoint === "") return false;
18
+ const normalized = entrypoint.replace(/\\/g, "/");
19
+ return (normalized === "bin/skillboard.mjs" || normalized.endsWith("/bin/skillboard.mjs"))
20
+ && !normalized.includes("/node_modules/")
21
+ && !normalized.includes("/_npx/")
22
+ && !normalized.includes("/.npm/");
23
+ }
24
+
25
+ function sourceTreeEntrypoint(entrypoint, cwd, platform) {
26
+ const absoluteEntrypoint = isAbsolute(entrypoint) ? entrypoint : resolve(cwd, entrypoint);
27
+ const relativeEntrypoint = relative(cwd, absoluteEntrypoint).replace(/\\/g, "/");
28
+ if (!relativeEntrypoint.startsWith("../") && relativeEntrypoint !== ".." && !isAbsolute(relativeEntrypoint)) {
29
+ return shellQuote(relativeEntrypoint, platform);
30
+ }
31
+ return shellQuote(absoluteEntrypoint, platform);
32
+ }
33
+
34
+ export function shellQuote(value, platform = process.platform) {
35
+ if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value)) return value;
36
+ if (platform === "win32") return `"${value.replace(/"/g, '""')}"`;
37
+ return `'${value.replace(/'/g, "'\\''")}'`;
38
+ }
@@ -85,6 +85,7 @@ async function refreshLocked(options) {
85
85
  bootstrappedV2,
86
86
  configPath: relativeArtifactPath(root, configPath),
87
87
  inventoryPath: generatedInventory === null ? null : relativeArtifactPath(root, inventoryPath),
88
+ observedSkillIds: inventory.skills.map(({ id }) => id).sort((left, right) => left.localeCompare(right)),
88
89
  inventoryChanged,
89
90
  changed: plan.changed,
90
91
  plan,
@@ -0,0 +1,14 @@
1
+ const SAFE_AMBIGUITY_KIND = "review_only_quarantine";
2
+
3
+ export function canAutomaticallyMigrateV2(report) {
4
+ if (report?.mode !== "preview" || report.changed !== true || report.target_version !== 2) return false;
5
+ if (!Array.isArray(report.ambiguities)) return false;
6
+ return report.ambiguities.every((ambiguity) => (
7
+ ambiguity?.kind === SAFE_AMBIGUITY_KIND
8
+ && ambiguity.mapped_enabled === true
9
+ && ambiguity.requires_grouped_confirmation === true
10
+ && Array.isArray(ambiguity.skill_ids)
11
+ && ambiguity.skill_ids.length > 0
12
+ && ambiguity.skill_ids.every((skillId) => typeof skillId === "string" && skillId.length > 0)
13
+ ));
14
+ }
@@ -103,6 +103,7 @@ export function unchangedMigrationResult(inputBytes) {
103
103
  ambiguities: [],
104
104
  grouped_decision: null,
105
105
  backup: null,
106
- manifest: null
106
+ manifest: null,
107
+ inventory_backup: null
107
108
  };
108
109
  }
@@ -46,6 +46,9 @@ export async function migrateV2(options) {
46
46
 
47
47
  async function migrateForward(configPath, inventoryPath, options) {
48
48
  const inputBytes = await readFile(configPath);
49
+ if (options.expectedInputSha256 !== undefined && sha256(inputBytes) !== options.expectedInputSha256) {
50
+ throw new Error("Config changed after migration preview; no files were changed.");
51
+ }
49
52
  const inputText = inputBytes.toString("utf8");
50
53
  const { document, parsed } = parseMigrationConfig(inputText);
51
54
  const version = parsed.version ?? 1;
@@ -85,6 +88,7 @@ async function migrateForward(configPath, inventoryPath, options) {
85
88
  changed: true,
86
89
  backup: basename(backup.configBackupPath),
87
90
  manifest: basename(backup.manifestPath),
91
+ inventory_backup: backup.inventoryBackupPath === null ? null : basename(backup.inventoryBackupPath),
88
92
  ...report
89
93
  };
90
94
  } catch (error) {
@@ -0,0 +1,43 @@
1
+ import { dirname, join, resolve } from "node:path";
2
+ import { canAutomaticallyMigrateV2 } from "./migration/automatic-v2.mjs";
3
+ import { migrateV2 } from "./migration/v2-transaction.mjs";
4
+
5
+ export async function upgradeLegacyUserPolicy(options) {
6
+ if (options.inventoryPath !== null) {
7
+ return { status: "current", inventoryPath: options.inventoryPath, artifacts: [] };
8
+ }
9
+
10
+ const inventoryPath = resolve(options.home, ".skillboard", "inventory.json");
11
+ const migrationOptions = {
12
+ configPath: options.configPath,
13
+ inventoryPath,
14
+ failpoint: options.failpoint
15
+ };
16
+ const preview = await migrateV2({ ...migrationOptions, apply: false });
17
+ const observed = new Set(options.observedSkillIds);
18
+ const unobservedSkillIds = preview.skills.map(({ id }) => id).filter((id) => !observed.has(id));
19
+ if (!canAutomaticallyMigrateV2(preview) || unobservedSkillIds.length > 0) {
20
+ return {
21
+ status: "decision-required",
22
+ inventoryPath: null,
23
+ artifacts: [],
24
+ preview,
25
+ unobservedSkillIds
26
+ };
27
+ }
28
+
29
+ const applied = await migrateV2({
30
+ ...migrationOptions,
31
+ apply: true,
32
+ expectedInputSha256: preview.input_sha256
33
+ });
34
+ const directory = dirname(options.configPath);
35
+ const artifactNames = [applied.backup, applied.manifest, applied.inventory_backup].filter(Boolean);
36
+ return {
37
+ status: "upgraded",
38
+ inventoryPath,
39
+ backupPath: join(directory, applied.backup),
40
+ artifacts: artifactNames.map((name) => join(directory, name)),
41
+ report: applied
42
+ };
43
+ }