loadout-ai 0.5.8 → 0.6.0

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.
Files changed (59) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/MASTER_PLAN.md +9 -9
  3. package/README.md +84 -121
  4. package/catalog/discovered.json +59800 -10065
  5. package/catalog/packages.json +4 -3
  6. package/dist/src/cli.js +26 -2979
  7. package/dist/src/commands/agents.js +168 -0
  8. package/dist/src/commands/catalog.js +708 -0
  9. package/dist/src/commands/health.js +98 -0
  10. package/dist/src/commands/inventory.js +417 -0
  11. package/dist/src/commands/lifecycle.js +411 -0
  12. package/dist/src/commands/mcp.js +424 -0
  13. package/dist/src/commands/setup.js +67 -0
  14. package/dist/src/commands/sharing.js +358 -0
  15. package/dist/src/commands/support.js +246 -0
  16. package/dist/src/core/active-limit.js +2 -0
  17. package/dist/src/core/active-policy.js +2 -1
  18. package/dist/src/core/catalog-install.js +47 -10
  19. package/dist/src/core/catalog.js +15 -96
  20. package/dist/src/core/cli-guide.js +43 -37
  21. package/dist/src/core/completion.js +2 -20
  22. package/dist/src/core/doctor.js +73 -24
  23. package/dist/src/core/github.js +3 -2
  24. package/dist/src/core/handoff.js +161 -0
  25. package/dist/src/core/health-score-evidence.js +2 -2
  26. package/dist/src/core/health.js +83 -0
  27. package/dist/src/core/install.js +8 -3
  28. package/dist/src/core/loadout-card.js +14 -2
  29. package/dist/src/core/review-queue.js +7 -2
  30. package/dist/src/core/route.js +248 -0
  31. package/dist/src/core/skill-inventory.js +3 -3
  32. package/dist/src/core/skills.js +6 -7
  33. package/dist/src/core/source.js +10 -3
  34. package/dist/src/core/upgrade.js +10 -9
  35. package/docs/ACTIVE_SET.md +1 -1
  36. package/docs/ACTIVE_SET_POLICY.md +1 -1
  37. package/docs/CATALOG.md +2 -2
  38. package/docs/DEMO_SCRIPT.md +5 -5
  39. package/docs/DISCOVERED.md +250 -253
  40. package/docs/RELEASE_REVIEW.md +28 -143
  41. package/docs/SUBMISSION_COPY.md +4 -4
  42. package/docs/UPSTREAM_LICENSE_DECISIONS.md +19 -12
  43. package/docs/USER_TEST_GUIDE.md +1 -1
  44. package/docs/evidence/readme-claims.json +2 -14
  45. package/package.json +4 -3
  46. package/dist/src/core/benchmark-campaign.js +0 -496
  47. package/dist/src/core/benchmark-evidence.js +0 -458
  48. package/dist/src/core/benchmark-fixtures.js +0 -1157
  49. package/dist/src/core/benchmark-runner.js +0 -301
  50. package/dist/src/core/benchmark-trust.js +0 -795
  51. package/dist/src/core/catalog-release.js +0 -287
  52. package/dist/src/core/compatibility-intelligence.js +0 -465
  53. package/dist/src/core/ecosystem-import.js +0 -1072
  54. package/dist/src/core/head-to-head.js +0 -437
  55. package/dist/src/core/intelligence-feed-build.js +0 -59
  56. package/dist/src/core/intelligence-feed.js +0 -353
  57. package/dist/src/core/registry-api.js +0 -97
  58. package/dist/src/core/release-claims.js +0 -175
  59. package/dist/src/core/signing.js +0 -115
