loadout-ai 0.1.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.
- package/LICENSE +21 -0
- package/MASTER_PLAN.md +1711 -0
- package/README.md +453 -0
- package/SECURITY.md +18 -0
- package/SIMPLE_PLAN.md +44 -0
- package/catalog/discovered.json +10469 -0
- package/catalog/packages.json +1150 -0
- package/dashboard/app.js +607 -0
- package/dashboard/index.html +249 -0
- package/dashboard/styles.css +384 -0
- package/dist/src/cli.js +2735 -0
- package/dist/src/core/active-policy.js +233 -0
- package/dist/src/core/active-set.js +382 -0
- package/dist/src/core/adapters.js +254 -0
- package/dist/src/core/adopt.js +92 -0
- package/dist/src/core/agent-health-score.js +475 -0
- package/dist/src/core/agent-inspection.js +152 -0
- package/dist/src/core/agent-versions.js +131 -0
- package/dist/src/core/api.js +90 -0
- package/dist/src/core/atomic-file.js +22 -0
- package/dist/src/core/audit.js +174 -0
- package/dist/src/core/benchmark-campaign.js +496 -0
- package/dist/src/core/benchmark-evidence.js +458 -0
- package/dist/src/core/benchmark-fixtures.js +1157 -0
- package/dist/src/core/benchmark-runner.js +301 -0
- package/dist/src/core/benchmark-trust.js +795 -0
- package/dist/src/core/canary.js +77 -0
- package/dist/src/core/candidate-intelligence.js +661 -0
- package/dist/src/core/catalog-coverage.js +85 -0
- package/dist/src/core/catalog-install.js +186 -0
- package/dist/src/core/catalog-release.js +286 -0
- package/dist/src/core/catalog.js +327 -0
- package/dist/src/core/codex-mcp.js +122 -0
- package/dist/src/core/community.js +86 -0
- package/dist/src/core/compatibility-intelligence.js +465 -0
- package/dist/src/core/completion.js +160 -0
- package/dist/src/core/components.js +138 -0
- package/dist/src/core/conversion.js +56 -0
- package/dist/src/core/credentials.js +377 -0
- package/dist/src/core/demo.js +136 -0
- package/dist/src/core/diff.js +83 -0
- package/dist/src/core/discovery-connector.js +165 -0
- package/dist/src/core/doctor.js +95 -0
- package/dist/src/core/ecosystem-import.js +1072 -0
- package/dist/src/core/evaluate.js +72 -0
- package/dist/src/core/file-lock.js +140 -0
- package/dist/src/core/freshness-alerts.js +230 -0
- package/dist/src/core/github-discovery.js +97 -0
- package/dist/src/core/github.js +148 -0
- package/dist/src/core/head-to-head.js +437 -0
- package/dist/src/core/health-score-evidence.js +169 -0
- package/dist/src/core/health.js +129 -0
- package/dist/src/core/improve.js +223 -0
- package/dist/src/core/install.js +233 -0
- package/dist/src/core/intelligence-feed-build.js +59 -0
- package/dist/src/core/intelligence-feed.js +353 -0
- package/dist/src/core/loadout-badge.js +55 -0
- package/dist/src/core/loadout-card.js +87 -0
- package/dist/src/core/manifest.js +307 -0
- package/dist/src/core/mcp-recipes.js +419 -0
- package/dist/src/core/mcp-registry-discovery.js +455 -0
- package/dist/src/core/mcp.js +287 -0
- package/dist/src/core/model-config.js +102 -0
- package/dist/src/core/observations.js +87 -0
- package/dist/src/core/outcomes.js +164 -0
- package/dist/src/core/package.js +225 -0
- package/dist/src/core/paths.js +214 -0
- package/dist/src/core/portable.js +130 -0
- package/dist/src/core/private-discovery.js +45 -0
- package/dist/src/core/profiles.js +255 -0
- package/dist/src/core/provenance.js +278 -0
- package/dist/src/core/ranking.js +104 -0
- package/dist/src/core/recommend.js +181 -0
- package/dist/src/core/registry-api.js +97 -0
- package/dist/src/core/registry.js +316 -0
- package/dist/src/core/release-claims.js +175 -0
- package/dist/src/core/remove.js +122 -0
- package/dist/src/core/review-queue.js +200 -0
- package/dist/src/core/runtime-tool-recipe.js +315 -0
- package/dist/src/core/runtime-tools.js +453 -0
- package/dist/src/core/safety.js +234 -0
- package/dist/src/core/sandbox.js +78 -0
- package/dist/src/core/scheduler.js +264 -0
- package/dist/src/core/share-report.js +85 -0
- package/dist/src/core/signing.js +115 -0
- package/dist/src/core/skill-compare.js +239 -0
- package/dist/src/core/skill-inventory.js +193 -0
- package/dist/src/core/skill-security.js +340 -0
- package/dist/src/core/skills-sh-discovery.js +336 -0
- package/dist/src/core/skills.js +208 -0
- package/dist/src/core/snapshot.js +210 -0
- package/dist/src/core/source.js +305 -0
- package/dist/src/core/state.js +300 -0
- package/dist/src/core/sync.js +348 -0
- package/dist/src/core/terminal.js +53 -0
- package/dist/src/core/transaction.js +211 -0
- package/dist/src/core/update-watch.js +39 -0
- package/dist/src/core/update.js +216 -0
- package/dist/src/core/upgrade.js +207 -0
- package/dist/src/dashboard.js +418 -0
- package/dist/src/shared/schemas.js +336 -0
- package/dist/src/shared/types.js +1 -0
- package/docs/ACTIVE_SET.md +53 -0
- package/docs/ACTIVE_SET_POLICY.md +45 -0
- package/docs/CANDIDATE_INTELLIGENCE.md +139 -0
- package/docs/CATALOG.md +78 -0
- package/docs/CATALOG_POLICY.md +41 -0
- package/docs/COMMUNITY_DISCOVERY.md +14 -0
- package/docs/COMPATIBILITY_POLICY.md +22 -0
- package/docs/CONVERSION_AND_SANDBOX.md +27 -0
- package/docs/CREDENTIAL_AND_UPDATE_POLICY.md +31 -0
- package/docs/DISCOVERED.md +280 -0
- package/docs/EVALUATION_PROTOCOL.md +40 -0
- package/docs/EVALUATION_PROTOCOL_V1.md +300 -0
- package/docs/FEATURE_TEST_MATRIX.md +802 -0
- package/docs/GITHUB_AUTHORIZATION.md +38 -0
- package/docs/HEAD_TO_HEAD_EVALUATION.md +79 -0
- package/docs/PROVENANCE_AND_COMPARISON.md +72 -0
- package/docs/PROVIDER_CONFIGURATION.md +45 -0
- package/docs/RELEASE_REVIEW.md +117 -0
- package/docs/SAFE_UPDATE_DEMO.md +25 -0
- package/docs/SCHEMA_DECISIONS.md +25 -0
- package/docs/TEAM_POLICY.md +18 -0
- package/docs/TESTING.md +190 -0
- package/package.json +82 -0
|
@@ -0,0 +1,353 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export function parseLoadoutBadgeMetric(value) {
|
|
2
|
+
if (["evidence", "active-skills", "managed-packages", "mcp"].includes(value))
|
|
3
|
+
return value;
|
|
4
|
+
throw new Error("--metric must be evidence, active-skills, managed-packages, or mcp");
|
|
5
|
+
}
|
|
6
|
+
export function buildLoadoutBadge(card, metric = "evidence") {
|
|
7
|
+
if (metric === "active-skills")
|
|
8
|
+
return {
|
|
9
|
+
schemaVersion: 1,
|
|
10
|
+
label: "Loadout active skills",
|
|
11
|
+
message: String(card.totals.activeSkills),
|
|
12
|
+
color: "blue",
|
|
13
|
+
cacheSeconds: 3600,
|
|
14
|
+
};
|
|
15
|
+
if (metric === "managed-packages")
|
|
16
|
+
return {
|
|
17
|
+
schemaVersion: 1,
|
|
18
|
+
label: "Loadout packages",
|
|
19
|
+
message: String(card.totals.managedPackages),
|
|
20
|
+
color: "blue",
|
|
21
|
+
cacheSeconds: 3600,
|
|
22
|
+
};
|
|
23
|
+
if (metric === "mcp")
|
|
24
|
+
return {
|
|
25
|
+
schemaVersion: 1,
|
|
26
|
+
label: "Loadout MCP",
|
|
27
|
+
message: String(card.totals.mcpEntries),
|
|
28
|
+
color: "blue",
|
|
29
|
+
cacheSeconds: 3600,
|
|
30
|
+
};
|
|
31
|
+
const scored = card.agents.filter((agent) => agent.knownDimensions > 0 && agent.evidenceCoverage > 0);
|
|
32
|
+
const average = (values) => values.length
|
|
33
|
+
? Math.round(values.reduce((total, value) => total + value, 0) / values.length)
|
|
34
|
+
: 0;
|
|
35
|
+
const health = average(scored.map((agent) => agent.health));
|
|
36
|
+
const coverage = average(scored.map((agent) => agent.evidenceCoverage));
|
|
37
|
+
return {
|
|
38
|
+
schemaVersion: 1,
|
|
39
|
+
label: "Loadout evidence",
|
|
40
|
+
message: scored.length
|
|
41
|
+
? `${health}/100 · ${coverage}% covered`
|
|
42
|
+
: "unknown · 0% covered",
|
|
43
|
+
// Color reflects evidence coverage, never an unevidenced quality verdict.
|
|
44
|
+
color: coverage >= 80 ? "2f855a" : coverage >= 40 ? "d69e2e" : "718096",
|
|
45
|
+
cacheSeconds: 3600,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function formatLoadoutBadgeUsage(outputPath) {
|
|
49
|
+
return [
|
|
50
|
+
`Wrote a telemetry-free Shields endpoint artifact to ${outputPath}.`,
|
|
51
|
+
"After publishing that JSON at a stable public URL, render it with:",
|
|
52
|
+
`https://img.shields.io/endpoint?url=<URL-ENCODED-ARTIFACT-URL>`,
|
|
53
|
+
"The evidence badge is not a universal quality or task-improvement claim.",
|
|
54
|
+
].join("\n");
|
|
55
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { buildLocalAgentHealthScores } from "./health-score-evidence.js";
|
|
2
|
+
import { buildPrivacySafeReport, } from "./share-report.js";
|
|
3
|
+
export async function buildLoadoutCard(options = {}) {
|
|
4
|
+
const [report, scores] = await Promise.all([
|
|
5
|
+
options.report ? Promise.resolve(options.report) : buildPrivacySafeReport(),
|
|
6
|
+
options.scores
|
|
7
|
+
? Promise.resolve(options.scores)
|
|
8
|
+
: buildLocalAgentHealthScores({ asOf: options.now }),
|
|
9
|
+
]);
|
|
10
|
+
return {
|
|
11
|
+
schemaVersion: 1,
|
|
12
|
+
generatedAt: (options.now ?? new Date()).toISOString(),
|
|
13
|
+
agents: scores
|
|
14
|
+
.map((score) => ({
|
|
15
|
+
id: score.agent,
|
|
16
|
+
health: score.score,
|
|
17
|
+
rating: score.rating,
|
|
18
|
+
evidenceCoverage: score.evidenceCoverage,
|
|
19
|
+
knownDimensions: score.knownDimensions,
|
|
20
|
+
}))
|
|
21
|
+
.sort((left, right) => left.id.localeCompare(right.id)),
|
|
22
|
+
totals: {
|
|
23
|
+
managedPackages: report.packages.length,
|
|
24
|
+
activeSkills: report.packages.reduce((total, item) => total + item.activeSkills, 0),
|
|
25
|
+
disabledSkills: report.packages.reduce((total, item) => total + item.disabledSkills, 0),
|
|
26
|
+
mcpEntries: report.mcp.length,
|
|
27
|
+
},
|
|
28
|
+
claimBoundary: "Health summarizes available local evidence; it is not a universal quality score or proof of task improvement.",
|
|
29
|
+
privacy: "Contains no project paths, project names, prompts, source code, filenames, repository names, or credential values.",
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function formatLoadoutCard(card) {
|
|
33
|
+
const agentCells = card.agents.length
|
|
34
|
+
? card.agents
|
|
35
|
+
.map((agent) => `| ${agent.id} | ${agent.health}/100 | ${agent.rating} | ${agent.evidenceCoverage}% |`)
|
|
36
|
+
.join("\n")
|
|
37
|
+
: "| No detected agent | 0/100 | unknown | 0% |";
|
|
38
|
+
return [
|
|
39
|
+
"# My Loadout",
|
|
40
|
+
"",
|
|
41
|
+
`**${card.totals.activeSkills} active skills · ${card.totals.managedPackages} managed packages · ${card.totals.mcpEntries} MCP entries**`,
|
|
42
|
+
"",
|
|
43
|
+
"| Agent | Evidence health | Rating | Evidence coverage |",
|
|
44
|
+
"| --- | ---: | --- | ---: |",
|
|
45
|
+
agentCells,
|
|
46
|
+
"",
|
|
47
|
+
`Updated: ${card.generatedAt}`,
|
|
48
|
+
"",
|
|
49
|
+
`> ${card.claimBoundary}`,
|
|
50
|
+
`> Privacy: ${card.privacy}`,
|
|
51
|
+
].join("\n");
|
|
52
|
+
}
|
|
53
|
+
function reportTotals(report) {
|
|
54
|
+
return {
|
|
55
|
+
managedPackages: report.packages.length,
|
|
56
|
+
activeSkills: report.packages.reduce((total, item) => total + item.activeSkills, 0),
|
|
57
|
+
disabledSkills: report.packages.reduce((total, item) => total + item.disabledSkills, 0),
|
|
58
|
+
mcpEntries: report.mcp.length,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export function compareLoadoutReports(left, right) {
|
|
62
|
+
const before = reportTotals(left);
|
|
63
|
+
const after = reportTotals(right);
|
|
64
|
+
return {
|
|
65
|
+
schemaVersion: 1,
|
|
66
|
+
leftGeneratedAt: left.generatedAt,
|
|
67
|
+
rightGeneratedAt: right.generatedAt,
|
|
68
|
+
delta: {
|
|
69
|
+
managedPackages: after.managedPackages - before.managedPackages,
|
|
70
|
+
activeSkills: after.activeSkills - before.activeSkills,
|
|
71
|
+
disabledSkills: after.disabledSkills - before.disabledSkills,
|
|
72
|
+
mcpEntries: after.mcpEntries - before.mcpEntries,
|
|
73
|
+
},
|
|
74
|
+
boundary: "This compares explicit privacy-safe inventory counts only; it does not rank users, agents, or task quality.",
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export function formatLoadoutComparison(value) {
|
|
78
|
+
const signed = (number) => `${number >= 0 ? "+" : ""}${number}`;
|
|
79
|
+
return [
|
|
80
|
+
"Loadout comparison (right minus left)",
|
|
81
|
+
`Managed packages: ${signed(value.delta.managedPackages)}`,
|
|
82
|
+
`Active skills: ${signed(value.delta.activeSkills)}`,
|
|
83
|
+
`Disabled skills: ${signed(value.delta.disabledSkills)}`,
|
|
84
|
+
`MCP entries: ${signed(value.delta.mcpEntries)}`,
|
|
85
|
+
value.boundary,
|
|
86
|
+
].join("\n");
|
|
87
|
+
}
|