gestalt-mobile 0.17.1 → 0.17.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/README.md CHANGED
@@ -50,7 +50,10 @@ skill paths without starting the server. Without `--skills`, a workspace
50
50
  `gestalt-skills.yml` is used when present; otherwise Codex-native selection is
51
51
  preserved. Explicit profiles take precedence over project defaults, which take
52
52
  precedence over native configuration. Gestalt Mobile never rewrites Codex
53
- configuration or skill files.
53
+ configuration or skill files. Exact skill paths remain authoritative, while
54
+ paths inside Codex's versioned plugin cache are rebound to the currently
55
+ discovered plugin version when their marketplace, plugin, and skill-relative
56
+ path still match.
54
57
 
55
58
  ## Themes
56
59
 
@@ -81,6 +81,36 @@ function canonicalSkillPath(value) {
81
81
  }
82
82
  return value;
83
83
  }
84
+ /**
85
+ * Return the durable identity of a skill inside Codex's versioned plugin cache.
86
+ * The concrete version directory is installation state, while marketplace,
87
+ * plugin, and skill-relative path remain stable across plugin upgrades.
88
+ */
89
+ function versionNeutralPluginSkillPath(value) {
90
+ const match = value.match(/^(.*[\\/]plugins[\\/]cache[\\/][^\\/]+[\\/][^\\/]+)[\\/][^\\/]+([\\/]skills[\\/].+[\\/]SKILL\.md)$/);
91
+ return match ? `${match[1]}${match[2]}` : undefined;
92
+ }
93
+ function rebindVersionedPluginSkills(discovered, selection) {
94
+ const discoveredPaths = new Set(discovered.map((skill) => canonicalSkillPath(skill.path)));
95
+ const discoveredByDurablePath = new Map();
96
+ for (const skill of discovered) {
97
+ const durablePath = versionNeutralPluginSkillPath(canonicalSkillPath(skill.path));
98
+ if (durablePath === undefined)
99
+ continue;
100
+ const matches = discoveredByDurablePath.get(durablePath) ?? [];
101
+ matches.push(skill);
102
+ discoveredByDurablePath.set(durablePath, matches);
103
+ }
104
+ return createSkillSelection(selection.map((entry) => {
105
+ if (discoveredPaths.has(entry.path))
106
+ return entry;
107
+ const durablePath = versionNeutralPluginSkillPath(entry.path);
108
+ if (durablePath === undefined)
109
+ return entry;
110
+ const matches = discoveredByDurablePath.get(durablePath) ?? [];
111
+ return matches.length === 1 ? { ...entry, path: matches[0].path } : entry;
112
+ }));
113
+ }
84
114
  /**
85
115
  * Validate a complete selection and return its canonical deterministic order.
86
116
  * This is lexical only: resolving symlinks is I/O and belongs to a platform
@@ -137,11 +167,12 @@ export function compileSkillOverride(input) {
137
167
  const effective = selectEffectiveSkillSelection(input);
138
168
  if (effective.selection === undefined)
139
169
  return { source: 'native', skillsConfig: undefined, warnings: [] };
170
+ const reboundSelection = rebindVersionedPluginSkills(input.discovered, effective.selection);
140
171
  const discoveredPaths = new Set(input.discovered.map((skill) => canonicalSkillPath(skill.path)));
141
- const warnings = effective.selection
172
+ const warnings = reboundSelection
142
173
  .filter((entry) => !discoveredPaths.has(entry.path))
143
174
  .map((entry) => `Saved skill path is no longer discovered: ${entry.path}`);
144
- const configured = applySkillSelectionSnapshot(input.discovered, effective.selection);
175
+ const configured = applySkillSelectionSnapshot(input.discovered, reboundSelection);
145
176
  return {
146
177
  source: effective.source,
147
178
  skillsConfig: configured
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gestalt-mobile",
3
- "version": "0.17.1",
3
+ "version": "0.17.2",
4
4
  "description": "Mobile-first web relay for durable Codex development sessions",
5
5
  "keywords": [
6
6
  "codex",