loadout-ai 0.5.5 → 0.5.6
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 +17 -0
- package/MASTER_PLAN.md +8 -0
- package/README.md +2 -2
- package/dist/src/cli.js +3 -3
- package/dist/src/core/source.js +30 -0
- package/dist/src/core/update.js +30 -5
- package/docs/USER_TEST_GUIDE.md +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.5.6 - 2026-07-21
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Resolve each repository's default-branch commit with a lightweight Git metadata
|
|
10
|
+
request before downloading files, deduplicate shared sources, and reuse exact
|
|
11
|
+
cached changed revisions for safety review.
|
|
12
|
+
- Give changed repositories a bounded 120-second review window while keeping remote
|
|
13
|
+
commit checks at 30 seconds, so large reviewed sources do not produce permanent
|
|
14
|
+
false connectivity warnings under four-way concurrency.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Complete update checks for large sources such as Scientific Agent Skills and
|
|
19
|
+
Awesome Copilot without weakening static diff analysis, activating disabled skills,
|
|
20
|
+
or treating a timed-out clone as a GitHub outage.
|
|
21
|
+
|
|
5
22
|
## 0.5.5 - 2026-07-21
|
|
6
23
|
|
|
7
24
|
### Changed
|
package/MASTER_PLAN.md
CHANGED
|
@@ -328,6 +328,14 @@ merged `codex/relatable-readme-hero` remote branch remains safe to delete after
|
|
|
328
328
|
managed drift, and no active adopted-skill update. Release this correction
|
|
329
329
|
before the remaining Graphify removal, discovery, uninstall, and reinstall
|
|
330
330
|
acceptance steps.
|
|
331
|
+
- [x] `P18-46 [TERRA]` Eliminate the repeated large-repository timeout exposed by the
|
|
332
|
+
published `0.5.5` founder check. Resolve remote HEAD metadata before fetching
|
|
333
|
+
content, download only changed revisions that require static safety analysis,
|
|
334
|
+
reuse immutable cached snapshots, and allow those bounded reviews 120 seconds.
|
|
335
|
+
The real 40-record Maximum state now completes with 19 disabled-library
|
|
336
|
+
changes, 21 current packages, zero active updates, and zero unavailable checks;
|
|
337
|
+
both previously failing large repositories complete without changing or
|
|
338
|
+
activating any agent-visible skill.
|
|
331
339
|
|
|
332
340
|
### Release 0.3 lifecycle hardening
|
|
333
341
|
|
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
</p>
|
|
27
27
|
|
|
28
28
|
> [!IMPORTANT]
|
|
29
|
-
> Installation is version-pinned so the code you test matches these docs. The commands below target `loadout-ai@0.5.
|
|
29
|
+
> Installation is version-pinned so the code you test matches these docs. The commands below target `loadout-ai@0.5.6`; review the preview before every apply.
|
|
30
30
|
|
|
31
31
|
## How it works
|
|
32
32
|
|
|
@@ -76,7 +76,7 @@ Skills, plugins, MCP servers, and agent settings tend to accumulate one experime
|
|
|
76
76
|
You need Node.js 20 or newer and Git.
|
|
77
77
|
|
|
78
78
|
```bash
|
|
79
|
-
npm install --global loadout-ai@0.5.
|
|
79
|
+
npm install --global loadout-ai@0.5.6
|
|
80
80
|
loadout --version
|
|
81
81
|
loadout guide
|
|
82
82
|
```
|
package/dist/src/cli.js
CHANGED
|
@@ -264,7 +264,7 @@ async function runSetup(options) {
|
|
|
264
264
|
reader?.close();
|
|
265
265
|
}
|
|
266
266
|
}
|
|
267
|
-
const LOADOUT_VERSION = "0.5.
|
|
267
|
+
const LOADOUT_VERSION = "0.5.6";
|
|
268
268
|
function durableSchedulerLauncher() {
|
|
269
269
|
return [
|
|
270
270
|
join(dirname(process.execPath), process.platform === "win32" ? "npx.cmd" : "npx"),
|
|
@@ -909,7 +909,7 @@ program
|
|
|
909
909
|
.option("--agents <ids>", "limit explained scores to selected agent ids")
|
|
910
910
|
.action(async (options) => {
|
|
911
911
|
if (options.updates && !options.json)
|
|
912
|
-
console.error("Checking
|
|
912
|
+
console.error("Checking repository commits (4 at a time; changed sources may take up to 120s for safety review)…");
|
|
913
913
|
const report = await buildHealthReport({
|
|
914
914
|
updates: options.updates
|
|
915
915
|
? () => buildUpdatePlan(undefined, {
|
|
@@ -2857,7 +2857,7 @@ program
|
|
|
2857
2857
|
profile = await evaluateInstalledProfile(catalog);
|
|
2858
2858
|
}
|
|
2859
2859
|
if (!options.json)
|
|
2860
|
-
console.error("Checking
|
|
2860
|
+
console.error("Checking repository commits (4 at a time; changed sources may take up to 120s for safety review)…");
|
|
2861
2861
|
let plans = await buildUpdatePlan(undefined, {
|
|
2862
2862
|
packageId: options.package,
|
|
2863
2863
|
onProgress: options.json
|
package/dist/src/core/source.js
CHANGED
|
@@ -144,6 +144,36 @@ export function normalizeRepository(input) {
|
|
|
144
144
|
}
|
|
145
145
|
return value;
|
|
146
146
|
}
|
|
147
|
+
export function parseRepositoryHead(repository, output) {
|
|
148
|
+
const commit = output
|
|
149
|
+
.split(/\r?\n/)
|
|
150
|
+
.map((line) => line.split(/\s+/))
|
|
151
|
+
.find((parts) => parts[1] === "HEAD" && /^[0-9a-f]{40}$/i.test(parts[0]))?.[0];
|
|
152
|
+
if (!commit)
|
|
153
|
+
throw new Error(`Git returned an invalid default-branch HEAD for ${repository}`);
|
|
154
|
+
return commit;
|
|
155
|
+
}
|
|
156
|
+
/** Resolve the public default-branch commit without downloading repository files. */
|
|
157
|
+
export async function resolveRepositoryHead(input, options = {}) {
|
|
158
|
+
const repository = normalizeRepository(input);
|
|
159
|
+
const gitEnvironment = await isolatedGitEnvironment(loadoutHome());
|
|
160
|
+
const url = `https://github.com/${repository}.git`;
|
|
161
|
+
try {
|
|
162
|
+
const { stdout } = await execFileAsync("git", ["ls-remote", "--symref", "--", url, "HEAD"], {
|
|
163
|
+
maxBuffer: 1024 * 1024,
|
|
164
|
+
timeout: options.timeoutMs,
|
|
165
|
+
env: gitEnvironment,
|
|
166
|
+
});
|
|
167
|
+
return {
|
|
168
|
+
repository,
|
|
169
|
+
commit: parseRepositoryHead(repository, stdout),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
174
|
+
throw new Error(`Could not resolve ${repository}: ${message}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
147
177
|
function normalizeRef(ref) {
|
|
148
178
|
if (!/^[A-Za-z0-9][A-Za-z0-9._/-]*$/.test(ref) ||
|
|
149
179
|
ref.includes("..") ||
|
package/dist/src/core/update.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import { basename, join } from "node:path";
|
|
3
|
-
import { fetchRepositorySnapshot } from "./source.js";
|
|
3
|
+
import { fetchRepositorySnapshot, resolveRepositoryHead } from "./source.js";
|
|
4
4
|
import { repositoryCachePath } from "./source.js";
|
|
5
5
|
import { diffRepositorySnapshots } from "./diff.js";
|
|
6
6
|
import { hashDirectory, readInstallState } from "./state.js";
|
|
@@ -85,21 +85,39 @@ async function analyzeManagedUpdate(oldRoot, newRoot, unitIds) {
|
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
87
|
/** Builds a read-only update plan from persisted installs and live GitHub snapshots. */
|
|
88
|
-
export async function buildUpdatePlan(resolver
|
|
88
|
+
export async function buildUpdatePlan(resolver, options = {}) {
|
|
89
89
|
const state = await readInstallState();
|
|
90
90
|
const records = options.packageId
|
|
91
91
|
? state.installs.filter((record) => record.packageId === options.packageId)
|
|
92
92
|
: state.installs;
|
|
93
93
|
const results = new Array(records.length);
|
|
94
|
+
const lightweightResolver = resolver ??
|
|
95
|
+
options.resolveHead ??
|
|
96
|
+
((repository) => resolveRepositoryHead(repository, { timeoutMs: 30_000 }));
|
|
94
97
|
const resolutions = new Map();
|
|
95
98
|
const resolveOnce = (repository) => {
|
|
96
99
|
const existing = resolutions.get(repository);
|
|
97
100
|
if (existing)
|
|
98
101
|
return existing;
|
|
99
|
-
const pending =
|
|
102
|
+
const pending = lightweightResolver(repository);
|
|
100
103
|
resolutions.set(repository, pending);
|
|
101
104
|
return pending;
|
|
102
105
|
};
|
|
106
|
+
const changedSnapshots = new Map();
|
|
107
|
+
const fetchChangedOnce = (repository, commit) => {
|
|
108
|
+
const key = `${repository}\0${commit.toLowerCase()}`;
|
|
109
|
+
const existing = changedSnapshots.get(key);
|
|
110
|
+
if (existing)
|
|
111
|
+
return existing;
|
|
112
|
+
const pending = options.fetchChangedSnapshot
|
|
113
|
+
? options.fetchChangedSnapshot(repository)
|
|
114
|
+
: fetchRepositorySnapshot(repository, {
|
|
115
|
+
ref: commit,
|
|
116
|
+
timeoutMs: 120_000,
|
|
117
|
+
});
|
|
118
|
+
changedSnapshots.set(key, pending);
|
|
119
|
+
return pending;
|
|
120
|
+
};
|
|
103
121
|
let cursor = 0;
|
|
104
122
|
let completed = 0;
|
|
105
123
|
const workers = Array.from({
|
|
@@ -139,9 +157,16 @@ export async function buildUpdatePlan(resolver = async (repository) => fetchRepo
|
|
|
139
157
|
let diff;
|
|
140
158
|
let safetyFindings;
|
|
141
159
|
let approvalRequired = false;
|
|
142
|
-
|
|
160
|
+
let currentPath = current.path;
|
|
161
|
+
if (!same && !currentPath && !resolver) {
|
|
162
|
+
const fetched = await fetchChangedOnce(record.repository, current.commit);
|
|
163
|
+
if (fetched.commit.toLowerCase() !== current.commit.toLowerCase())
|
|
164
|
+
throw new Error(`Resolved ${current.commit}, but fetched ${fetched.commit} for safety review`);
|
|
165
|
+
currentPath = fetched.path;
|
|
166
|
+
}
|
|
167
|
+
if (!same && currentPath) {
|
|
143
168
|
const oldPath = repositoryCachePath(record.repository, record.resolvedCommit);
|
|
144
|
-
const analysis = await analyzeManagedUpdate(oldPath,
|
|
169
|
+
const analysis = await analyzeManagedUpdate(oldPath, currentPath, managedUnitIds(state, record.packageId));
|
|
145
170
|
diff = analysis.diff;
|
|
146
171
|
safetyFindings = analysis.safetyFindings;
|
|
147
172
|
approvalRequired = analysis.approvalRequired;
|
package/docs/USER_TEST_GUIDE.md
CHANGED
|
@@ -211,7 +211,7 @@ cleanup deliberately deletes Loadout's snapshots, so it is the last lifecycle te
|
|
|
211
211
|
## Troubleshooting and recovery
|
|
212
212
|
|
|
213
213
|
- **`loadout` is not found after installation:** confirm `npm install --global
|
|
214
|
-
loadout-ai@0.5.
|
|
214
|
+
loadout-ai@0.5.6` completed, run `hash -r`, and confirm npm's global binary
|
|
215
215
|
directory is on `PATH`. For a source checkout, run `npm run build` and `npm link`.
|
|
216
216
|
- **A preview asks for `--approve-risk`:** read the reported scripts, domains,
|
|
217
217
|
credentials, binaries, or instruction findings. If you accept that specific plan,
|