@@ -1,353 +0,0 @@
1
- import { createHash, createPublicKey } from "node:crypto";
2
- import { readFile, rm, stat } from "node:fs/promises";
3
- import { dirname, join, resolve } from "node:path";
4
- import { z } from "zod";
5
- import { writeFileAtomically } from "./atomic-file.js";
6
- import { compatibilityNoticeSchema } from "./compatibility-intelligence.js";
7
- import { fetchBoundedJson } from "./discovery-connector.js";
8
- import { withFileLock } from "./file-lock.js";
9
- import { loadoutHome } from "./paths.js";
10
- import { publicKeyFingerprint, signPayload, verifyEnvelope, } from "./signing.js";
11
- const MAX_FEED_BYTES = 2 * 1024 * 1024;
12
- const MAX_KEY_BYTES = 64 * 1024;
13
- const DEFAULT_MAX_AGE_MS = 48 * 60 * 60 * 1000;
14
- const DEFAULT_MAX_STALE_MS = 7 * 24 * 60 * 60 * 1000;
15
- const timestamp = z.string().datetime({ offset: true });
16
- const httpsUrl = z
17
- .string()
18
- .url()
19
- .refine((value) => {
20
- const url = new URL(value);
21
- return url.protocol === "https:" && !url.username && !url.password;
22
- }, "credential-free HTTPS URL required");
23
- const identifier = z
24
- .string()
25
- .min(1)
26
- .max(200)
27
- .regex(/^[A-Za-z0-9][A-Za-z0-9._:/@+-]*$/);
28
- const discoveryObservationSchema = z
29
- .object({
30
- id: identifier,
31
- source: z.enum([
32
- "github",
33
- "hacker-news",
34
- "skills-sh",
35
- "official-mcp-registry",
36
- ]),
37
- observedAt: timestamp,
38
- sourceUrl: httpsUrl,
39
- identityKey: z.string().min(1).max(300),
40
- signal: z.string().min(1).max(300),
41
- })
42
- .strict();
43
- const candidateSummarySchema = z
44
- .object({
45
- id: identifier,
46
- repository: z.string().regex(/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/),
47
- commit: z.string().regex(/^[a-f0-9]{40}$/),
48
- observedAt: timestamp,
49
- status: z.enum(["inspected", "candidate", "rejected"]),
50
- summary: z.string().min(1).max(500),
51
- dossierSha256: z.string().regex(/^[a-f0-9]{64}$/),
52
- })
53
- .strict();
54
- const benchmarkChangeSchema = z
55
- .object({
56
- evidenceId: identifier,
57
- candidateId: identifier,
58
- taskFamily: identifier,
59
- change: z.enum(["added", "superseded", "withdrawn"]),
60
- evidenceSha256: z.string().regex(/^[a-f0-9]{64}$/),
61
- })
62
- .strict();
63
- const catalogPointerSchema = z
64
- .object({
65
- url: httpsUrl,
66
- createdAt: timestamp,
67
- signerFingerprint: z.string().regex(/^sha256:[a-f0-9]{64}$/),
68
- envelopeSha256: z.string().regex(/^[a-f0-9]{64}$/),
69
- })
70
- .strict();
71
- export const intelligenceFeedPayloadSchema = z
72
- .object({
73
- schemaVersion: z.literal(1),
74
- sequence: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
75
- createdAt: timestamp,
76
- expiresAt: timestamp,
77
- publicDataOnly: z.literal(true),
78
- discoveryObservations: z.array(discoveryObservationSchema).max(5_000),
79
- compatibilityNotices: z.array(compatibilityNoticeSchema).max(2_000),
80
- candidateSummaries: z.array(candidateSummarySchema).max(2_000),
81
- benchmarkChanges: z.array(benchmarkChangeSchema).max(2_000),
82
- catalogRelease: catalogPointerSchema.optional(),
83
- })
84
- .strict();
85
- const envelopeSchema = z
86
- .object({
87
- schemaVersion: z.literal(1),
88
- algorithm: z.literal("Ed25519"),
89
- createdAt: timestamp,
90
- publicKeyFingerprint: z.string().regex(/^sha256:[a-f0-9]{64}$/),
91
- payload: intelligenceFeedPayloadSchema,
92
- signature: z.string().min(1).max(1024),
93
- })
94
- .strict();
95
- function defaultStatePath() {
96
- return join(loadoutHome(), "intelligence", "trust.json");
97
- }
98
- function defaultCachePath() {
99
- return join(loadoutHome(), "intelligence", "latest.json");
100
- }
101
- function feedLockPath(statePath) {
102
- return join(dirname(statePath), "feed.lock");
103
- }
104
- async function readBounded(path, maximum, label) {
105
- const info = await stat(path);
106
- if (!info.isFile() || info.size > maximum)
107
- throw new Error(`${label} exceeds its byte limit`);
108
- const content = await readFile(path, "utf8");
109
- if (Buffer.byteLength(content) > maximum)
110
- throw new Error(`${label} exceeds its byte limit`);
111
- return content;
112
- }
113
- async function readTrustState(path) {
114
- try {
115
- const value = JSON.parse(await readBounded(path, 64 * 1024, "Intelligence trust state"));
116
- const state = value;
117
- if (!value ||
118
- typeof value !== "object" ||
119
- Array.isArray(value) ||
120
- state.schemaVersion !== 1 ||
121
- typeof state.fingerprint !== "string" ||
122
- !/^sha256:[a-f0-9]{64}$/.test(state.fingerprint) ||
123
- !Number.isSafeInteger(state.highWaterSequence) ||
124
- Number(state.highWaterSequence) < 0 ||
125
- typeof state.highWaterCreatedAt !== "string" ||
126
- !Number.isFinite(Date.parse(state.highWaterCreatedAt)) ||
127
- (state.pendingRotationFingerprint !== undefined &&
128
- !/^sha256:[a-f0-9]{64}$/.test(state.pendingRotationFingerprint)))
129
- throw new Error("Intelligence trust state is invalid");
130
- return state;
131
- }
132
- catch (error) {
133
- if (error.code === "ENOENT")
134
- return undefined;
135
- throw error;
136
- }
137
- }
138
- async function readSource(source, fetcher) {
139
- if (/^https:\/\//i.test(source))
140
- return (await fetchBoundedJson(source, { fetcher, maximumBytes: MAX_FEED_BYTES })).value;
141
- if (/^[a-z]+:\/\//i.test(source))
142
- throw new Error("Remote intelligence feeds must use HTTPS");
143
- return JSON.parse(await readBounded(resolve(source), MAX_FEED_BYTES, "Intelligence feed"));
144
- }
145
- function envelopeHash(envelope) {
146
- return createHash("sha256").update(JSON.stringify(envelope)).digest("hex");
147
- }
148
- function validateFreshness(payload, now, maxAgeMs) {
149
- const created = Date.parse(payload.createdAt);
150
- const expires = Date.parse(payload.expiresAt);
151
- if (expires <= created)
152
- throw new Error("Intelligence feed expiry must follow creation");
153
- if (created > now.getTime() + 5 * 60 * 1000)
154
- throw new Error("Intelligence feed is dated in the future");
155
- if (now.getTime() - created > maxAgeMs || expires <= now.getTime())
156
- throw new Error("Intelligence feed is stale");
157
- }
158
- export function createSignedIntelligenceFeed(payload, privateKeyPem) {
159
- const validated = intelligenceFeedPayloadSchema.parse(payload);
160
- return signPayload(validated, privateKeyPem, validated.createdAt);
161
- }
162
- /** Preview is read-only: it verifies but never installs, promotes, executes, or changes trust. */
163
- export async function previewIntelligenceFeed(options) {
164
- const statePath = resolve(options.statePath ?? defaultStatePath());
165
- const cachePath = resolve(options.cachePath ?? defaultCachePath());
166
- const [rawEnvelope, publicKeyPem, state] = await Promise.all([
167
- readSource(options.source, options.fetcher),
168
- readBounded(resolve(options.publicKeyPath), MAX_KEY_BYTES, "Intelligence public key"),
169
- readTrustState(statePath),
170
- ]);
171
- const envelope = envelopeSchema.parse(rawEnvelope);
172
- const verification = verifyEnvelope(envelope, publicKeyPem);
173
- if (!verification.valid)
174
- throw new Error("Intelligence feed signature is invalid");
175
- if (envelope.createdAt !== envelope.payload.createdAt)
176
- throw new Error("Intelligence envelope and payload creation times differ");
177
- validateFreshness(envelope.payload, options.now ?? new Date(), options.maxAgeMs ?? DEFAULT_MAX_AGE_MS);
178
- const keyRotation = Boolean(state && verification.fingerprint !== state.fingerprint);
179
- if (keyRotation &&
180
- verification.fingerprint !== state?.pendingRotationFingerprint)
181
- throw new Error("Intelligence signer does not match the pinned or explicitly authorized next key");
182
- if (state && envelope.payload.sequence <= state.highWaterSequence)
183
- throw new Error("Refusing replayed or downgraded intelligence sequence");
184
- if (state &&
185
- Date.parse(envelope.payload.createdAt) <=
186
- Date.parse(state.highWaterCreatedAt))
187
- throw new Error("Refusing intelligence older than the trust high-water mark");
188
- return {
189
- source: options.source,
190
- status: "current",
191
- fingerprint: verification.fingerprint,
192
- payload: envelope.payload,
193
- envelope,
194
- envelopeSha256: envelopeHash(envelope),
195
- firstPin: !state,
196
- keyRotation,
197
- cachePath,
198
- statePath,
199
- publicKeyPem,
200
- summary: {
201
- discoveryObservations: envelope.payload.discoveryObservations.length,
202
- compatibilityNotices: envelope.payload.compatibilityNotices.length,
203
- candidateSummaries: envelope.payload.candidateSummaries.length,
204
- benchmarkChanges: envelope.payload.benchmarkChanges.length,
205
- hasCatalogPointer: Boolean(envelope.payload.catalogRelease),
206
- },
207
- boundary: "read-only-intelligence-no-install-promotion-or-execution",
208
- };
209
- }
210
- /** Apply only caches verified public intelligence and advances replay protection. */
211
- export async function applyIntelligenceFeed(preview) {
212
- return withFileLock(feedLockPath(preview.statePath), async () => {
213
- const verification = verifyEnvelope(preview.envelope, preview.publicKeyPem);
214
- if (!verification.valid || verification.fingerprint !== preview.fingerprint)
215
- throw new Error("Intelligence preview signature no longer verifies");
216
- const state = await readTrustState(preview.statePath);
217
- if (state && preview.payload.sequence <= state.highWaterSequence)
218
- throw new Error("Refusing replayed or downgraded intelligence sequence");
219
- const allowed = !state ||
220
- verification.fingerprint === state.fingerprint ||
221
- verification.fingerprint === state.pendingRotationFingerprint;
222
- if (!allowed)
223
- throw new Error("Intelligence signer is not pinned or authorized for rotation");
224
- const next = {
225
- schemaVersion: 1,
226
- fingerprint: verification.fingerprint,
227
- highWaterSequence: preview.payload.sequence,
228
- highWaterCreatedAt: preview.payload.createdAt,
229
- };
230
- await writeFileAtomically(preview.cachePath, `${JSON.stringify(preview.envelope, null, 2)}\n`);
231
- await writeFileAtomically(preview.statePath, `${JSON.stringify(next, null, 2)}\n`);
232
- return { cachePath: preview.cachePath, sequence: preview.payload.sequence };
233
- });
234
- }
235
- /** Explicitly authorize one new key; no feed can rotate trust merely by claiming it. */
236
- export async function authorizeIntelligenceFeedKeyRotation(options) {
237
- const statePath = resolve(options.statePath ?? defaultStatePath());
238
- return withFileLock(feedLockPath(statePath), async () => {
239
- const [state, currentPem, nextPem] = await Promise.all([
240
- readTrustState(statePath),
241
- readBounded(resolve(options.currentPublicKeyPath), MAX_KEY_BYTES, "Current public key"),
242
- readBounded(resolve(options.nextPublicKeyPath), MAX_KEY_BYTES, "Next public key"),
243
- ]);
244
- if (!state)
245
- throw new Error("Cannot rotate an intelligence key before first pin");
246
- if (publicKeyFingerprint(currentPem) !== state.fingerprint)
247
- throw new Error("Current rotation key does not match pinned trust");
248
- const pendingRotationFingerprint = publicKeyFingerprint(nextPem);
249
- if (pendingRotationFingerprint === state.fingerprint)
250
- throw new Error("Next intelligence key must differ from the pinned key");
251
- await writeFileAtomically(statePath, `${JSON.stringify({ ...state, pendingRotationFingerprint }, null, 2)}\n`);
252
- return pendingRotationFingerprint;
253
- });
254
- }
255
- /** Explicit compromise recovery removes only feed trust/cache; catalog trust is separate. */
256
- export async function resetIntelligenceFeedTrust(options) {
257
- if (!options.acknowledgeCompromiseRecovery)
258
- throw new Error("Compromise recovery requires explicit acknowledgement");
259
- await Promise.all([
260
- rm(resolve(options.statePath ?? defaultStatePath()), { force: true }),
261
- rm(resolve(options.cachePath ?? defaultCachePath()), { force: true }),
262
- ]);
263
- }
264
- /** On refresh failure, return only a previously verified bounded cache and mark it stale. */
265
- export async function loadIntelligenceFeed(options) {
266
- try {
267
- const preview = await previewIntelligenceFeed(options);
268
- return {
269
- status: "current",
270
- payload: preview.payload,
271
- fingerprint: preview.fingerprint,
272
- stale: false,
273
- boundary: preview.boundary,
274
- };
275
- }
276
- catch (error) {
277
- const statePath = resolve(options.statePath ?? defaultStatePath());
278
- const cachePath = resolve(options.cachePath ?? defaultCachePath());
279
- const [state, publicKeyPem, cached] = await Promise.all([
280
- readTrustState(statePath),
281
- readBounded(resolve(options.publicKeyPath), MAX_KEY_BYTES, "Intelligence public key"),
282
- readBounded(cachePath, MAX_FEED_BYTES, "Cached intelligence feed"),
283
- ]);
284
- if (!state)
285
- throw error;
286
- const envelope = envelopeSchema.parse(JSON.parse(cached));
287
- const verification = verifyEnvelope(envelope, publicKeyPem);
288
- if (!verification.valid || verification.fingerprint !== state.fingerprint)
289
- throw new Error("Cached intelligence feed no longer verifies against pinned trust");
290
- if ((options.now ?? new Date()).getTime() -
291
- Date.parse(envelope.payload.createdAt) >
292
- (options.maxStaleMs ?? DEFAULT_MAX_STALE_MS))
293
- throw new Error("Cached intelligence feed exceeds the stale fallback limit");
294
- return {
295
- status: "stale-cache",
296
- payload: envelope.payload,
297
- fingerprint: verification.fingerprint,
298
- stale: true,
299
- issue: error instanceof Error ? error.message : String(error),
300
- boundary: "read-only-intelligence-no-install-promotion-or-execution",
301
- };
302
- }
303
- }
304
- /** Read the last explicitly applied feed and verify it against pinned local trust. */
305
- export async function readCachedIntelligenceFeed(options) {
306
- const statePath = resolve(options.statePath ?? defaultStatePath());
307
- const cachePath = resolve(options.cachePath ?? defaultCachePath());
308
- const [state, publicKeyPem, cached] = await Promise.all([
309
- readTrustState(statePath),
310
- readBounded(resolve(options.publicKeyPath), MAX_KEY_BYTES, "Intelligence public key"),
311
- readBounded(cachePath, MAX_FEED_BYTES, "Cached intelligence feed"),
312
- ]);
313
- if (!state)
314
- throw new Error("No pinned intelligence feed is available");
315
- const envelope = envelopeSchema.parse(JSON.parse(cached));
316
- const verification = verifyEnvelope(envelope, publicKeyPem);
317
- if (!verification.valid || verification.fingerprint !== state.fingerprint)
318
- throw new Error("Cached intelligence feed does not verify against pinned trust");
319
- if (envelope.payload.sequence !== state.highWaterSequence)
320
- throw new Error("Cached intelligence sequence does not match trust state");
321
- const stale = Date.parse(envelope.payload.expiresAt) <=
322
- (options.now ?? new Date()).getTime();
323
- return {
324
- status: stale ? "stale-cache" : "current",
325
- payload: envelope.payload,
326
- fingerprint: verification.fingerprint,
327
- stale,
328
- ...(stale ? { issue: "Cached intelligence feed is expired" } : {}),
329
- boundary: "read-only-intelligence-no-install-promotion-or-execution",
330
- };
331
- }
332
- export function intelligenceFeedPublicFields() {
333
- return Object.keys(intelligenceFeedPayloadSchema.shape).sort();
334
- }
335
- export function validateIntelligencePublicBoundary(value) {
336
- const serialized = JSON.stringify(value).toLowerCase();
337
- for (const forbidden of [
338
- "prompt",
339
- "projectpath",
340
- "projectsource",
341
- "private repository",
342
- "credentialvalue",
343
- "apikeyvalue",
344
- ])
345
- if (serialized.includes(forbidden))
346
- throw new Error(`Public intelligence feed contains forbidden private field marker: ${forbidden}`);
347
- return intelligenceFeedPayloadSchema.parse(value);
348
- }
349
- export function intelligencePublicKeySpkiSha256(publicKeyPem) {
350
- return createHash("sha256")
351
- .update(createPublicKey(publicKeyPem).export({ type: "spki", format: "der" }))
352
- .digest("hex");
353
- }
@@ -1,97 +0,0 @@
1
- import { createServer, } from "node:http";
2
- import { randomBytes, timingSafeEqual } from "node:crypto";
3
- import { mkdtemp, rm } from "node:fs/promises";
4
- import { tmpdir } from "node:os";
5
- import { join } from "node:path";
6
- import { createRegistryBundle, importRegistryBundle, publishLocalPackage, resolveRegistryPackage, } from "./registry.js";
7
- function send(response, status, value) {
8
- const body = JSON.stringify(value);
9
- response.writeHead(status, {
10
- "content-type": "application/json; charset=utf-8",
11
- "content-length": Buffer.byteLength(body),
12
- "cache-control": "no-store",
13
- "x-content-type-options": "nosniff",
14
- });
15
- response.end(body);
16
- }
17
- function tokenMatches(request, token) {
18
- const header = request.headers.authorization;
19
- if (!header?.startsWith("Bearer "))
20
- return false;
21
- const provided = Buffer.from(header.slice(7));
22
- const expected = Buffer.from(token);
23
- return (provided.length === expected.length && timingSafeEqual(provided, expected));
24
- }
25
- async function requestJson(request) {
26
- const chunks = [];
27
- let size = 0;
28
- for await (const chunk of request) {
29
- const value = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
30
- size += value.length;
31
- if (size > 35_000_000)
32
- throw new Error("Request exceeds registry limit");
33
- chunks.push(value);
34
- }
35
- return JSON.parse(Buffer.concat(chunks).toString("utf8"));
36
- }
37
- export async function startRegistryServer(options = {}) {
38
- const host = options.host ?? "127.0.0.1";
39
- const port = options.port ?? 0;
40
- const token = options.token ?? randomBytes(32).toString("hex");
41
- if (host !== "127.0.0.1" && host !== "::1" && !options.token)
42
- throw new Error("A publish token is required when binding beyond loopback");
43
- const server = createServer(async (request, response) => {
44
- try {
45
- const url = new URL(request.url ?? "/", "http://registry.local");
46
- const match = url.pathname.match(/^\/v1\/packages\/([a-z0-9][a-z0-9._-]*)\/([^/]+)$/);
47
- if (request.method === "GET" && match) {
48
- const resolved = await resolveRegistryPackage(match[1], decodeURIComponent(match[2]));
49
- send(response, 200, await createRegistryBundle(resolved.path));
50
- return;
51
- }
52
- if (request.method === "POST" && url.pathname === "/v1/packages") {
53
- if (!tokenMatches(request, token)) {
54
- request.resume();
55
- await new Promise((resolve) => request.once("end", resolve));
56
- send(response, 401, { error: "Invalid registry token" });
57
- return;
58
- }
59
- const temporary = await mkdtemp(join(tmpdir(), "loadout-registry-upload-"));
60
- try {
61
- const packed = await importRegistryBundle(await requestJson(request), join(temporary, "package"));
62
- const published = await publishLocalPackage(packed.root, {
63
- approveRisk: request.headers["x-loadout-approve-risk"] === "true",
64
- });
65
- send(response, 201, {
66
- name: published.descriptor.name,
67
- version: published.descriptor.version,
68
- digest: published.digest,
69
- });
70
- }
71
- finally {
72
- await rm(temporary, { recursive: true, force: true });
73
- }
74
- return;
75
- }
76
- send(response, 404, { error: "Not found" });
77
- }
78
- catch (error) {
79
- send(response, 400, {
80
- error: error instanceof Error ? error.message : String(error),
81
- });
82
- }
83
- });
84
- await new Promise((resolve, reject) => {
85
- server.once("error", reject);
86
- server.listen(port, host, resolve);
87
- });
88
- const address = server.address();
89
- if (!address || typeof address === "string")
90
- throw new Error("Could not determine registry address");
91
- return {
92
- server,
93
- host,
94
- port: address.port,
95
- close: () => new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve()))),
96
- };
97
- }
@@ -1,175 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import { access } from "node:fs/promises";
3
- import { dirname, join, resolve } from "node:path";
4
- import { fileURLToPath } from "node:url";
5
- export const RELEASE_CLAIMS = [
6
- {
7
- id: "catalog-immutable-attribution",
8
- status: "supported",
9
- statement: "The bundled catalog records immutable Git commits and upstream credit links.",
10
- boundary: "A pin proves source identity; it does not prove safety, license permission, usefulness, or future compatibility.",
11
- evidence: {
12
- files: [
13
- "catalog/packages.json",
14
- "docs/CATALOG.md",
15
- "scripts/check-catalog-attribution.mjs",
16
- ],
17
- commands: ["npm run check:evidence"],
18
- sources: [],
19
- },
20
- },
21
- {
22
- id: "stable-bounded-profile",
23
- status: "bounded",
24
- statement: "Stable selects 30 skill directories from four pinned SPDX-identified sources.",
25
- boundary: "Recommended means the bounded Loadout policy default, not universal task superiority.",
26
- evidence: {
27
- files: [
28
- "src/core/profiles.ts",
29
- "tests/profiles.test.ts",
30
- "tests/catalog-install.test.ts",
31
- ],
32
- commands: ["npm test -- --run"],
33
- sources: [],
34
- },
35
- },
36
- {
37
- id: "transactional-managed-mutations",
38
- status: "bounded",
39
- statement: "Managed installation batches use snapshot-backed filesystem transactions and rollback.",
40
- boundary: "This is not database-grade power-loss durability and never authorizes mutation of unmanaged content.",
41
- evidence: {
42
- files: [
43
- "src/core/transaction.ts",
44
- "src/core/snapshot.ts",
45
- "tests/transaction.test.ts",
46
- "tests/snapshot.test.ts",
47
- "tests/native-filesystem-smoke.test.ts",
48
- ],
49
- commands: ["npm run test:e2e:cli"],
50
- sources: [],
51
- },
52
- },
53
- {
54
- id: "daily-read-only-autopilot",
55
- status: "bounded",
56
- statement: "Autopilot schedules read-only discovery and update checks on supported native schedulers.",
57
- boundary: "Scheduled jobs do not install, promote, execute, or update candidate content.",
58
- evidence: {
59
- files: ["src/core/scheduler.ts", "tests/scheduler.test.ts"],
60
- commands: ["npm test -- --run tests/scheduler.test.ts"],
61
- sources: [],
62
- },
63
- },
64
- {
65
- id: "benchmark-performance-evidence",
66
- status: "not-established",
67
- statement: "Loadout has a deterministic evaluation protocol and model-free campaign planner.",
68
- boundary: "No bundled source is benchmarked until isolated real trials, signed evidence, and human promotion approval exist.",
69
- evidence: {
70
- files: [
71
- "docs/EVALUATION_PROTOCOL_V1.md",
72
- "src/core/benchmark-campaign.ts",
73
- "tests/benchmark-campaign.test.ts",
74
- "tests/benchmark-cli.test.ts",
75
- ],
76
- commands: [
77
- "npm test -- --run tests/benchmark-campaign.test.ts tests/benchmark-cli.test.ts",
78
- ],
79
- sources: [],
80
- },
81
- },
82
- {
83
- id: "privacy-safe-sharing",
84
- status: "bounded",
85
- statement: "Cards, reports, comparisons, and badges expose aggregate local evidence without project content or credentials.",
86
- boundary: "Users must review generated artifacts before sharing; scores do not establish universal quality.",
87
- evidence: {
88
- files: [
89
- "src/core/share-report.ts",
90
- "src/core/loadout-card.ts",
91
- "src/core/loadout-badge.ts",
92
- "tests/share-report.test.ts",
93
- "tests/loadout-card.test.ts",
94
- "tests/loadout-badge.test.ts",
95
- ],
96
- commands: ["npm test -- --run"],
97
- sources: [],
98
- },
99
- },
100
- ];
101
- export function releaseEvidenceRoot() {
102
- const moduleDirectory = dirname(fileURLToPath(import.meta.url));
103
- const candidates = [
104
- join(moduleDirectory, "..", ".."),
105
- join(moduleDirectory, "..", "..", ".."),
106
- process.cwd(),
107
- ];
108
- return (candidates.find((candidate) => existsSync(join(candidate, "README.md"))) ??
109
- candidates[0]);
110
- }
111
- const FORBIDDEN_PUBLIC_CLAIMS = [
112
- /\buniversally best\b/i,
113
- /\bguaranteed safe\b/i,
114
- /\bzero[- ]risk\b/i,
115
- /\bperfect(?:ly)? compatible\b/i,
116
- /\bproves? task improvement\b/i,
117
- /\bevery (?:skill|repository) is (?:safe|reviewed|benchmarked)\b/i,
118
- ];
119
- function portableEvidencePath(path) {
120
- return (path.length > 0 &&
121
- !path.startsWith("/") &&
122
- !path.includes("\\") &&
123
- !path.split("/").includes(".."));
124
- }
125
- export async function auditReleaseClaims(options) {
126
- const claims = [...(options.claims ?? RELEASE_CLAIMS)];
127
- const blockers = [];
128
- const ids = new Set();
129
- for (const claim of claims) {
130
- if (ids.has(claim.id))
131
- blockers.push(`duplicate claim id: ${claim.id}`);
132
- ids.add(claim.id);
133
- if (!claim.statement.trim() || !claim.boundary.trim())
134
- blockers.push(`claim ${claim.id} lacks statement or boundary`);
135
- if (!claim.evidence.files.length)
136
- blockers.push(`claim ${claim.id} has no evidence files`);
137
- for (const path of claim.evidence.files) {
138
- if (!portableEvidencePath(path)) {
139
- blockers.push(`claim ${claim.id} has unsafe evidence path: ${path}`);
140
- continue;
141
- }
142
- if (options.verifyEvidenceFiles !== false)
143
- try {
144
- await access(resolve(options.root, path));
145
- }
146
- catch {
147
- blockers.push(`claim ${claim.id} evidence is missing: ${path}`);
148
- }
149
- }
150
- }
151
- for (const pattern of FORBIDDEN_PUBLIC_CLAIMS)
152
- if (pattern.test(options.readme))
153
- blockers.push(`README contains forbidden release claim: ${pattern}`);
154
- const catalogPhrase = `${options.catalogCount} credited public repositories`;
155
- if (!options.readme.includes(catalogPhrase))
156
- blockers.push(`README catalog count is stale or missing; expected '${catalogPhrase}'`);
157
- if (!/does not claim there is one universally [“"]best[”"] configuration/i.test(options.readme))
158
- blockers.push("README lacks the universal-best claim boundary");
159
- if (!/no bundled source is called benchmarked/i.test(options.readme))
160
- blockers.push("README lacks the no-bundled-benchmark-evidence boundary");
161
- return {
162
- schemaVersion: 1,
163
- policyVersion: "p16-17-v1",
164
- claims,
165
- releaseBlocked: blockers.length > 0,
166
- blockers,
167
- };
168
- }
169
- export function formatReleaseEvidenceIndex(index) {
170
- return [
171
- `Release claim gate: ${index.releaseBlocked ? "BLOCKED" : "PASS"} (${index.claims.length} claim(s))`,
172
- ...index.claims.map((claim) => `${claim.status === "supported" ? "✓" : claim.status === "bounded" ? "△" : "○"} ${claim.id} — ${claim.statement}\n Boundary: ${claim.boundary}`),
173
- ...index.blockers.map((blocker) => `BLOCKER: ${blocker}`),
174
- ].join("\n");
175
- }