@tangle-network/agent-app 0.60.215 → 0.60.216
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/dist/chunk-CD2LQXY6.js +306 -0
- package/dist/chunk-CD2LQXY6.js.map +1 -0
- package/dist/{chunk-KDPTXXOQ.js → chunk-J3N6RNMH.js} +456 -75
- package/dist/chunk-J3N6RNMH.js.map +1 -0
- package/dist/chunk-LRYY3OX7.js +105 -0
- package/dist/chunk-LRYY3OX7.js.map +1 -0
- package/dist/{chunk-LLO436QO.js → chunk-QM7RTTDY.js} +27 -86
- package/dist/chunk-QM7RTTDY.js.map +1 -0
- package/dist/{chunk-ZRLKB4KB.js → chunk-YWLZOPDS.js} +10 -11
- package/dist/{chunk-ZRLKB4KB.js.map → chunk-YWLZOPDS.js.map} +1 -1
- package/dist/launch-invariants/catalog.d.ts +16 -0
- package/dist/launch-invariants/cli.js +5 -2
- package/dist/launch-invariants/cli.js.map +1 -1
- package/dist/launch-invariants/index.d.ts +1 -1
- package/dist/launch-invariants/index.js +13 -5
- package/dist/launch-invariants/index.js.map +1 -1
- package/dist/launch-invariants/limit-alarms.d.ts +19 -1
- package/dist/launch-invariants/testing.d.ts +0 -3
- package/dist/launch-invariants/testing.js +5 -2
- package/dist/signoff/app-defaults.d.ts +65 -0
- package/dist/signoff/cli.js +71 -4
- package/dist/signoff/cli.js.map +1 -1
- package/dist/signoff/defaults-cli.d.ts +1 -0
- package/dist/signoff/index.js +2 -1
- package/dist/signoff/types.d.ts +6 -0
- package/dist/signoff/worker-defaults.d.ts +8 -0
- package/dist/turn-health/index.js +12 -291
- package/dist/turn-health/index.js.map +1 -1
- package/dist/turn-trace/index.js +20 -3
- package/dist/turn-trace/index.js.map +1 -1
- package/dist/turn-trace/otel.d.ts +11 -4
- package/package.json +1 -1
- package/dist/chunk-KDPTXXOQ.js.map +0 -1
- package/dist/chunk-LLO436QO.js.map +0 -1
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LAUNCH_BUDGETS
|
|
3
|
+
} from "./chunk-LRYY3OX7.js";
|
|
4
|
+
|
|
1
5
|
// src/signoff/worker-defaults.ts
|
|
2
6
|
import { spawnSync } from "child_process";
|
|
3
7
|
import { existsSync } from "fs";
|
|
@@ -6,10 +10,13 @@ import { basename, dirname, join } from "path";
|
|
|
6
10
|
var WORKER_DEFAULTS_STEP = "worker defaults";
|
|
7
11
|
var WORKER_COMPATIBILITY_DATE_FLOOR = "2026-09-01";
|
|
8
12
|
var WRANGLER_CONFIG_NAME = /^wrangler[.-]?[\w.-]*\.(toml|json|jsonc)$/;
|
|
9
|
-
function
|
|
10
|
-
const listed = spawnSync("git", ["ls-files", "-z"], { cwd: repoRoot, encoding: "utf8" });
|
|
13
|
+
function trackedFiles(repoRoot) {
|
|
14
|
+
const listed = spawnSync("git", ["ls-files", "-z"], { cwd: repoRoot, encoding: "utf8", maxBuffer: 256 * 1024 * 1024 });
|
|
11
15
|
if (listed.status !== 0) throw new Error(`signoff: git ls-files failed in ${repoRoot}: ${listed.stderr}`);
|
|
12
|
-
return listed.stdout.split("\0").filter((path) => path !== ""
|
|
16
|
+
return listed.stdout.split("\0").filter((path) => path !== "");
|
|
17
|
+
}
|
|
18
|
+
function trackedWorkerConfigs(repoRoot) {
|
|
19
|
+
return trackedFiles(repoRoot).filter((path) => WRANGLER_CONFIG_NAME.test(basename(path))).sort();
|
|
13
20
|
}
|
|
14
21
|
function problemsFor(config) {
|
|
15
22
|
const problems = [];
|
|
@@ -98,10 +105,351 @@ function formatWorkerDefaults(result) {
|
|
|
98
105
|
}
|
|
99
106
|
|
|
100
107
|
// src/signoff/config.ts
|
|
101
|
-
import { existsSync as
|
|
102
|
-
import { join as
|
|
108
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
109
|
+
import { join as join3, resolve } from "path";
|
|
103
110
|
import { pathToFileURL } from "url";
|
|
104
111
|
import { z } from "zod";
|
|
112
|
+
|
|
113
|
+
// src/signoff/app-defaults.ts
|
|
114
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
115
|
+
import { existsSync as existsSync2, mkdtempSync, readFileSync, rmSync, statSync } from "fs";
|
|
116
|
+
import { tmpdir } from "os";
|
|
117
|
+
import { join as join2, posix } from "path";
|
|
118
|
+
var APP_DEFAULTS_STEP = "agent-app defaults";
|
|
119
|
+
var EXEMPTIBLE_DEFAULTS = ["turn-trace", "first-text", "prewarm", "placement", "limit-alarms"];
|
|
120
|
+
var AGENT_APP_OUTCOME_FLOORS = [
|
|
121
|
+
{ id: "turn-trace", label: "turn trace", since: "0.60.199" },
|
|
122
|
+
{ id: "first-text", label: "first text", since: "0.60.203" }
|
|
123
|
+
];
|
|
124
|
+
var SOURCE_EXTENSIONS = /\.(?:[cm]?[jt]sx?)$/;
|
|
125
|
+
var NOT_SOURCE = /(?:^|\/)(?:node_modules|dist|build|\.wrangler|tests?|__tests__|e2e|scripts|proof|journeys?|evals?|docs|examples|stories|\.storybook)\/|\.(?:test|spec|stories|e2e|bench)\.[cm]?[jt]sx?$|\.d\.ts$/;
|
|
126
|
+
var MAX_SOURCE_BYTES = 400 * 1024;
|
|
127
|
+
function readJson(path) {
|
|
128
|
+
try {
|
|
129
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
130
|
+
} catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function findAppPackages(repoRoot, tracked) {
|
|
135
|
+
const packages = [];
|
|
136
|
+
for (const path of tracked) {
|
|
137
|
+
if (posix.basename(path) !== "package.json" || /(?:^|\/)node_modules\//.test(path)) continue;
|
|
138
|
+
const pkg = readJson(join2(repoRoot, path));
|
|
139
|
+
if (!pkg || pkg.name === "@tangle-network/agent-app") continue;
|
|
140
|
+
const spec = pkg.dependencies?.["@tangle-network/agent-app"] ?? pkg.devDependencies?.["@tangle-network/agent-app"] ?? pkg.peerDependencies?.["@tangle-network/agent-app"];
|
|
141
|
+
if (spec === void 0) continue;
|
|
142
|
+
packages.push({ name: pkg.name ?? posix.dirname(path), dir: posix.dirname(path), spec });
|
|
143
|
+
}
|
|
144
|
+
return packages;
|
|
145
|
+
}
|
|
146
|
+
function underDir(path, dir) {
|
|
147
|
+
return dir === "." || path === dir || path.startsWith(`${dir}/`);
|
|
148
|
+
}
|
|
149
|
+
function withoutComments(text) {
|
|
150
|
+
return text.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(?<!:)\/\/.*$/gm, "");
|
|
151
|
+
}
|
|
152
|
+
function readSources(repoRoot, tracked, dir, others) {
|
|
153
|
+
const files = [];
|
|
154
|
+
for (const path of tracked) {
|
|
155
|
+
if (!underDir(path, dir) || !SOURCE_EXTENSIONS.test(path) || NOT_SOURCE.test(path)) continue;
|
|
156
|
+
if (others.some((other) => other !== dir && other !== "." && underDir(path, other) && underDir(other, dir))) continue;
|
|
157
|
+
const abs = join2(repoRoot, path);
|
|
158
|
+
try {
|
|
159
|
+
if (statSync(abs).size > MAX_SOURCE_BYTES) continue;
|
|
160
|
+
files.push({ path, text: withoutComments(readFileSync(abs, "utf8")) });
|
|
161
|
+
} catch {
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return files;
|
|
165
|
+
}
|
|
166
|
+
function versionTuple(spec) {
|
|
167
|
+
const match = /(\d+)\.(\d+)\.(\d+)/.exec(spec);
|
|
168
|
+
if (!match || /^(?:workspace|file|link|portal|git|github|http|catalog):/.test(spec)) return null;
|
|
169
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
170
|
+
}
|
|
171
|
+
function atLeast(have, floor) {
|
|
172
|
+
const want = versionTuple(floor);
|
|
173
|
+
for (let index = 0; index < 3; index += 1) {
|
|
174
|
+
if (have[index] !== want[index]) return have[index] > want[index];
|
|
175
|
+
}
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
function filesMatching(sources, pattern) {
|
|
179
|
+
return sources.filter((file) => pattern.test(file.text));
|
|
180
|
+
}
|
|
181
|
+
function callArguments(text, openIndex) {
|
|
182
|
+
let depth = 0;
|
|
183
|
+
for (let index = openIndex; index < Math.min(text.length, openIndex + 1200); index += 1) {
|
|
184
|
+
const char = text[index];
|
|
185
|
+
if (char === "(") depth += 1;
|
|
186
|
+
else if (char === ")" && --depth === 0) return text.slice(openIndex, index + 1);
|
|
187
|
+
}
|
|
188
|
+
return text.slice(openIndex, openIndex + 1200);
|
|
189
|
+
}
|
|
190
|
+
function sinkCalls(sources) {
|
|
191
|
+
const calls = [];
|
|
192
|
+
for (const file of sources) {
|
|
193
|
+
for (const match of file.text.matchAll(/createIntelligenceTurnTraceSink\s*\(/g)) {
|
|
194
|
+
const args = callArguments(file.text, match.index + match[0].length - 1);
|
|
195
|
+
const direct = /\bproject\s*:\s*['"`]([^'"`]+)['"`]/.exec(args);
|
|
196
|
+
const named = /\bproject\s*:\s*([A-Za-z_$][\w$]*)\s*[,}\n]/.exec(args) ?? /\bproject\b\s*[,}]/.exec(args);
|
|
197
|
+
const identifier = named?.[1] ?? (named ? "project" : void 0);
|
|
198
|
+
const constant = identifier ? new RegExp(`(?:const|let)\\s+${identifier}\\s*(?::\\s*string\\s*)?=\\s*['"\`]([^'"\`]+)['"\`]`).exec(file.text) : null;
|
|
199
|
+
calls.push({ file: file.path, args, project: direct?.[1] ?? constant?.[1] ?? null });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return calls;
|
|
203
|
+
}
|
|
204
|
+
function pinRow(pin, spec) {
|
|
205
|
+
const tuple = pin === null ? null : versionTuple(pin);
|
|
206
|
+
if (pin === null || tuple === null) {
|
|
207
|
+
return { id: "pin", label: "agent-app pin", status: "unknown", detail: `"${spec}" is not a plain version; the floors cannot be compared` };
|
|
208
|
+
}
|
|
209
|
+
const below = AGENT_APP_OUTCOME_FLOORS.filter((floor) => !atLeast(tuple, floor.since));
|
|
210
|
+
const floors = AGENT_APP_OUTCOME_FLOORS.map((floor) => `${floor.label} ${floor.since}`).join(", ");
|
|
211
|
+
return below.length === 0 ? { id: "pin", label: "agent-app pin", status: "ok", detail: `${pin} (floors: ${floors})` } : { id: "pin", label: "agent-app pin", status: "gap", detail: `${pin} is below the ${below.map((floor) => `${floor.label} floor ${floor.since}`).join(" and the ")}` };
|
|
212
|
+
}
|
|
213
|
+
function withExemption(row, id, exemptions, optedOut) {
|
|
214
|
+
const reason = exemptions[id];
|
|
215
|
+
if (optedOut !== null) {
|
|
216
|
+
return reason ? { row: { ...row, status: "exempt", detail: `${optedOut}; exempt: ${reason}` }, finding: null } : { row: { ...row, status: "opted-out", detail: optedOut }, finding: `${row.label}: ${optedOut}, with no reason in signoff.config.mjs agentApp.exemptions.${id}` };
|
|
217
|
+
}
|
|
218
|
+
if (row.status !== "ok" && row.status !== "na" && reason) {
|
|
219
|
+
return { row: { ...row, status: "exempt", detail: `${row.detail}; exempt: ${reason}` }, finding: null };
|
|
220
|
+
}
|
|
221
|
+
return { row, finding: null };
|
|
222
|
+
}
|
|
223
|
+
function turnTraceRow(sources, calls, exports) {
|
|
224
|
+
const optOut = filesMatching(sources, /\bturnTrace\s*:\s*false\b/)[0];
|
|
225
|
+
if (optOut) return { row: { id: "turn-trace", label: "turn trace", status: "gap", detail: "" }, optedOut: `turnTrace: false in ${optOut.path}`, wired: false };
|
|
226
|
+
const customSink = filesMatching(sources, /\bturnTrace\s*:\s*\{/);
|
|
227
|
+
if (calls.length === 0 && customSink.length === 0) {
|
|
228
|
+
return { row: { id: "turn-trace", label: "turn trace", status: "gap", detail: "no turn trace sink; createChatTurnRoutes gets no turnTrace" }, optedOut: null, wired: false };
|
|
229
|
+
}
|
|
230
|
+
const project = calls.find((call) => call.project)?.project ?? null;
|
|
231
|
+
const last = project === null ? void 0 : exports?.get(project);
|
|
232
|
+
const where = calls[0]?.file ?? customSink[0]?.path ?? "";
|
|
233
|
+
const wired = `sink wired in ${where}${project ? ` (${project})` : ""}`;
|
|
234
|
+
if (last) {
|
|
235
|
+
return { row: { id: "turn-trace", label: "turn trace", status: "ok", detail: `${wired}; last export accepted ${new Date(last.at).toISOString().slice(0, 16)}Z` }, optedOut: null, wired: true };
|
|
236
|
+
}
|
|
237
|
+
if (last === null) {
|
|
238
|
+
return { row: { id: "turn-trace", label: "turn trace", status: "gap", detail: `${wired}; Intelligence holds no turn from the last 30 days for this key` }, optedOut: null, wired: true };
|
|
239
|
+
}
|
|
240
|
+
const routerKey = calls.find((call) => /\bapiKey\s*:[^,}]*\bTANGLE_API_KEY\b/.test(call.args));
|
|
241
|
+
if (routerKey) {
|
|
242
|
+
return { row: { id: "turn-trace", label: "turn trace", status: "unknown", detail: `${wired} with TANGLE_API_KEY; the ingest refused it for Legal (#468): run with --probe, or drop apiKey so the sink reads the Sandbox product key` }, optedOut: null, wired: true };
|
|
243
|
+
}
|
|
244
|
+
return { row: { id: "turn-trace", label: "turn trace", status: "ok", detail: `${wired}; last export not checked` }, optedOut: null, wired: true };
|
|
245
|
+
}
|
|
246
|
+
function firstTextRow(pin, sources, traceWired, calls, exports) {
|
|
247
|
+
const floor = AGENT_APP_OUTCOME_FLOORS.find((entry) => entry.id === "first-text")?.since ?? "";
|
|
248
|
+
const tuple = pin === null ? null : versionTuple(pin);
|
|
249
|
+
const marks = filesMatching(sources, /\.markFirstText\s*\(/).length > 0;
|
|
250
|
+
const routes = filesMatching(sources, /\bcreateChatTurnRoutes\s*[(<]/).length > 0;
|
|
251
|
+
if (!traceWired) return { id: "first-text", label: "first text", status: "gap", detail: "needs the turn trace" };
|
|
252
|
+
if (tuple !== null && !atLeast(tuple, floor)) return { id: "first-text", label: "first text", status: "gap", detail: `agent-app ${pin} does not mark first text; ${floor} does` };
|
|
253
|
+
if (!routes && !marks) return { id: "first-text", label: "first text", status: "gap", detail: "the turn owner does not call trace.markFirstText() and the app does not use createChatTurnRoutes" };
|
|
254
|
+
const project = calls.find((call) => call.project)?.project ?? null;
|
|
255
|
+
const last = project === null ? void 0 : exports?.get(project);
|
|
256
|
+
const observed = last?.firstTextMs === void 0 ? "" : `; last turn first_text_ms ${Math.round(last.firstTextMs)}`;
|
|
257
|
+
if (last && last.firstTextMs === void 0) return { id: "first-text", label: "first text", status: "gap", detail: "the last exported turn has no tangle.turn.first_text_ms" };
|
|
258
|
+
return { id: "first-text", label: "first text", status: "ok", detail: `${routes ? "createChatTurnRoutes marks it" : "the turn owner marks it"}${observed}` };
|
|
259
|
+
}
|
|
260
|
+
function prewarmRow(sources) {
|
|
261
|
+
const usesBox = filesMatching(sources, /@tangle-network\/agent-app\/sandbox|\bensureWorkspaceSandbox\b/).length > 0;
|
|
262
|
+
if (!usesBox) return { id: "prewarm", label: "prewarm route", status: "na", detail: "the app opens no workspace box" };
|
|
263
|
+
const shared = filesMatching(sources, /\bcreateWorkspacePrewarmRoute\b/)[0];
|
|
264
|
+
if (shared) return { id: "prewarm", label: "prewarm route", status: "ok", detail: `shared route in ${shared.path}` };
|
|
265
|
+
const local = sources.find((file) => /prewarm/i.test(file.path) || /['"`/]prewarm['"`/]/.test(file.text));
|
|
266
|
+
return local ? { id: "prewarm", label: "prewarm route", status: "gap", detail: `local copy in ${local.path}; mount createWorkspacePrewarmRoute` } : { id: "prewarm", label: "prewarm route", status: "gap", detail: "no prewarm route; the first send waits for the box" };
|
|
267
|
+
}
|
|
268
|
+
function placementRow(configs) {
|
|
269
|
+
const pinned = configs.find((file) => /^\s*\[placement\]\s*$[\s\S]{0,300}?^\s*(?:region|host|hostname)\s*=/m.test(file.text) || /"placement"\s*:\s*\{[^}]*"(?:region|host|hostname)"/.test(file.text));
|
|
270
|
+
if (pinned) return { id: "placement", label: "placement", status: "ok", detail: `pinned in ${pinned.path}` };
|
|
271
|
+
if (configs.length === 0) return { id: "placement", label: "placement", status: "na", detail: "no Wrangler config" };
|
|
272
|
+
const smart = configs.some((file) => /^\s*\[placement\]/m.test(file.text) || /"placement"/.test(file.text));
|
|
273
|
+
return {
|
|
274
|
+
id: "placement",
|
|
275
|
+
label: "placement",
|
|
276
|
+
status: "gap",
|
|
277
|
+
detail: smart ? "a [placement] without a region or host is smart placement, which does not pin; unmeasured against the D1 primary" : "no [placement]; unmeasured against the D1 primary. Pin it beside D1 or declare an exemption with the reason"
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function limitAlarmsRow(sources) {
|
|
281
|
+
const wired = filesMatching(sources, /\bwithD1LimitAlarms\s*\(/);
|
|
282
|
+
const copies = filesMatching(sources, /\bcreateLimitAlarms\s*\(/);
|
|
283
|
+
const product = filesMatching(sources, /\bcreateProductLimitAlarms\s*\(/);
|
|
284
|
+
if (wired.length === 0) {
|
|
285
|
+
return {
|
|
286
|
+
id: "limit-alarms",
|
|
287
|
+
label: "limit alarms",
|
|
288
|
+
status: "gap",
|
|
289
|
+
detail: copies.length + product.length > 0 ? `alarms are built in ${(copies[0] ?? product[0])?.path} but nothing wraps the D1 binding with withD1LimitAlarms in the server` : "no limit alarms"
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
if (copies.length > 0) {
|
|
293
|
+
return { id: "limit-alarms", label: "limit alarms", status: "gap", detail: `wired in ${wired[0]?.path}; ${copies[0]?.path} keeps a local copy of the budgets: use createProductLimitAlarms with overrides` };
|
|
294
|
+
}
|
|
295
|
+
return { id: "limit-alarms", label: "limit alarms", status: "ok", detail: `wired in ${wired[0]?.path}` };
|
|
296
|
+
}
|
|
297
|
+
function parseWranglerUpload(output) {
|
|
298
|
+
const clean = output.replace(/\u001b\[[0-9;]*m/g, "");
|
|
299
|
+
const match = /Total Upload:\s*([\d.]+)\s*KiB\s*\/\s*gzip:\s*([\d.]+)\s*KiB/.exec(clean);
|
|
300
|
+
return match ? { rawKiB: Number(match[1]), gzipKiB: Number(match[2]) } : null;
|
|
301
|
+
}
|
|
302
|
+
function wranglerBinary(repoRoot, dir) {
|
|
303
|
+
for (const base of [join2(repoRoot, dir), repoRoot]) {
|
|
304
|
+
const bin = join2(base, "node_modules", ".bin", "wrangler");
|
|
305
|
+
if (existsSync2(bin)) return bin;
|
|
306
|
+
}
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
function bundleRow(repoRoot, dir, configs, measure) {
|
|
310
|
+
const label = "Worker upload";
|
|
311
|
+
if (configs.length === 0) return { id: "worker-bundle", label, status: "na", detail: "no Wrangler config" };
|
|
312
|
+
if (!measure) return { id: "worker-bundle", label, status: "unknown", detail: "not measured" };
|
|
313
|
+
const bin = wranglerBinary(repoRoot, dir);
|
|
314
|
+
if (!bin) return { id: "worker-bundle", label, status: "unknown", detail: "wrangler is not installed; install, build, then measure" };
|
|
315
|
+
const outDir = mkdtempSync(join2(tmpdir(), "agent-app-upload-"));
|
|
316
|
+
try {
|
|
317
|
+
const run = spawnSync2(bin, ["deploy", "--dry-run", "--env=", "--outdir", outDir], {
|
|
318
|
+
cwd: join2(repoRoot, dir),
|
|
319
|
+
encoding: "utf8",
|
|
320
|
+
timeout: 5 * 6e4,
|
|
321
|
+
maxBuffer: 20 * 1024 * 1024,
|
|
322
|
+
env: { ...process.env, CI: "1", WRANGLER_SEND_METRICS: "false", NO_COLOR: "1" }
|
|
323
|
+
});
|
|
324
|
+
const upload = parseWranglerUpload(`${run.stdout}
|
|
325
|
+
${run.stderr}`);
|
|
326
|
+
if (!upload) {
|
|
327
|
+
const reason = `${run.stderr}${run.stdout}`.split("\n").map((line) => line.trim()).find((line) => /error|not found|no such/i.test(line)) ?? `wrangler exited ${run.status}`;
|
|
328
|
+
return { id: "worker-bundle", label, status: "unknown", detail: `unmeasured (build first): ${reason.slice(0, 160)}` };
|
|
329
|
+
}
|
|
330
|
+
const limit = LAUNCH_BUDGETS.workerUploadGzipKiB;
|
|
331
|
+
const share = upload.gzipKiB / limit;
|
|
332
|
+
return {
|
|
333
|
+
id: "worker-bundle",
|
|
334
|
+
label,
|
|
335
|
+
status: share >= LAUNCH_BUDGETS.alarmRatio ? "gap" : "ok",
|
|
336
|
+
detail: `${Math.round(upload.gzipKiB)} KiB gzip of ${limit} KiB (${Math.round(share * 100)}%); ${Math.round(upload.rawKiB)} KiB raw of ${LAUNCH_BUDGETS.workerUploadRawKiB} KiB`
|
|
337
|
+
};
|
|
338
|
+
} finally {
|
|
339
|
+
rmSync(outDir, { recursive: true, force: true });
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function toolCountRow() {
|
|
343
|
+
return { id: "tool-count", label: "tool_count p50", status: "unknown", detail: "unmeasured; no ceiling until a measured link to first text exists" };
|
|
344
|
+
}
|
|
345
|
+
function checkAppDefaults(repoRoot, options = {}) {
|
|
346
|
+
const tracked = trackedFiles(repoRoot);
|
|
347
|
+
const packages = findAppPackages(repoRoot, tracked);
|
|
348
|
+
const owned = /* @__PURE__ */ new Map();
|
|
349
|
+
for (const path of trackedWorkerConfigs(repoRoot)) {
|
|
350
|
+
const owner = packages.filter((pkg) => underDir(path, pkg.dir)).sort((x, y) => y.dir.length - x.dir.length)[0];
|
|
351
|
+
if (owner) owned.set(owner.dir, [...owned.get(owner.dir) ?? [], path]);
|
|
352
|
+
}
|
|
353
|
+
const deployed = packages.filter((pkg) => owned.has(pkg.dir));
|
|
354
|
+
const apps = [];
|
|
355
|
+
const findings = [];
|
|
356
|
+
for (const pkg of deployed.length > 0 ? deployed : packages) {
|
|
357
|
+
const sources = readSources(repoRoot, tracked, pkg.dir, packages.map((other) => other.dir));
|
|
358
|
+
const configs = (owned.get(pkg.dir) ?? []).map((path) => ({ path, text: readFileSync(join2(repoRoot, path), "utf8") }));
|
|
359
|
+
const exemptions = options.exemptions ?? {};
|
|
360
|
+
const pin = versionTuple(pkg.spec) ? /(\d+\.\d+\.\d+)/.exec(pkg.spec)?.[1] ?? null : null;
|
|
361
|
+
const calls = sinkCalls(sources);
|
|
362
|
+
const trace = turnTraceRow(sources, calls, options.exports);
|
|
363
|
+
const traceWired = trace.wired;
|
|
364
|
+
const reports = [
|
|
365
|
+
{ row: pinRow(pin, pkg.spec), id: null, optedOut: null },
|
|
366
|
+
{ row: trace.row, id: "turn-trace", optedOut: trace.optedOut },
|
|
367
|
+
{ row: firstTextRow(pin, sources, traceWired, calls, options.exports), id: "first-text", optedOut: null },
|
|
368
|
+
{ row: prewarmRow(sources), id: "prewarm", optedOut: null },
|
|
369
|
+
{ row: placementRow(configs), id: "placement", optedOut: null },
|
|
370
|
+
{ row: toolCountRow(), id: null, optedOut: null },
|
|
371
|
+
{ row: limitAlarmsRow(sources), id: "limit-alarms", optedOut: null },
|
|
372
|
+
{ row: bundleRow(repoRoot, pkg.dir, configs, options.measureBundle ?? true), id: null, optedOut: null }
|
|
373
|
+
];
|
|
374
|
+
const rows = [];
|
|
375
|
+
const appFindings = [];
|
|
376
|
+
for (const entry of reports) {
|
|
377
|
+
if (entry.id === null) {
|
|
378
|
+
rows.push(entry.row);
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
const resolved = withExemption(entry.row, entry.id, exemptions, entry.optedOut);
|
|
382
|
+
rows.push(resolved.row);
|
|
383
|
+
if (resolved.finding) appFindings.push(resolved.finding);
|
|
384
|
+
}
|
|
385
|
+
const traceResolved = rows.find((row) => row.id === "turn-trace");
|
|
386
|
+
if (trace.optedOut && traceResolved) {
|
|
387
|
+
const at = rows.findIndex((row) => row.id === "first-text");
|
|
388
|
+
rows[at] = { id: "first-text", label: "first text", status: traceResolved.status, detail: "the turn trace is turned off" };
|
|
389
|
+
}
|
|
390
|
+
apps.push({ app: pkg.name, dir: pkg.dir, pin, rows, findings: appFindings });
|
|
391
|
+
findings.push(...appFindings.map((finding) => `${pkg.name}: ${finding}`));
|
|
392
|
+
}
|
|
393
|
+
return { apps, findings };
|
|
394
|
+
}
|
|
395
|
+
var STATUS_MARK = {
|
|
396
|
+
ok: "ok ",
|
|
397
|
+
gap: "GAP ",
|
|
398
|
+
unknown: "? ",
|
|
399
|
+
exempt: "exempt ",
|
|
400
|
+
"opted-out": "FAIL ",
|
|
401
|
+
na: "n/a "
|
|
402
|
+
};
|
|
403
|
+
function formatAppDefaults(result) {
|
|
404
|
+
if (result.apps.length === 0) return "no agent app in this checkout; nothing to report\n";
|
|
405
|
+
const lines = [];
|
|
406
|
+
for (const app of result.apps) {
|
|
407
|
+
lines.push(`${app.app}${app.dir === "." ? "" : ` (${app.dir})`}`);
|
|
408
|
+
for (const row of app.rows) lines.push(` ${STATUS_MARK[row.status]}${row.label.padEnd(15)} ${row.detail}`);
|
|
409
|
+
}
|
|
410
|
+
for (const finding of result.findings) lines.push(`FAIL ${finding}`);
|
|
411
|
+
if (result.findings.length > 0) lines.push("Defaults and how to opt out with a reason: https://github.com/tangle-network/agent-app/blob/main/docs/app-defaults.md");
|
|
412
|
+
return `${lines.join("\n")}
|
|
413
|
+
`;
|
|
414
|
+
}
|
|
415
|
+
async function readLastTurnExport(project, options) {
|
|
416
|
+
const base = (options.baseUrl ?? "https://intelligence.tangle.tools").replace(/\/+$/, "");
|
|
417
|
+
const get = async (path) => {
|
|
418
|
+
const response = await (options.fetchImpl ?? fetch)(`${base}${path}`, {
|
|
419
|
+
headers: { authorization: `Bearer ${options.apiKey}` },
|
|
420
|
+
signal: AbortSignal.timeout(15e3)
|
|
421
|
+
});
|
|
422
|
+
if (!response.ok) throw new Error(`Intelligence answered HTTP ${response.status} for ${path.split("?")[0]}`);
|
|
423
|
+
return response.json();
|
|
424
|
+
};
|
|
425
|
+
const listing = await get(`/v1/traces?limit=1&q=agent_app.turn&attr=${encodeURIComponent(`tangle.project:${project}`)}`);
|
|
426
|
+
const newest = listing.items?.[0];
|
|
427
|
+
if (!newest) return null;
|
|
428
|
+
const spans = await get(`/v1/traces/${newest.traceId}/spans`);
|
|
429
|
+
const firstTextMs = Number(spans.items?.find((span) => span.name === "agent_app.turn")?.attributes?.["tangle.turn.first_text_ms"]);
|
|
430
|
+
return {
|
|
431
|
+
at: Number(BigInt(newest.startUnixNano) / 1000000n),
|
|
432
|
+
traceId: newest.traceId,
|
|
433
|
+
...Number.isFinite(firstTextMs) ? { firstTextMs } : {}
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
async function collectLastExports(repoRoot, apiKey) {
|
|
437
|
+
const tracked = trackedFiles(repoRoot);
|
|
438
|
+
const projects = /* @__PURE__ */ new Set();
|
|
439
|
+
for (const pkg of findAppPackages(repoRoot, tracked)) {
|
|
440
|
+
for (const call of sinkCalls(readSources(repoRoot, tracked, pkg.dir, []))) if (call.project) projects.add(call.project);
|
|
441
|
+
}
|
|
442
|
+
const exports = /* @__PURE__ */ new Map();
|
|
443
|
+
for (const project of projects) {
|
|
444
|
+
try {
|
|
445
|
+
exports.set(project, await readLastTurnExport(project, { apiKey }));
|
|
446
|
+
} catch {
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return exports;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// src/signoff/config.ts
|
|
105
453
|
var SIGNOFF_CONFIG_FILES = ["signoff.config.mjs", "signoff.config.js"];
|
|
106
454
|
var shuffleSchema = z.object({
|
|
107
455
|
runs: z.number().int().positive().optional(),
|
|
@@ -133,7 +481,12 @@ var configSchema = z.object({
|
|
|
133
481
|
carryFiles: z.array(z.string()).optional(),
|
|
134
482
|
cacheDir: z.string().optional(),
|
|
135
483
|
storeGenerations: z.number().int().positive().optional(),
|
|
136
|
-
workerConfigs: z.array(z.string().min(1)).optional()
|
|
484
|
+
workerConfigs: z.array(z.string().min(1)).optional(),
|
|
485
|
+
agentApp: z.object({
|
|
486
|
+
exemptions: z.object(
|
|
487
|
+
Object.fromEntries(EXEMPTIBLE_DEFAULTS.map((id) => [id, z.string().trim().min(8, "say why in a sentence").optional()]))
|
|
488
|
+
).strict().optional()
|
|
489
|
+
}).optional()
|
|
137
490
|
});
|
|
138
491
|
function describeIssues(error, where) {
|
|
139
492
|
const lines = error.issues.map((issue) => ` ${issue.path.join(".") || "(root)"}: ${issue.message}`);
|
|
@@ -143,8 +496,10 @@ ${lines.join("\n")}`;
|
|
|
143
496
|
function parseSignoffConfig(value, where) {
|
|
144
497
|
const result = configSchema.safeParse(value);
|
|
145
498
|
if (!result.success) throw new Error(describeIssues(result.error, where));
|
|
146
|
-
|
|
147
|
-
|
|
499
|
+
for (const reserved of [WORKER_DEFAULTS_STEP, APP_DEFAULTS_STEP]) {
|
|
500
|
+
if (result.data.steps.some((step) => step.name === reserved)) {
|
|
501
|
+
throw new Error(`signoff: ${where} declares a step named "${reserved}", which the gate runs itself`);
|
|
502
|
+
}
|
|
148
503
|
}
|
|
149
504
|
return result.data;
|
|
150
505
|
}
|
|
@@ -180,18 +535,18 @@ async function loadSignoffConfig(options) {
|
|
|
180
535
|
const { repoRoot, configPath } = options;
|
|
181
536
|
if (configPath !== void 0) {
|
|
182
537
|
const abs = resolve(repoRoot, configPath);
|
|
183
|
-
if (!
|
|
538
|
+
if (!existsSync3(abs)) throw new Error(`signoff: no config at ${abs}`);
|
|
184
539
|
return { config: await importConfig(abs), origin: { kind: "file", path: abs } };
|
|
185
540
|
}
|
|
186
541
|
for (const candidate of SIGNOFF_CONFIG_FILES) {
|
|
187
|
-
const abs =
|
|
188
|
-
if (
|
|
542
|
+
const abs = join3(repoRoot, candidate);
|
|
543
|
+
if (existsSync3(abs)) return { config: await importConfig(abs), origin: { kind: "file", path: abs } };
|
|
189
544
|
}
|
|
190
|
-
const pkgPath =
|
|
191
|
-
if (!
|
|
545
|
+
const pkgPath = join3(repoRoot, "package.json");
|
|
546
|
+
if (!existsSync3(pkgPath)) {
|
|
192
547
|
throw new Error(`signoff: ${repoRoot} has no package.json, no signoff.config.mjs, and nothing to derive from.`);
|
|
193
548
|
}
|
|
194
|
-
const pkg = JSON.parse(
|
|
549
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
|
|
195
550
|
if (pkg.signoff !== void 0) {
|
|
196
551
|
return { config: parseSignoffConfig(pkg.signoff, `${pkgPath} "signoff"`), origin: { kind: "package-json", path: pkgPath } };
|
|
197
552
|
}
|
|
@@ -206,19 +561,19 @@ async function importConfig(abs) {
|
|
|
206
561
|
}
|
|
207
562
|
|
|
208
563
|
// src/signoff/run.ts
|
|
209
|
-
import { spawnSync as
|
|
210
|
-
import { existsSync as
|
|
564
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
565
|
+
import { existsSync as existsSync8 } from "fs";
|
|
211
566
|
import { availableParallelism, homedir } from "os";
|
|
212
|
-
import { basename as basename2, join as
|
|
567
|
+
import { basename as basename2, join as join8, resolve as resolve3 } from "path";
|
|
213
568
|
|
|
214
569
|
// src/signoff/node-version.ts
|
|
215
|
-
import { existsSync as
|
|
216
|
-
import { join as
|
|
570
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
571
|
+
import { join as join5 } from "path";
|
|
217
572
|
|
|
218
573
|
// src/signoff/workflow-pin.ts
|
|
219
|
-
import { existsSync as
|
|
220
|
-
import { join as
|
|
221
|
-
var WORKFLOW_DIR =
|
|
574
|
+
import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync3 } from "fs";
|
|
575
|
+
import { join as join4 } from "path";
|
|
576
|
+
var WORKFLOW_DIR = join4(".github", "workflows");
|
|
222
577
|
function scalarValue(raw) {
|
|
223
578
|
const withoutComment = raw.replace(/\s+#.*$/, "").trim();
|
|
224
579
|
const quoted = /^(['"])(.*)\1$/.exec(withoutComment);
|
|
@@ -268,24 +623,24 @@ function pinsInWorkflow(repoRoot, file, source) {
|
|
|
268
623
|
pins.push({ file, value, via: "node-version" });
|
|
269
624
|
continue;
|
|
270
625
|
}
|
|
271
|
-
const target =
|
|
272
|
-
if (!
|
|
626
|
+
const target = join4(repoRoot, value);
|
|
627
|
+
if (!existsSync4(target)) {
|
|
273
628
|
throw new Error(
|
|
274
629
|
`signoff: ${file} reads its Node pin from "${value}" (node-version-file) and that file does not exist. The workflow this gate replaces cannot itself run, so there is nothing to verify against.`
|
|
275
630
|
);
|
|
276
631
|
}
|
|
277
|
-
const declared =
|
|
632
|
+
const declared = readFileSync3(target, "utf8").split("\n").map((entry) => entry.trim()).find((entry) => entry.length > 0 && !entry.startsWith("#"));
|
|
278
633
|
if (declared !== void 0) pins.push({ file, value: declared, via: `node-version-file ${value}` });
|
|
279
634
|
}
|
|
280
635
|
return pins;
|
|
281
636
|
}
|
|
282
637
|
function scanMergeGateNodePins(repoRoot) {
|
|
283
|
-
const dir =
|
|
284
|
-
if (!
|
|
638
|
+
const dir = join4(repoRoot, WORKFLOW_DIR);
|
|
639
|
+
if (!existsSync4(dir)) return [];
|
|
285
640
|
const pins = [];
|
|
286
641
|
const files = readdirSync(dir).filter((name) => name.endsWith(".yml") || name.endsWith(".yaml")).sort();
|
|
287
642
|
for (const name of files) {
|
|
288
|
-
const source =
|
|
643
|
+
const source = readFileSync3(join4(dir, name), "utf8");
|
|
289
644
|
if (!triggersOnPullRequest(source)) continue;
|
|
290
645
|
pins.push(...pinsInWorkflow(repoRoot, `${WORKFLOW_DIR}/${name}`, source));
|
|
291
646
|
}
|
|
@@ -335,9 +690,9 @@ function resolveNodeRequirement(repoRoot, configured) {
|
|
|
335
690
|
return { major, declared: configured.trim(), source: "signoff config `nodeVersion`" };
|
|
336
691
|
}
|
|
337
692
|
let fromNvmrc = null;
|
|
338
|
-
const nvmrc =
|
|
339
|
-
if (
|
|
340
|
-
const raw =
|
|
693
|
+
const nvmrc = join5(repoRoot, ".nvmrc");
|
|
694
|
+
if (existsSync5(nvmrc)) {
|
|
695
|
+
const raw = readFileSync4(nvmrc, "utf8").trim();
|
|
341
696
|
const major = majorOf(raw);
|
|
342
697
|
if (major !== null) fromNvmrc = { major, declared: raw, source: ".nvmrc" };
|
|
343
698
|
}
|
|
@@ -361,9 +716,9 @@ function assertNodeVersion(requirement, running = process.version) {
|
|
|
361
716
|
}
|
|
362
717
|
function assertNodeTypesVersion(repoRoot, requirement) {
|
|
363
718
|
if (!requirement) return;
|
|
364
|
-
const packagePath =
|
|
365
|
-
if (!
|
|
366
|
-
const manifest = JSON.parse(
|
|
719
|
+
const packagePath = join5(repoRoot, "package.json");
|
|
720
|
+
if (!existsSync5(packagePath)) return;
|
|
721
|
+
const manifest = JSON.parse(readFileSync4(packagePath, "utf8"));
|
|
367
722
|
const declared = manifest.devDependencies?.["@types/node"] ?? manifest.dependencies?.["@types/node"] ?? manifest.optionalDependencies?.["@types/node"];
|
|
368
723
|
if (!declared) return;
|
|
369
724
|
const declaredMajor = /^[~^]?v?(\d+)(?:\.|$)/.exec(declared.trim())?.[1];
|
|
@@ -372,11 +727,11 @@ function assertNodeTypesVersion(repoRoot, requirement) {
|
|
|
372
727
|
`signoff: this repo pins Node ${requirement.declared} but declares @types/node ${declared}. Use the latest @types/node ${requirement.major}.x release.`
|
|
373
728
|
);
|
|
374
729
|
}
|
|
375
|
-
const installedPath =
|
|
376
|
-
if (!
|
|
730
|
+
const installedPath = join5(repoRoot, "node_modules", "@types", "node", "package.json");
|
|
731
|
+
if (!existsSync5(installedPath)) {
|
|
377
732
|
throw new Error(`signoff: @types/node is declared but missing after install in ${repoRoot}.`);
|
|
378
733
|
}
|
|
379
|
-
const installed = JSON.parse(
|
|
734
|
+
const installed = JSON.parse(readFileSync4(installedPath, "utf8"));
|
|
380
735
|
const installedMajor = majorOf(installed.version ?? "");
|
|
381
736
|
if (installedMajor !== requirement.major) {
|
|
382
737
|
throw new Error(
|
|
@@ -660,8 +1015,8 @@ function planAttempts(step, seedBase, overrideRuns) {
|
|
|
660
1015
|
|
|
661
1016
|
// src/signoff/store.ts
|
|
662
1017
|
import { createHash as createHash2 } from "crypto";
|
|
663
|
-
import { existsSync as
|
|
664
|
-
import { join as
|
|
1018
|
+
import { existsSync as existsSync6, mkdirSync, readFileSync as readFileSync5, readdirSync as readdirSync2, rmSync as rmSync2, statSync as statSync2, utimesSync, writeFileSync } from "fs";
|
|
1019
|
+
import { join as join6, relative, sep } from "path";
|
|
665
1020
|
var MANIFEST_FILES = [
|
|
666
1021
|
"pnpm-lock.yaml",
|
|
667
1022
|
"pnpm-workspace.yaml",
|
|
@@ -677,9 +1032,9 @@ function collectManifests(dir, root, out) {
|
|
|
677
1032
|
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
678
1033
|
if (entry.isDirectory()) {
|
|
679
1034
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
680
|
-
collectManifests(
|
|
1035
|
+
collectManifests(join6(dir, entry.name), root, out);
|
|
681
1036
|
} else if (MANIFEST_FILES.includes(entry.name)) {
|
|
682
|
-
out.push(relative(root,
|
|
1037
|
+
out.push(relative(root, join6(dir, entry.name)).split(sep).join("/"));
|
|
683
1038
|
}
|
|
684
1039
|
}
|
|
685
1040
|
}
|
|
@@ -693,20 +1048,20 @@ function manifestCacheKey(treePath, files) {
|
|
|
693
1048
|
for (const rel of files) {
|
|
694
1049
|
hash.update(rel);
|
|
695
1050
|
hash.update("\0");
|
|
696
|
-
hash.update(createHash2("sha256").update(
|
|
1051
|
+
hash.update(createHash2("sha256").update(readFileSync5(join6(treePath, rel))).digest("hex"));
|
|
697
1052
|
hash.update("\n");
|
|
698
1053
|
}
|
|
699
1054
|
return hash.digest("hex");
|
|
700
1055
|
}
|
|
701
1056
|
function pruneStores(storesRoot, keep) {
|
|
702
|
-
if (!
|
|
1057
|
+
if (!existsSync6(storesRoot)) return [];
|
|
703
1058
|
const entries = readdirSync2(storesRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => {
|
|
704
|
-
const full =
|
|
705
|
-
return { full, mtimeMs:
|
|
1059
|
+
const full = join6(storesRoot, entry.name);
|
|
1060
|
+
return { full, mtimeMs: statSync2(full).mtimeMs };
|
|
706
1061
|
}).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
707
1062
|
const pruned = [];
|
|
708
1063
|
for (const stale of entries.slice(keep)) {
|
|
709
|
-
|
|
1064
|
+
rmSync2(stale.full, { recursive: true, force: true });
|
|
710
1065
|
pruned.push(stale.full);
|
|
711
1066
|
}
|
|
712
1067
|
return pruned;
|
|
@@ -720,10 +1075,10 @@ function resolveStore(options) {
|
|
|
720
1075
|
);
|
|
721
1076
|
}
|
|
722
1077
|
const cacheKey = manifestCacheKey(treePath, files);
|
|
723
|
-
const storesRoot =
|
|
724
|
-
const storeDir =
|
|
725
|
-
const marker =
|
|
726
|
-
const hit =
|
|
1078
|
+
const storesRoot = join6(cacheDir, "stores");
|
|
1079
|
+
const storeDir = join6(storesRoot, cacheKey);
|
|
1080
|
+
const marker = join6(storeDir, ".signoff-store.json");
|
|
1081
|
+
const hit = existsSync6(storeDir) && readdirSync2(storeDir).some((entry) => entry !== ".signoff-store.json");
|
|
727
1082
|
mkdirSync(storeDir, { recursive: true });
|
|
728
1083
|
writeFileSync(marker, `${JSON.stringify({ cacheKey, keyedOn: files, usedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
|
|
729
1084
|
`);
|
|
@@ -733,12 +1088,12 @@ function resolveStore(options) {
|
|
|
733
1088
|
}
|
|
734
1089
|
|
|
735
1090
|
// src/signoff/workspace.ts
|
|
736
|
-
import { spawnSync as
|
|
1091
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
737
1092
|
import { createHash as createHash3 } from "crypto";
|
|
738
|
-
import { copyFileSync, existsSync as
|
|
739
|
-
import { dirname as dirname2, isAbsolute, join as
|
|
1093
|
+
import { copyFileSync, existsSync as existsSync7, mkdirSync as mkdirSync2, rmSync as rmSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
1094
|
+
import { dirname as dirname2, isAbsolute, join as join7, resolve as resolve2 } from "path";
|
|
740
1095
|
function git(args, cwd) {
|
|
741
|
-
const result =
|
|
1096
|
+
const result = spawnSync3("git", args, { cwd, encoding: "utf8", maxBuffer: 256 * 1024 * 1024 });
|
|
742
1097
|
if (result.error) throw new Error(`signoff: git ${args.join(" ")} failed to start: ${result.error.message}`);
|
|
743
1098
|
if (result.status !== 0) {
|
|
744
1099
|
throw new Error(`signoff: git ${args.join(" ")} exited ${result.status}
|
|
@@ -759,7 +1114,7 @@ function materializeCleanTree(options) {
|
|
|
759
1114
|
const branch = git(["rev-parse", "--abbrev-ref", "HEAD"], root).trim();
|
|
760
1115
|
git(["worktree", "prune"], root);
|
|
761
1116
|
mkdirSync2(dirname2(dest), { recursive: true });
|
|
762
|
-
if (
|
|
1117
|
+
if (existsSync7(dest)) rmSync3(dest, { recursive: true, force: true });
|
|
763
1118
|
git(["worktree", "add", "--detach", "--quiet", dest, head], root);
|
|
764
1119
|
let diffSha256 = null;
|
|
765
1120
|
let untrackedFiles = [];
|
|
@@ -767,28 +1122,28 @@ function materializeCleanTree(options) {
|
|
|
767
1122
|
const patch = git(["diff", "HEAD", "--binary", "--no-color", "--no-ext-diff"], root);
|
|
768
1123
|
if (patch.length > 0) {
|
|
769
1124
|
diffSha256 = createHash3("sha256").update(patch).digest("hex");
|
|
770
|
-
const patchFile =
|
|
1125
|
+
const patchFile = join7(dirname2(dest), `${dest.split("/").pop() ?? "tree"}.patch`);
|
|
771
1126
|
writeFileSync2(patchFile, patch);
|
|
772
1127
|
git(["apply", "--binary", "--whitespace=nowarn", patchFile], dest);
|
|
773
|
-
|
|
1128
|
+
rmSync3(patchFile, { force: true });
|
|
774
1129
|
}
|
|
775
1130
|
untrackedFiles = zsplit(git(["ls-files", "--others", "--exclude-standard", "-z"], root));
|
|
776
1131
|
for (const rel of untrackedFiles) {
|
|
777
|
-
const target =
|
|
1132
|
+
const target = join7(dest, rel);
|
|
778
1133
|
mkdirSync2(dirname2(target), { recursive: true });
|
|
779
|
-
copyFileSync(
|
|
1134
|
+
copyFileSync(join7(root, rel), target);
|
|
780
1135
|
}
|
|
781
1136
|
}
|
|
782
1137
|
const carried = [];
|
|
783
1138
|
for (const rel of carryFiles) {
|
|
784
1139
|
if (isAbsolute(rel)) throw new Error(`signoff: carryFiles must be repo-relative; got "${rel}"`);
|
|
785
1140
|
const from = resolve2(root, rel);
|
|
786
|
-
if (!
|
|
1141
|
+
if (!existsSync7(from)) {
|
|
787
1142
|
throw new Error(
|
|
788
1143
|
`signoff: carryFiles names "${rel}", which does not exist at ${from}. Remove it from the config or create the file \u2014 installing without it would resolve against a different registry than the one you think you are verifying.`
|
|
789
1144
|
);
|
|
790
1145
|
}
|
|
791
|
-
const target =
|
|
1146
|
+
const target = join7(dest, rel);
|
|
792
1147
|
mkdirSync2(dirname2(target), { recursive: true });
|
|
793
1148
|
copyFileSync(from, target);
|
|
794
1149
|
carried.push(rel);
|
|
@@ -810,9 +1165,9 @@ function removeCleanTree(tree) {
|
|
|
810
1165
|
}
|
|
811
1166
|
|
|
812
1167
|
// src/signoff/run.ts
|
|
813
|
-
var DEFAULT_CACHE_DIR =
|
|
1168
|
+
var DEFAULT_CACHE_DIR = join8(homedir(), ".cache", "agent-app-signoff");
|
|
814
1169
|
function hostFacts(treePath, requirement) {
|
|
815
|
-
const pm =
|
|
1170
|
+
const pm = spawnSync4("pnpm", ["--version"], { cwd: treePath, encoding: "utf8" });
|
|
816
1171
|
return {
|
|
817
1172
|
node: process.version,
|
|
818
1173
|
nodePinned: requirement?.declared ?? null,
|
|
@@ -854,7 +1209,7 @@ async function runSignoff(options = {}) {
|
|
|
854
1209
|
const repoRoot = repoRootOf(repoDir);
|
|
855
1210
|
const source = options.source ?? "working-tree";
|
|
856
1211
|
let cacheDir = resolve3(options.cacheDir ?? DEFAULT_CACHE_DIR);
|
|
857
|
-
const treePath = () =>
|
|
1212
|
+
const treePath = () => join8(cacheDir, "trees", `${basename2(repoRoot)}-${process.pid}`);
|
|
858
1213
|
let tree = null;
|
|
859
1214
|
try {
|
|
860
1215
|
tree = materializeCleanTree({ repoDir: repoRoot, dest: treePath(), source });
|
|
@@ -874,7 +1229,7 @@ async function runSignoff(options = {}) {
|
|
|
874
1229
|
const store = resolveStore({ treePath: tree.path, cacheDir, generations: config.storeGenerations });
|
|
875
1230
|
options.onEvent?.({ kind: "store", storeDir: store.storeDir, cacheHit: store.hit, cacheKey: store.cacheKey });
|
|
876
1231
|
const installSpec = config.install ?? {};
|
|
877
|
-
const installCwd =
|
|
1232
|
+
const installCwd = join8(tree.path, installSpec.cwd ?? ".");
|
|
878
1233
|
const installCommand = withStoreDir(
|
|
879
1234
|
installSpec.run ?? "pnpm install --frozen-lockfile",
|
|
880
1235
|
installSpec.storeDirFlag,
|
|
@@ -921,7 +1276,7 @@ async function runSignoff(options = {}) {
|
|
|
921
1276
|
origin,
|
|
922
1277
|
host,
|
|
923
1278
|
install,
|
|
924
|
-
steps: [...config.steps.map((step) => step.name), WORKER_DEFAULTS_STEP].map(
|
|
1279
|
+
steps: [...config.steps.map((step) => step.name), WORKER_DEFAULTS_STEP, APP_DEFAULTS_STEP].map(
|
|
925
1280
|
(name) => ({
|
|
926
1281
|
name,
|
|
927
1282
|
status: "skipped",
|
|
@@ -951,7 +1306,7 @@ async function runSignoff(options = {}) {
|
|
|
951
1306
|
options.onEvent?.({ kind: "step-start", name: step.name, command: plan.command, seed: plan.seed });
|
|
952
1307
|
const result = await runCommand({
|
|
953
1308
|
command: plan.command,
|
|
954
|
-
cwd:
|
|
1309
|
+
cwd: join8(treeRoot, step.cwd ?? "."),
|
|
955
1310
|
env: buildEnv(sharedEnv, [step.env]),
|
|
956
1311
|
timeoutMs: step.timeoutMs,
|
|
957
1312
|
signal
|
|
@@ -967,7 +1322,8 @@ async function runSignoff(options = {}) {
|
|
|
967
1322
|
return { ok: true, value: attempts };
|
|
968
1323
|
}
|
|
969
1324
|
});
|
|
970
|
-
const
|
|
1325
|
+
const appDefaults = runAppDefaults(tree.path, config, options);
|
|
1326
|
+
const steps = [...outcomes.map(toStepResult), workerDefaults, appDefaults];
|
|
971
1327
|
return finish({
|
|
972
1328
|
ok: steps.every((step) => step.status === "passed"),
|
|
973
1329
|
startedAt,
|
|
@@ -984,7 +1340,7 @@ async function runSignoff(options = {}) {
|
|
|
984
1340
|
options
|
|
985
1341
|
});
|
|
986
1342
|
} finally {
|
|
987
|
-
if (tree && !options.keepWorkspace &&
|
|
1343
|
+
if (tree && !options.keepWorkspace && existsSync8(tree.path)) removeCleanTree(tree);
|
|
988
1344
|
}
|
|
989
1345
|
}
|
|
990
1346
|
function runWorkerDefaults(treeRoot, workerConfigs, options) {
|
|
@@ -1009,6 +1365,28 @@ function runWorkerDefaults(treeRoot, workerConfigs, options) {
|
|
|
1009
1365
|
emitStepEnd(options, WORKER_DEFAULTS_STEP, ok ? "passed" : "failed", attempts);
|
|
1010
1366
|
return { name: WORKER_DEFAULTS_STEP, status: ok ? "passed" : "failed", attempts, durationMs, startedAtMs: null, finishedAtMs: null };
|
|
1011
1367
|
}
|
|
1368
|
+
function runAppDefaults(treeRoot, config, options) {
|
|
1369
|
+
const command = `agent-app-signoff ${APP_DEFAULTS_STEP}`;
|
|
1370
|
+
options.onEvent?.({ kind: "step-start", name: APP_DEFAULTS_STEP, command, seed: null });
|
|
1371
|
+
const started = Date.now();
|
|
1372
|
+
let output;
|
|
1373
|
+
let ok;
|
|
1374
|
+
try {
|
|
1375
|
+
const result = checkAppDefaults(treeRoot, { exemptions: config.agentApp?.exemptions });
|
|
1376
|
+
output = formatAppDefaults(result);
|
|
1377
|
+
ok = result.findings.length === 0;
|
|
1378
|
+
} catch (error) {
|
|
1379
|
+
output = `${error instanceof Error ? error.stack ?? error.message : String(error)}
|
|
1380
|
+
`;
|
|
1381
|
+
ok = false;
|
|
1382
|
+
}
|
|
1383
|
+
const durationMs = Date.now() - started;
|
|
1384
|
+
const attempts = [
|
|
1385
|
+
{ command, seed: null, exitCode: ok ? 0 : 1, signal: null, durationMs, timedOut: false, output, outputTruncated: false }
|
|
1386
|
+
];
|
|
1387
|
+
emitStepEnd(options, APP_DEFAULTS_STEP, ok ? "passed" : "failed", attempts);
|
|
1388
|
+
return { name: APP_DEFAULTS_STEP, status: ok ? "passed" : "failed", attempts, durationMs, startedAtMs: null, finishedAtMs: null };
|
|
1389
|
+
}
|
|
1012
1390
|
function emitStepEnd(options, name, status, attempts) {
|
|
1013
1391
|
options.onEvent?.({
|
|
1014
1392
|
kind: "step-end",
|
|
@@ -1394,7 +1772,7 @@ function round(value) {
|
|
|
1394
1772
|
}
|
|
1395
1773
|
|
|
1396
1774
|
// src/signoff/shell-alignment-cli.ts
|
|
1397
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
1775
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
1398
1776
|
import { dirname as dirname3, resolve as resolve4 } from "path";
|
|
1399
1777
|
function shellAlignmentUsage() {
|
|
1400
1778
|
return [
|
|
@@ -1448,7 +1826,7 @@ function parseShellAlignmentArgs(argv) {
|
|
|
1448
1826
|
out.storageState = resolve4(take());
|
|
1449
1827
|
break;
|
|
1450
1828
|
case "--theme-script":
|
|
1451
|
-
out.themeScript =
|
|
1829
|
+
out.themeScript = readFileSync6(resolve4(take()), "utf8");
|
|
1452
1830
|
break;
|
|
1453
1831
|
case "--ready":
|
|
1454
1832
|
out.readySelector = take();
|
|
@@ -1787,7 +2165,7 @@ function formatSurfaceLadderReport(report) {
|
|
|
1787
2165
|
}
|
|
1788
2166
|
|
|
1789
2167
|
// src/signoff/surface-ladder-cli.ts
|
|
1790
|
-
import { mkdirSync as mkdirSync4, readFileSync as
|
|
2168
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
1791
2169
|
import { dirname as dirname4, resolve as resolve5 } from "path";
|
|
1792
2170
|
function surfaceLadderUsage() {
|
|
1793
2171
|
return [
|
|
@@ -1849,7 +2227,7 @@ function parseSurfaceLadderArgs(argv) {
|
|
|
1849
2227
|
out.storageState = resolve5(take());
|
|
1850
2228
|
break;
|
|
1851
2229
|
case "--theme-script":
|
|
1852
|
-
out.themeScript =
|
|
2230
|
+
out.themeScript = readFileSync7(resolve5(take()), "utf8");
|
|
1853
2231
|
break;
|
|
1854
2232
|
case "--ready":
|
|
1855
2233
|
out.readySelector = take();
|
|
@@ -1867,7 +2245,7 @@ function parseSurfaceLadderArgs(argv) {
|
|
|
1867
2245
|
break;
|
|
1868
2246
|
}
|
|
1869
2247
|
case "--inject-css":
|
|
1870
|
-
out.injectCss =
|
|
2248
|
+
out.injectCss = readFileSync7(resolve5(take()), "utf8");
|
|
1871
2249
|
break;
|
|
1872
2250
|
case "--screenshots":
|
|
1873
2251
|
out.screenshotDir = resolve5(take());
|
|
@@ -2047,6 +2425,9 @@ export {
|
|
|
2047
2425
|
WORKER_COMPATIBILITY_DATE_FLOOR,
|
|
2048
2426
|
checkWorkerDefaults,
|
|
2049
2427
|
formatWorkerDefaults,
|
|
2428
|
+
checkAppDefaults,
|
|
2429
|
+
formatAppDefaults,
|
|
2430
|
+
collectLastExports,
|
|
2050
2431
|
loadSignoffConfig,
|
|
2051
2432
|
runSignoff,
|
|
2052
2433
|
formatSignoffReport,
|
|
@@ -2088,4 +2469,4 @@ export {
|
|
|
2088
2469
|
formatWorkspaceSurfaceReport,
|
|
2089
2470
|
runWorkspaceSurfaceCli
|
|
2090
2471
|
};
|
|
2091
|
-
//# sourceMappingURL=chunk-
|
|
2472
|
+
//# sourceMappingURL=chunk-J3N6RNMH.js.map
|