loadout-ai 0.2.2 → 0.2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.3 - 2026-07-17
4
+
5
+ ### Fixed
6
+
7
+ - Validate large rollback snapshots in linear, constant-stack time so project-aware activation can safely snapshot large reviewed skills.
8
+ - Preserve strict malformed-base64 rejection without relying on a stack-intensive regular expression.
9
+ - Rewrite every generated Graphify top-level lookup, repair, and optional Gemini install to the reviewed hashed artifact instead of leaving unpinned package fallbacks.
10
+
3
11
  ## 0.2.2 - 2026-07-17
4
12
 
5
13
  ### Fixed
package/dist/src/cli.js CHANGED
@@ -262,7 +262,7 @@ async function runSetup(options) {
262
262
  reader?.close();
263
263
  }
264
264
  }
265
- const LOADOUT_VERSION = "0.2.2";
265
+ const LOADOUT_VERSION = "0.2.3";
266
266
  function durableSchedulerLauncher() {
267
267
  return [
268
268
  join(dirname(process.execPath), process.platform === "win32" ? "npx.cmd" : "npx"),
@@ -118,7 +118,14 @@ const generatedFilesSchema = z
118
118
  .object({
119
119
  fileExtension: z.string().regex(/^\.[A-Za-z0-9]+$/),
120
120
  from: nonControlTextSchema,
121
- to: nonControlTextSchema.refine((value) => !/[{}]/u.test(value.replaceAll("{artifactRequirement}", "")), "unknown rewrite template variable"),
121
+ to: nonControlTextSchema.refine((value) => {
122
+ const rendered = [
123
+ "{artifactRequirement}",
124
+ "{artifactUrl}",
125
+ "{artifactSha256}",
126
+ ].reduce((current, placeholder) => current.replaceAll(placeholder, ""), value);
127
+ return !/[{}]/u.test(rendered);
128
+ }, "unknown rewrite template variable"),
122
129
  mustEliminate: z.boolean(),
123
130
  })
124
131
  .strict()),
@@ -306,6 +313,8 @@ export function runtimeArtifactRequirement(recipe) {
306
313
  export function renderRuntimeRecipeValue(value, recipe) {
307
314
  return value
308
315
  .replaceAll("{artifactRequirement}", runtimeArtifactRequirement(recipe))
316
+ .replaceAll("{artifactUrl}", recipe.artifactUrl)
317
+ .replaceAll("{artifactSha256}", recipe.artifactSha256)
309
318
  .replaceAll("{version}", recipe.version);
310
319
  }
311
320
  /** Resolve reviewed path segments without ever accepting a recipe-owned root. */
@@ -162,6 +162,24 @@ export const GRAPHIFY_RECIPE = deepFreeze(parseRuntimeToolRecipe({
162
162
  to: "--from '{artifactRequirement}'",
163
163
  mustEliminate: true,
164
164
  },
165
+ {
166
+ fileExtension: ".md",
167
+ from: "install --upgrade graphifyy",
168
+ to: "install '{artifactRequirement}' --exclude-newer 2026-07-17T00:00:00Z",
169
+ mustEliminate: true,
170
+ },
171
+ {
172
+ fileExtension: ".md",
173
+ from: "pip install graphifyy",
174
+ to: "pip install '{artifactRequirement}'",
175
+ mustEliminate: true,
176
+ },
177
+ {
178
+ fileExtension: ".md",
179
+ from: "pip install 'graphifyy[gemini]'",
180
+ to: "pip install 'graphifyy[gemini] @ {artifactUrl}#sha256={artifactSha256}'",
181
+ mustEliminate: true,
182
+ },
165
183
  ],
166
184
  },
167
185
  guarantees: [
@@ -169,7 +187,7 @@ export const GRAPHIFY_RECIPE = deepFreeze(parseRuntimeToolRecipe({
169
187
  "exact top-level wheel URL and SHA-256; dependency uploads bounded by the reviewed cutoff",
170
188
  "no API keys or provider credentials inherited by installer subprocesses",
171
189
  "agent skill targets and isolated runtime are snapshotted for rollback/removal",
172
- "generated runtime lookup is rewritten to the same pinned Graphify artifact",
190
+ "generated top-level Graphify lookups and repair commands are rewritten to the same pinned artifact",
173
191
  ],
174
192
  }));
175
193
  export const REVIEWED_RUNTIME_TOOLS = [GRAPHIFY_RECIPE];
@@ -205,6 +205,19 @@ export function validateSnapshot(value) {
205
205
  return value;
206
206
  }
207
207
  function isCanonicalBase64(value) {
208
- return (value.length % 4 === 0 &&
209
- /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value));
208
+ if (value.length % 4 !== 0)
209
+ return false;
210
+ const padding = value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0;
211
+ const contentLength = value.length - padding;
212
+ for (let index = 0; index < contentLength; index += 1) {
213
+ const code = value.charCodeAt(index);
214
+ const valid = (code >= 65 && code <= 90) ||
215
+ (code >= 97 && code <= 122) ||
216
+ (code >= 48 && code <= 57) ||
217
+ code === 43 ||
218
+ code === 47;
219
+ if (!valid)
220
+ return false;
221
+ }
222
+ return value.slice(contentLength) === "=".repeat(padding);
210
223
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loadout-ai",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "description": "Universal upgrade manager for AI coding agents",