terminalhire 0.42.35 → 0.42.36

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.
@@ -30343,6 +30343,10 @@ var init_venueDescriptor = __esm({
30343
30343
  function fmtMs(ms) {
30344
30344
  return ms < 1e3 ? `${String(ms)}ms` : `${(ms / 1e3).toFixed(1)}s`;
30345
30345
  }
30346
+ function fmtMinutes(ms) {
30347
+ const min = ms / 6e4;
30348
+ return Number.isInteger(min) ? String(min) : min.toFixed(1);
30349
+ }
30346
30350
  function renderVerdictLine(r) {
30347
30351
  if (r.status === "refused") {
30348
30352
  const first = r.boundaryRefusals[0];
@@ -30366,7 +30370,7 @@ function renderVerdictLine(r) {
30366
30370
  case "environment-exhausted":
30367
30371
  return `OUR FAULT the run ran out of a resource we cap; your work has not been judged \u2014 ${t}`;
30368
30372
  case "budget-exceeded":
30369
- return `OUR LIMIT we stopped the run at our time limit; this is not a result about the work \u2014 ${t}`;
30373
+ return r.testTimeoutMs === void 0 ? `OUR LIMIT we stopped the run at our time limit; this is not a result about the work \u2014 ${t}` : `OUR LIMIT the ${fmtMinutes(r.testTimeoutMs)}-min test time limit set for this posting stopped the run; this is not a result about the work \u2014 ${t}`;
30370
30374
  case null:
30371
30375
  throw new Error("a verified run has no outcome \u2014 the result was assembled wrong");
30372
30376
  }
@@ -30388,7 +30392,7 @@ function renderRunReport(r) {
30388
30392
  if (r.status === "verified" && r.outcome === "counts-unparsed") {
30389
30393
  lines.push("", "Your suite exited 0, but we could not read how many tests ran, so this is not a verdict either way.");
30390
30394
  } else if (r.status === "verified" && r.outcome === "budget-exceeded") {
30391
- lines.push("", "Our time limit ended the run; that says nothing about your work.");
30395
+ lines.push("", r.testTimeoutMs === void 0 ? "Our time limit ended the run; that says nothing about your work." : `The ${fmtMinutes(r.testTimeoutMs)}-min test time limit set for this posting ended the run; that says nothing about your work.`);
30392
30396
  } else if (r.status === "verified" && r.outcome !== null && isOurFault(r.outcome)) {
30393
30397
  lines.push("", "This is an environment failure on our side, not a statement about your work.");
30394
30398
  }
@@ -30445,6 +30449,9 @@ var init_result = __esm({
30445
30449
  "testOutputTail",
30446
30450
  "counts",
30447
30451
  "wallMs",
30452
+ "installMs",
30453
+ "testMs",
30454
+ "testTimeoutMs",
30448
30455
  "targetRepo",
30449
30456
  "targetSha",
30450
30457
  "patchSha256",
@@ -30477,6 +30484,9 @@ var init_result = __esm({
30477
30484
  // printed as a block below the fields, when red
30478
30485
  counts: (r) => r.counts === null ? null : `tests ${String(r.counts.tests_passed)} passed, ${String(r.counts.tests_failed)} failed (${r.counts.runner})`,
30479
30486
  wallMs: (r) => `round trip ${fmtMs(r.wallMs)}`,
30487
+ installMs: (r) => r.installMs === void 0 ? null : `install step ${fmtMs(r.installMs)}`,
30488
+ testMs: (r) => r.testMs === void 0 ? null : `test step ${fmtMs(r.testMs)}`,
30489
+ testTimeoutMs: (r) => r.testTimeoutMs === void 0 ? null : `time limit ${fmtMinutes(r.testTimeoutMs)} min`,
30480
30490
  targetRepo: (r) => `target ${r.targetRepo}`,
30481
30491
  targetSha: (r) => `commit ${r.targetSha.slice(0, 12)}`,
30482
30492
  patchSha256: (r) => r.patchSha256 === null ? null : `patch ${r.patchSha256.slice(0, 12)}`,
@@ -30726,7 +30736,8 @@ var init_attestation2 = __esm({
30726
30736
  "test-command-unavailable": null,
30727
30737
  // null, with the same reasoning as `test-command-unavailable` and NOT
30728
30738
  // `budget-exceeded` (TERM-644). A signed `budget-exceeded` records one fact:
30729
- // the run hit OUR time budget. It blames no one (TERM-1289; `LIMIT_OWNER` in
30739
+ // the run hit the time budget we applied — our default, or the one set for the
30740
+ // posting (TERM-1308), which is still ours to enforce. It blames no one (TERM-1289; `LIMIT_OWNER` in
30730
30741
  // `classify.ts` owns that question), and it is not an acceptable run:
30731
30742
  // `acceptRun` (`packages/attest/src/verify.ts`) refuses every `budget_outcome`
30732
30743
  // except `completed`. Exhausting the tmpfs we sized, or an OOM kill from memory we
@@ -31425,7 +31436,7 @@ var init_gcpPlacement = __esm({
31425
31436
  this.name = "GcpPlacementError";
31426
31437
  }
31427
31438
  };
31428
- GCP_MAX_RUN_DURATION_SECONDS = 3600;
31439
+ GCP_MAX_RUN_DURATION_SECONDS = 10800;
31429
31440
  GCP_MANAGED_LABEL_KEY = "th-managed";
31430
31441
  GCP_RUN_LABEL_KEY = "th-run";
31431
31442
  CONFIDENTIAL_SPACE_MACHINE_TYPE = "n2d-standard-2";
@@ -33321,9 +33332,9 @@ var init_placement = __esm({
33321
33332
  });
33322
33333
 
33323
33334
  // ../../packages/envspec/dist/yaml.js
33324
- function parseYaml(source) {
33325
- const lines = splitLines(source);
33326
- const cursor = { lines, index: 0 };
33335
+ function parseYaml(source, limits = {}) {
33336
+ const lines = splitLines(source, limits.maxLineLength ?? Infinity);
33337
+ const cursor = { lines, index: 0, depth: 0, maxDepth: limits.maxDepth ?? Infinity };
33327
33338
  skipIgnorable(cursor);
33328
33339
  if (cursor.index >= lines.length)
33329
33340
  return null;
@@ -33335,13 +33346,16 @@ function parseYaml(source) {
33335
33346
  }
33336
33347
  return value;
33337
33348
  }
33338
- function splitLines(source) {
33349
+ function splitLines(source, maxLineLength) {
33339
33350
  const raw = source.replace(/\r\n/g, "\n").split("\n");
33340
33351
  const out = [];
33341
33352
  let sawDocumentStart = false;
33342
33353
  for (let i = 0; i < raw.length; i += 1) {
33343
33354
  const line = raw[i];
33344
33355
  const number = i + 1;
33356
+ if (line.length > maxLineLength) {
33357
+ throw new YamlUnsupportedError("line-length", number, `line longer than ${maxLineLength} characters`);
33358
+ }
33345
33359
  const withoutIndent = line.replace(/^[ ]+/, "");
33346
33360
  const indent = line.length - withoutIndent.length;
33347
33361
  if (/^[ ]*\t/.test(line)) {
@@ -33378,6 +33392,18 @@ function peek(cursor) {
33378
33392
  return cursor.index < cursor.lines.length ? cursor.lines[cursor.index] : null;
33379
33393
  }
33380
33394
  function parseNode(cursor, indent) {
33395
+ if (cursor.depth >= cursor.maxDepth) {
33396
+ const at = peek(cursor);
33397
+ throw new YamlUnsupportedError("depth", at?.number ?? 0, `nesting deeper than ${cursor.maxDepth}`);
33398
+ }
33399
+ cursor.depth += 1;
33400
+ try {
33401
+ return parseNodeAt(cursor, indent);
33402
+ } finally {
33403
+ cursor.depth -= 1;
33404
+ }
33405
+ }
33406
+ function parseNodeAt(cursor, indent) {
33381
33407
  const line = peek(cursor);
33382
33408
  if (line === null)
33383
33409
  return null;
@@ -33797,6 +33823,178 @@ var init_database = __esm({
33797
33823
  }
33798
33824
  });
33799
33825
 
33826
+ // ../../packages/envspec/dist/semverRange.js
33827
+ function rangeAdmits(version, range) {
33828
+ const locked = parseVersion(version);
33829
+ if (locked === null || range.length > MAX_RANGE_LENGTH)
33830
+ return null;
33831
+ let unknown = false;
33832
+ for (const alternative of range.split("||")) {
33833
+ const interval = parseSet(alternative);
33834
+ if (interval === null) {
33835
+ unknown = true;
33836
+ continue;
33837
+ }
33838
+ if (within(locked, interval))
33839
+ return true;
33840
+ }
33841
+ return unknown ? null : false;
33842
+ }
33843
+ function parseVersion(text) {
33844
+ const m = /^v?(\d+)\.(\d+)\.(\d+)(?:\+[0-9A-Za-z.-]+)?$/.exec(text.trim());
33845
+ if (m === null || !NUMBER.test(m[1]) || !NUMBER.test(m[2]) || !NUMBER.test(m[3]))
33846
+ return null;
33847
+ return [Number(m[1]), Number(m[2]), Number(m[3])];
33848
+ }
33849
+ function parseSet(text) {
33850
+ const set = text.trim();
33851
+ if (set === "")
33852
+ return { low: null, high: null };
33853
+ const hyphen = /^(\S+)\s+-\s+(\S+)$/.exec(set);
33854
+ if (hyphen !== null) {
33855
+ const from = parsePartial(hyphen[1]);
33856
+ const to = parsePartial(hyphen[2]);
33857
+ if (from === null || to === null)
33858
+ return null;
33859
+ return intersect(atLeast2(from), atMost(to));
33860
+ }
33861
+ const comparators = set.replace(/(<=|>=|<|>|=|~>|~|\^)\s+/g, "$1").split(/\s+/);
33862
+ let interval = { low: null, high: null };
33863
+ for (const comparator of comparators) {
33864
+ const next = parseComparator(comparator);
33865
+ if (next === null)
33866
+ return null;
33867
+ interval = intersect(interval, next);
33868
+ }
33869
+ return interval;
33870
+ }
33871
+ function parsePartial(text) {
33872
+ const m = /^v?([^.]+)(?:\.([^.]+))?(?:\.([^.+]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(text);
33873
+ if (m === null)
33874
+ return null;
33875
+ const parts = [];
33876
+ let wild = false;
33877
+ for (const part of [m[1], m[2], m[3]]) {
33878
+ if (part === void 0 || /^[xX*]$/.test(part)) {
33879
+ wild = true;
33880
+ parts.push(null);
33881
+ continue;
33882
+ }
33883
+ if (wild || !NUMBER.test(part))
33884
+ return null;
33885
+ parts.push(Number(part));
33886
+ }
33887
+ return { major: parts[0] ?? null, minor: parts[1] ?? null, patch: parts[2] ?? null };
33888
+ }
33889
+ function parseComparator(text) {
33890
+ const m = /^(<=|>=|<|>|=|~>|~|\^)?(.*)$/.exec(text);
33891
+ if (m === null)
33892
+ return null;
33893
+ const operator = m[1] ?? "";
33894
+ const partial = parsePartial(m[2]);
33895
+ if (partial === null)
33896
+ return null;
33897
+ const { major, minor, patch } = partial;
33898
+ if (major === null) {
33899
+ return operator === "" || operator === "=" || operator === ">=" ? { low: null, high: null } : null;
33900
+ }
33901
+ switch (operator) {
33902
+ case "":
33903
+ case "=":
33904
+ return intersect(atLeast2(partial), atMost(partial));
33905
+ case ">=":
33906
+ return atLeast2(partial);
33907
+ case "<=":
33908
+ return atMost(partial);
33909
+ case ">":
33910
+ return { low: { at: bumpPartial(partial), inclusive: true }, high: null };
33911
+ case "<":
33912
+ return { low: null, high: { at: floor(partial), inclusive: false } };
33913
+ case "~":
33914
+ case "~>":
33915
+ return {
33916
+ low: { at: floor(partial), inclusive: true },
33917
+ high: { at: minor === null ? [major + 1, 0, 0] : [major, minor + 1, 0], inclusive: false }
33918
+ };
33919
+ case "^": {
33920
+ let high;
33921
+ if (major > 0 || minor === null)
33922
+ high = [major + 1, 0, 0];
33923
+ else if (minor > 0 || patch === null)
33924
+ high = [0, minor + 1, 0];
33925
+ else
33926
+ high = [0, 0, patch + 1];
33927
+ return { low: { at: floor(partial), inclusive: true }, high: { at: high, inclusive: false } };
33928
+ }
33929
+ default:
33930
+ return null;
33931
+ }
33932
+ }
33933
+ function floor(p) {
33934
+ return [p.major ?? 0, p.minor ?? 0, p.patch ?? 0];
33935
+ }
33936
+ function bumpPartial(p) {
33937
+ const major = p.major ?? 0;
33938
+ if (p.minor === null)
33939
+ return [major + 1, 0, 0];
33940
+ if (p.patch === null)
33941
+ return [major, p.minor + 1, 0];
33942
+ return [major, p.minor, p.patch + 1];
33943
+ }
33944
+ function atLeast2(p) {
33945
+ return p.major === null ? { low: null, high: null } : { low: { at: floor(p), inclusive: true }, high: null };
33946
+ }
33947
+ function atMost(p) {
33948
+ if (p.major === null)
33949
+ return { low: null, high: null };
33950
+ if (p.patch !== null && p.minor !== null) {
33951
+ return { low: null, high: { at: [p.major, p.minor, p.patch], inclusive: true } };
33952
+ }
33953
+ return { low: null, high: { at: bumpPartial(p), inclusive: false } };
33954
+ }
33955
+ function compare(a, b) {
33956
+ return a[0] - b[0] || a[1] - b[1] || a[2] - b[2];
33957
+ }
33958
+ function intersect(a, b) {
33959
+ const low = a.low === null ? b.low : b.low === null ? a.low : tighterLow(a.low, b.low);
33960
+ const high = a.high === null ? b.high : b.high === null ? a.high : tighterHigh(a.high, b.high);
33961
+ return { low, high };
33962
+ }
33963
+ function tighterLow(a, b) {
33964
+ const c = compare(a.at, b.at);
33965
+ if (c !== 0)
33966
+ return c > 0 ? a : b;
33967
+ return a.inclusive ? b : a;
33968
+ }
33969
+ function tighterHigh(a, b) {
33970
+ const c = compare(a.at, b.at);
33971
+ if (c !== 0)
33972
+ return c < 0 ? a : b;
33973
+ return a.inclusive ? b : a;
33974
+ }
33975
+ function within(v, interval) {
33976
+ const { low, high } = interval;
33977
+ if (low !== null) {
33978
+ const c = compare(v, low.at);
33979
+ if (c < 0 || c === 0 && !low.inclusive)
33980
+ return false;
33981
+ }
33982
+ if (high !== null) {
33983
+ const c = compare(v, high.at);
33984
+ if (c > 0 || c === 0 && !high.inclusive)
33985
+ return false;
33986
+ }
33987
+ return true;
33988
+ }
33989
+ var MAX_RANGE_LENGTH, NUMBER;
33990
+ var init_semverRange = __esm({
33991
+ "../../packages/envspec/dist/semverRange.js"() {
33992
+ "use strict";
33993
+ MAX_RANGE_LENGTH = 1024;
33994
+ NUMBER = /^(?:0|[1-9]\d{0,15})$/;
33995
+ }
33996
+ });
33997
+
33800
33998
  // ../../packages/envspec/dist/manifest.js
33801
33999
  function detectRuntime(repo) {
33802
34000
  const candidates = [];
@@ -33878,47 +34076,155 @@ function lockfileGaps(repo) {
33878
34076
  const pkg = readJsonObject(repo, "package.json");
33879
34077
  if (pkg === null)
33880
34078
  return null;
34079
+ const lockfile = ["package-lock.json", "npm-shrinkwrap.json", "yarn.lock", "pnpm-lock.yaml"].find((name) => repo.exists(name));
34080
+ if (lockfile === void 0)
34081
+ return null;
34082
+ const listingFor = lockfileListings(repo, pkg, lockfile);
34083
+ if (listingFor === null)
34084
+ return null;
34085
+ const missing = [];
34086
+ const rootListing = listingFor("");
34087
+ if (rootListing !== null) {
34088
+ for (const [n, r, sec] of wantedDependencies(pkg))
34089
+ if (!rootListing(n, r, sec))
34090
+ missing.push(`${n}@${r}`);
34091
+ }
34092
+ for (const dir of workspaceDirs(repo, pkg, lockfile)) {
34093
+ const listing = listingFor(dir);
34094
+ const manifest = readJsonObject(repo, `${dir}/package.json`);
34095
+ if (listing === null || manifest === null)
34096
+ continue;
34097
+ for (const [n, r, sec] of wantedDependencies(manifest)) {
34098
+ if (!listing(n, r, sec))
34099
+ missing.push(`${n}@${r} (${dir})`);
34100
+ }
34101
+ }
34102
+ return missing.length === 0 ? null : { lockfile, missing };
34103
+ }
34104
+ function wantedDependencies(pkg) {
33881
34105
  const wanted = [];
34106
+ const optional = readObjectField(pkg, "optionalDependencies");
33882
34107
  for (const section of DEPENDENCY_SECTIONS) {
33883
34108
  const deps = readObjectField(pkg, section);
33884
34109
  if (deps === null)
33885
34110
  continue;
33886
34111
  for (const [name, range] of Object.entries(deps)) {
34112
+ if (section === "dependencies" && readStringField(optional, name) !== null)
34113
+ continue;
33887
34114
  if (typeof range === "string")
33888
- wanted.push([name, range]);
34115
+ wanted.push([name, range, section]);
33889
34116
  }
33890
34117
  }
33891
- if (wanted.length === 0)
33892
- return null;
33893
- const check = (lockfile, listed) => {
33894
- if (listed === null)
33895
- return null;
33896
- const missing = wanted.filter(([n, r]) => !listed(n, r)).map(([n, r]) => `${n}@${r}`);
33897
- return missing.length === 0 ? null : { lockfile, missing };
33898
- };
33899
- for (const lockfile of ["package-lock.json", "npm-shrinkwrap.json"]) {
33900
- if (repo.exists(lockfile))
33901
- return check(lockfile, npmLockListing(repo, lockfile));
34118
+ return wanted;
34119
+ }
34120
+ function lockfileListings(repo, pkg, lockfile) {
34121
+ if (lockfile === "yarn.lock") {
34122
+ const root = yarnLockListing(repo.readText(lockfile));
34123
+ return root === null ? null : (dir) => dir === "" ? root : null;
33902
34124
  }
33903
- if (repo.exists("yarn.lock"))
33904
- return check("yarn.lock", yarnLockListing(repo.readText("yarn.lock")));
33905
- if (repo.exists("pnpm-lock.yaml")) {
33906
- return check("pnpm-lock.yaml", pnpmLockListing(repo.readText("pnpm-lock.yaml")));
34125
+ if (lockfile === "pnpm-lock.yaml") {
34126
+ const text = repo.readText(lockfile);
34127
+ const importers = pnpmImporters(text);
34128
+ const settings = pnpmSettings(text);
34129
+ const overrides = pnpmOverrides(text);
34130
+ if (importers === null || settings === null || overrides === null)
34131
+ return null;
34132
+ const honoured = sameOverrides(overrides, declaredPnpmOverrides(repo, pkg)) ? overrides : /* @__PURE__ */ new Map();
34133
+ const withOverrides = (listing) => (name, range, section) => {
34134
+ const override = honoured.get(name);
34135
+ return listing(name, range, section) || override !== void 0 && listing(name, override, section);
34136
+ };
34137
+ const withSettings = (listing) => (name, range, section) => !settings.autoInstallPeers && section === "peerDependencies" || settings.excludeLinksFromLockfile && range.startsWith("link:") || listing(name, range, section);
34138
+ const workspaceAware = text !== null && /^importers:/m.test(text);
34139
+ return (dir) => {
34140
+ const importer = importers.get(dir === "" ? "." : dir);
34141
+ if (importer !== void 0)
34142
+ return withSettings(withOverrides(pnpmImporterListing(importer)));
34143
+ return dir === "" || !workspaceAware ? null : () => false;
34144
+ };
33907
34145
  }
33908
- return null;
34146
+ return npmLockListings(repo, lockfile);
33909
34147
  }
33910
- function npmLockListing(repo, lockfile) {
34148
+ function npmLockListings(repo, lockfile) {
33911
34149
  const lock = readJsonObject(repo, lockfile);
33912
34150
  if (lock === null)
33913
34151
  return null;
33914
- const root = readObjectField(readObjectField(lock, "packages"), "");
33915
- if (root !== null) {
33916
- return (name, range) => DEPENDENCY_SECTIONS.some((s) => readStringField(readObjectField(root, s), name) === range);
34152
+ const packages = readObjectField(lock, "packages");
34153
+ if (readObjectField(packages, "") !== null) {
34154
+ return (dir) => {
34155
+ const entry = readObjectField(packages, dir);
34156
+ if (entry === null)
34157
+ return () => false;
34158
+ return (name, range) => DEPENDENCY_SECTIONS.some((s) => readStringField(readObjectField(entry, s), name) === range);
34159
+ };
33917
34160
  }
33918
34161
  const v1 = readObjectField(lock, "dependencies");
33919
34162
  if (v1 === null)
33920
34163
  return null;
33921
- return (name) => readObjectField(v1, name) !== null;
34164
+ const root = (name, range) => {
34165
+ const entry = readObjectField(v1, name);
34166
+ if (entry === null)
34167
+ return false;
34168
+ const version = readStringField(entry, "version");
34169
+ return version === null ? true : rangeAdmits(version, range) ?? true;
34170
+ };
34171
+ return (dir) => dir === "" ? root : null;
34172
+ }
34173
+ function workspaceDirs(repo, pkg, lockfile) {
34174
+ let patterns;
34175
+ if (lockfile === "pnpm-lock.yaml") {
34176
+ const text = repo.readText("pnpm-workspace.yaml");
34177
+ try {
34178
+ patterns = text === null ? [] : asStringList(mapGet(parseYaml(text, PNPM_LIMITS), "packages"));
34179
+ } catch (err) {
34180
+ if (!(err instanceof YamlUnsupportedError))
34181
+ throw err;
34182
+ patterns = [];
34183
+ }
34184
+ } else {
34185
+ const field = pkg["workspaces"];
34186
+ patterns = Array.isArray(field) ? field : readObjectField(pkg, "workspaces")?.["packages"];
34187
+ }
34188
+ if (!Array.isArray(patterns))
34189
+ return [];
34190
+ const budget = { visits: 0 };
34191
+ const included = /* @__PURE__ */ new Set();
34192
+ const excluded = /* @__PURE__ */ new Set();
34193
+ for (const raw of patterns) {
34194
+ if (typeof raw !== "string")
34195
+ continue;
34196
+ const negated = raw.startsWith("!");
34197
+ const pattern = (negated ? raw.slice(1) : raw).replace(/^\.\//, "").replace(/\/+$/, "");
34198
+ const segments = pattern.split("/");
34199
+ if (pattern === "" || /[{}[\]\\]/.test(pattern) || segments.some((s) => s === ".." || s === "." || s === "")) {
34200
+ continue;
34201
+ }
34202
+ for (const dir of matchSegments(repo, "", segments, 0, budget))
34203
+ (negated ? excluded : included).add(dir);
34204
+ }
34205
+ const manifestOf = (dir) => `${dir}/package.json`;
34206
+ return [...included].filter((dir) => !excluded.has(dir) && dir !== "" && readJsonObject(repo, manifestOf(dir)) !== null).sort().slice(0, WORKSPACE_LIMITS.maxWorkspaces);
34207
+ }
34208
+ function matchSegments(repo, base, segments, depth, budget) {
34209
+ if (segments.length === 0)
34210
+ return [base];
34211
+ if (depth > WORKSPACE_LIMITS.maxDepth || budget.visits >= WORKSPACE_LIMITS.maxVisits)
34212
+ return [];
34213
+ budget.visits += 1;
34214
+ const [head, ...rest] = segments;
34215
+ const at = (name) => base === "" ? name : `${base}/${name}`;
34216
+ const children = () => repo.listNames(base).filter((n) => n !== "node_modules" && !n.startsWith(".") && repo.isDirectory(at(n)));
34217
+ if (head === "**") {
34218
+ return [
34219
+ ...matchSegments(repo, base, rest, depth + 1, budget),
34220
+ ...children().flatMap((n) => matchSegments(repo, at(n), segments, depth + 1, budget))
34221
+ ];
34222
+ }
34223
+ if (!/[*?]/.test(head)) {
34224
+ return head !== "node_modules" && repo.isDirectory(at(head)) ? matchSegments(repo, at(head), rest, depth + 1, budget) : [];
34225
+ }
34226
+ const re = new RegExp(`^${head.replace(/[.+^$()|]/g, "\\$&").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]")}$`);
34227
+ return children().filter((n) => re.test(n)).flatMap((n) => matchSegments(repo, at(n), rest, depth + 1, budget));
33922
34228
  }
33923
34229
  function yarnLockListing(text) {
33924
34230
  if (text === null || !/^(# yarn lockfile v1|__metadata:)/m.test(text))
@@ -33937,40 +34243,120 @@ function yarnLockListing(text) {
33937
34243
  }
33938
34244
  return (name, range) => specs.has(`${name}@${range}`);
33939
34245
  }
33940
- function pnpmLockListing(text) {
33941
- if (text === null || !/^lockfileVersion:/m.test(text))
34246
+ function pnpmImporterListing(importer) {
34247
+ return (name, range) => asString(mapGet(mapGet(importer, "specifiers"), name)) === range || PNPM_SECTIONS.some((section) => asString(mapGet(mapGet(mapGet(importer, section), name), "specifier")) === range);
34248
+ }
34249
+ function pnpmSettings(text) {
34250
+ if (text === null)
33942
34251
  return null;
33943
- const lines = pnpmRootLines(text.split(/\r?\n/));
33944
- if (lines === null)
34252
+ const block = topLevelBlock(text.replace(/\r\n/g, "\n").split("\n"), "settings");
34253
+ let node = null;
34254
+ try {
34255
+ node = block === null ? null : mapGet(parseYaml(block, PNPM_LIMITS), "settings");
34256
+ } catch (err) {
34257
+ if (err instanceof YamlUnsupportedError)
34258
+ return null;
34259
+ throw err;
34260
+ }
34261
+ return {
34262
+ autoInstallPeers: asString(mapGet(node, "autoInstallPeers")) !== "false",
34263
+ excludeLinksFromLockfile: asString(mapGet(node, "excludeLinksFromLockfile")) === "true"
34264
+ };
34265
+ }
34266
+ function pnpmOverrides(text) {
34267
+ if (text === null)
33945
34268
  return null;
33946
- const unquote = (s) => s.replace(/^['"]|['"]$/g, "");
33947
- return (name, range) => lines.some((line, i) => {
33948
- if (unquote(line) === `${name}:` || line === `'${name}':` || line === `"${name}":`) {
33949
- const next = lines[i + 1] ?? "";
33950
- return next.startsWith("specifier:") && unquote(next.slice(10).trim()) === range;
33951
- }
33952
- const flat = /^(['"]?)(.+)\1:\s*(.+)$/.exec(line);
33953
- return flat !== null && flat[2] === name && unquote(flat[3] ?? "") === range;
33954
- });
34269
+ const block = topLevelBlock(text.replace(/\r\n/g, "\n").split("\n"), "overrides");
34270
+ if (block === null)
34271
+ return /* @__PURE__ */ new Map();
34272
+ let node;
34273
+ try {
34274
+ node = mapGet(parseYaml(block, PNPM_LIMITS), "overrides");
34275
+ } catch (err) {
34276
+ if (err instanceof YamlUnsupportedError)
34277
+ return null;
34278
+ throw err;
34279
+ }
34280
+ if (node === null)
34281
+ return /* @__PURE__ */ new Map();
34282
+ if (!isMap(node))
34283
+ return null;
34284
+ const overrides = /* @__PURE__ */ new Map();
34285
+ for (const { key, value } of node.entries) {
34286
+ const spec = asString(value);
34287
+ if (spec === null)
34288
+ return null;
34289
+ overrides.set(key, spec);
34290
+ }
34291
+ return overrides;
33955
34292
  }
33956
- function pnpmRootLines(raw) {
33957
- const importers = raw.indexOf("importers:");
33958
- if (importers === -1) {
33959
- const end = raw.indexOf("packages:");
33960
- return (end === -1 ? raw : raw.slice(0, end)).map((l) => l.trim());
34293
+ function declaredPnpmOverrides(repo, pkg) {
34294
+ const sources = [
34295
+ readObjectField(readObjectField(pkg, "pnpm"), "overrides"),
34296
+ readObjectField(pkg, "resolutions")
34297
+ ];
34298
+ const workspace = repo.readText("pnpm-workspace.yaml");
34299
+ if (workspace !== null) {
34300
+ let node;
34301
+ try {
34302
+ node = mapGet(parseYaml(workspace, PNPM_LIMITS), "overrides");
34303
+ } catch (err) {
34304
+ if (err instanceof YamlUnsupportedError)
34305
+ return null;
34306
+ throw err;
34307
+ }
34308
+ if (node !== null && !isMap(node))
34309
+ return null;
34310
+ if (isMap(node))
34311
+ sources.push(Object.fromEntries(node.entries.map((e) => [e.key, e.value])));
33961
34312
  }
33962
- const start = raw.findIndex((l, i) => i > importers && /^ {2}(['"]?)\.\1:\s*$/.test(l));
33963
- if (start === -1)
34313
+ const declared = /* @__PURE__ */ new Map();
34314
+ for (const source of sources) {
34315
+ for (const [key, value] of Object.entries(source ?? {})) {
34316
+ if (typeof value !== "string")
34317
+ return null;
34318
+ const seen = declared.get(key);
34319
+ if (seen !== void 0 && seen !== value)
34320
+ return null;
34321
+ declared.set(key, value);
34322
+ }
34323
+ }
34324
+ return declared;
34325
+ }
34326
+ function sameOverrides(a, b) {
34327
+ return b !== null && a.size === b.size && [...a].every(([key, value]) => b.get(key) === value);
34328
+ }
34329
+ function pnpmImporters(text) {
34330
+ if (text === null || !/^lockfileVersion:/m.test(text))
33964
34331
  return null;
33965
- const out = [];
33966
- for (const line of raw.slice(start + 1)) {
33967
- if (line.trim() === "")
33968
- continue;
33969
- if (!line.startsWith(" "))
33970
- break;
33971
- out.push(line.trim());
34332
+ const lines = text.replace(/\r\n/g, "\n").split("\n");
34333
+ try {
34334
+ const importers = topLevelBlock(lines, "importers");
34335
+ if (importers !== null) {
34336
+ const node = mapGet(parseYaml(importers, PNPM_LIMITS), "importers");
34337
+ return isMap(node) ? new Map(node.entries.map((e) => [e.key, e.value])) : null;
34338
+ }
34339
+ const entries = [];
34340
+ for (const key of [...PNPM_SECTIONS, "specifiers"]) {
34341
+ const block = topLevelBlock(lines, key);
34342
+ if (block !== null)
34343
+ entries.push({ key, value: mapGet(parseYaml(block, PNPM_LIMITS), key) });
34344
+ }
34345
+ return /* @__PURE__ */ new Map([[".", { kind: "map", entries }]]);
34346
+ } catch (err) {
34347
+ if (err instanceof YamlUnsupportedError)
34348
+ return null;
34349
+ throw err;
33972
34350
  }
33973
- return out;
34351
+ }
34352
+ function topLevelBlock(lines, key) {
34353
+ const start = lines.findIndex((l) => l === `${key}:` || l.startsWith(`${key}: `));
34354
+ if (start === -1)
34355
+ return null;
34356
+ let end = start + 1;
34357
+ while (end < lines.length && (lines[end] === "" || /^[ \t#]/.test(lines[end])))
34358
+ end += 1;
34359
+ return lines.slice(start, end).join("\n");
33974
34360
  }
33975
34361
  function unfrozenInstall(command) {
33976
34362
  const lead = /^(?:(?:sudo|time)\s+|env(?:\s+[A-Za-z_]\w*=\S*)*\s+)*/.exec(command)?.[0] ?? "";
@@ -34259,10 +34645,12 @@ function matchFirst(text, pattern) {
34259
34645
  const match2 = pattern.exec(text);
34260
34646
  return match2 === null ? null : match2[1];
34261
34647
  }
34262
- var RUNTIME_MANIFESTS, MAKEFILE_NAMES, C_FAMILY_SOURCE, MANIFEST_FILENAMES, JVM_BUILD_FILES, NPM_PLACEHOLDER_TEST, DEPENDENCY_SECTIONS, YARN_MUTABLE, REQUIREMENT_SPECIFIER, CPP_BUILD_FILES, CMAKE_DECLARES_TESTS, EXACT_VERSION;
34648
+ var RUNTIME_MANIFESTS, MAKEFILE_NAMES, C_FAMILY_SOURCE, MANIFEST_FILENAMES, JVM_BUILD_FILES, NPM_PLACEHOLDER_TEST, DEPENDENCY_SECTIONS, WORKSPACE_LIMITS, PNPM_LIMITS, PNPM_SECTIONS, YARN_MUTABLE, REQUIREMENT_SPECIFIER, CPP_BUILD_FILES, CMAKE_DECLARES_TESTS, EXACT_VERSION;
34263
34649
  var init_manifest2 = __esm({
34264
34650
  "../../packages/envspec/dist/manifest.js"() {
34265
34651
  "use strict";
34652
+ init_semverRange();
34653
+ init_yaml();
34266
34654
  RUNTIME_MANIFESTS = [
34267
34655
  { runtime: "node", files: ["package.json"] },
34268
34656
  { runtime: "python", files: ["pyproject.toml", "setup.py", "setup.cfg", "requirements.txt"] },
@@ -34286,6 +34674,9 @@ var init_manifest2 = __esm({
34286
34674
  "optionalDependencies",
34287
34675
  "peerDependencies"
34288
34676
  ];
34677
+ WORKSPACE_LIMITS = { maxDepth: 8, maxVisits: 2e3, maxWorkspaces: 200 };
34678
+ PNPM_LIMITS = { maxDepth: 8, maxLineLength: 4096 };
34679
+ PNPM_SECTIONS = ["dependencies", "devDependencies", "optionalDependencies"];
34289
34680
  YARN_MUTABLE = "YARN_ENABLE_IMMUTABLE_INSTALLS=false";
34290
34681
  REQUIREMENT_SPECIFIER = /^[A-Za-z0-9._-]+(\[[A-Za-z0-9._,-]+\])?([<>=!~]=?[A-Za-z0-9._*+-]+(,[<>=!~]=?[A-Za-z0-9._*+-]+)*)?$/;
34291
34682
  CPP_BUILD_FILES = [
@@ -34366,21 +34757,50 @@ var init_references = __esm({
34366
34757
  });
34367
34758
 
34368
34759
  // ../../packages/envspec/dist/repo.js
34369
- import { readdirSync as readdirSync4, readFileSync as readFileSync13, statSync as statSync5 } from "fs";
34760
+ import { readdirSync as readdirSync4, readFileSync as readFileSync13, realpathSync as realpathSync2, statSync as statSync5 } from "fs";
34370
34761
  import { join as join26, relative as relative2, sep as sep7 } from "path";
34371
34762
  function createRepoReader(repoPath) {
34372
34763
  const resolveIn = (relativePath) => relativePath === "" ? repoPath : join26(repoPath, relativePath);
34373
34764
  const toPosix = (absolute) => relative2(repoPath, absolute).split(sep7).join("/");
34765
+ let realRoot;
34766
+ const rootPrefix = () => {
34767
+ if (realRoot === void 0) {
34768
+ try {
34769
+ const real = realpathSync2(repoPath);
34770
+ realRoot = real.endsWith(sep7) ? real : real + sep7;
34771
+ } catch {
34772
+ realRoot = null;
34773
+ }
34774
+ }
34775
+ return realRoot;
34776
+ };
34777
+ const containedPath = (relativePath) => {
34778
+ const prefix = rootPrefix();
34779
+ if (prefix === null)
34780
+ return null;
34781
+ try {
34782
+ const real = realpathSync2(resolveIn(relativePath));
34783
+ return real + sep7 === prefix || real.startsWith(prefix) ? real : null;
34784
+ } catch {
34785
+ return null;
34786
+ }
34787
+ };
34374
34788
  const readText = (relativePath) => {
34789
+ const real = containedPath(relativePath);
34790
+ if (real === null)
34791
+ return null;
34375
34792
  try {
34376
- return readFileSync13(resolveIn(relativePath), "utf8");
34793
+ return readFileSync13(real, "utf8");
34377
34794
  } catch {
34378
34795
  return null;
34379
34796
  }
34380
34797
  };
34381
34798
  const statOf = (relativePath) => {
34799
+ const real = containedPath(relativePath);
34800
+ if (real === null)
34801
+ return null;
34382
34802
  try {
34383
- return statSync5(resolveIn(relativePath));
34803
+ return statSync5(real);
34384
34804
  } catch {
34385
34805
  return null;
34386
34806
  }
@@ -34426,11 +34846,14 @@ function createRepoReader(repoPath) {
34426
34846
  const st = statOf(relativeDir);
34427
34847
  if (st === null || !st.isDirectory())
34428
34848
  return [];
34849
+ let names;
34429
34850
  try {
34430
- return readdirSync4(resolveIn(relativeDir)).slice().sort();
34851
+ names = readdirSync4(resolveIn(relativeDir));
34431
34852
  } catch {
34432
34853
  return [];
34433
34854
  }
34855
+ const at = (name) => relativeDir === "" ? name : `${relativeDir}/${name}`;
34856
+ return names.filter((name) => containedPath(at(name)) !== null).sort();
34434
34857
  }
34435
34858
  };
34436
34859
  }
@@ -36467,6 +36890,12 @@ async function runVerification(req, ctx) {
36467
36890
  testOutputTail: outputTail,
36468
36891
  counts: verdict.counts,
36469
36892
  wallMs: Date.now() - startedAt,
36893
+ // TERM-1308: the steps' own durations, which `runEnvironmentSpec` measures, and the
36894
+ // limit applied to the test step. Absent rather than 0 or a default when there is
36895
+ // nothing to report, so a baseline never sizes a budget from a step that did not run.
36896
+ ...verdict.install === null ? {} : { installMs: verdict.install.wallMs },
36897
+ ...verdict.test === null ? {} : { testMs: verdict.test.wallMs },
36898
+ ...req.testTimeoutMs === void 0 ? {} : { testTimeoutMs: req.testTimeoutMs },
36470
36899
  targetRepo: publishableTarget(req.targetRepo),
36471
36900
  targetSha: req.targetSha,
36472
36901
  patchSha256,
@@ -38045,7 +38474,7 @@ import {
38045
38474
  renameSync as renameSync6,
38046
38475
  existsSync as existsSync14,
38047
38476
  lstatSync as lstatSync5,
38048
- realpathSync as realpathSync2,
38477
+ realpathSync as realpathSync3,
38049
38478
  rmSync as rmSync10,
38050
38479
  readdirSync as readdirSync5,
38051
38480
  statSync as statSync6,
@@ -38810,7 +39239,7 @@ function canonicalPath2(p) {
38810
39239
  if (typeof p !== "string" || p === "") return p;
38811
39240
  const resolved = pathResolve(p);
38812
39241
  try {
38813
- return realpathSync2.native(resolved);
39242
+ return realpathSync3.native(resolved);
38814
39243
  } catch {
38815
39244
  return resolved;
38816
39245
  }
@@ -38891,6 +39320,66 @@ async function fetchFreshClaimablePool() {
38891
39320
  const contributions = (index?.contribute ?? []).filter((j) => j.source === "contribute");
38892
39321
  return [...bounties, ...contributions];
38893
39322
  }
39323
+ var BASELINE_STATUSES = /* @__PURE__ */ new Set([
39324
+ "pending",
39325
+ "not-scheduled",
39326
+ "passed",
39327
+ "failed",
39328
+ "unreadable",
39329
+ "no-tests",
39330
+ "over-ceiling",
39331
+ "not-runnable",
39332
+ "not-runnable-policy",
39333
+ "over-cap"
39334
+ ]);
39335
+ var MIN_TEST_BUDGET_MINUTES = 5;
39336
+ var MAX_TEST_BUDGET_MINUTES = 120;
39337
+ function baselineFields(b) {
39338
+ const out = {};
39339
+ const m = b?.testBudgetMinutes;
39340
+ if (Number.isInteger(m) && m >= MIN_TEST_BUDGET_MINUTES && m <= MAX_TEST_BUDGET_MINUTES) {
39341
+ out.testBudgetMinutes = m;
39342
+ }
39343
+ if (typeof b?.baselineStatus === "string" && BASELINE_STATUSES.has(b.baselineStatus)) {
39344
+ out.baselineStatus = b.baselineStatus;
39345
+ }
39346
+ if (typeof b?.baselineSha === "string" && /^[0-9a-f]{7,40}$/.test(b.baselineSha)) {
39347
+ out.baselineSha = b.baselineSha;
39348
+ }
39349
+ return out;
39350
+ }
39351
+ function baselineLine({ baselineStatus, baselineSha, testBudgetMinutes } = {}) {
39352
+ const at = typeof baselineSha === "string" ? ` at ${baselineSha.slice(0, 12)}` : "";
39353
+ const limit2 = typeof testBudgetMinutes === "number" ? ` Time limit ${String(testBudgetMinutes)} min.` : "";
39354
+ switch (baselineStatus) {
39355
+ case "passed":
39356
+ return `Tests ran on Terminal Hire's machines${at} and passed.${limit2}`;
39357
+ case "failed":
39358
+ return `Tests ran on Terminal Hire's machines${at} and did not pass.${limit2}`;
39359
+ case "unreadable":
39360
+ return `Tests ran; Terminal Hire could not read the result.${limit2}`;
39361
+ case "no-tests":
39362
+ return `Tests ran and none were observed.${limit2}`;
39363
+ case "over-ceiling":
39364
+ return "Terminal Hire stopped this repository's tests at the 120-min limit.";
39365
+ case "not-runnable":
39366
+ return "Terminal Hire could not run this repository's tests.";
39367
+ case "not-runnable-policy":
39368
+ return "Terminal Hire does not run tests for postings shared at this level.";
39369
+ case "pending":
39370
+ return "Test time being measured.";
39371
+ case "over-cap":
39372
+ return "Test time not measured yet.";
39373
+ default:
39374
+ return null;
39375
+ }
39376
+ }
39377
+ function postingTestBudgetMinutes(bountyId) {
39378
+ if (typeof bountyId !== "string") return null;
39379
+ const job = findClaimableInCache(bountyId);
39380
+ if (!job || job.source !== "bounty") return null;
39381
+ return baselineFields(job.bounty).testBudgetMinutes ?? null;
39382
+ }
38894
39383
  function extractClaimableFields(job) {
38895
39384
  if (job.source === "contribute") {
38896
39385
  const c = job.contribution ?? {};
@@ -38934,7 +39423,9 @@ function extractClaimableFields(job) {
38934
39423
  // (`publicRunRequirements`). Carried, never derived here: the CLI has no copy of the
38935
39424
  // repository before a claim, and a second derivation could disagree with the one the
38936
39425
  // poster saw. Null when the index carries none.
38937
- runRequirements: b.runRequirements && typeof b.runRequirements === "object" ? b.runRequirements : null
39426
+ runRequirements: b.runRequirements && typeof b.runRequirements === "object" ? b.runRequirements : null,
39427
+ // TERM-1308. Present only when the index row carries them; no key otherwise.
39428
+ ...b.bountySource === "founder" ? baselineFields(b) : {}
38938
39429
  };
38939
39430
  }
38940
39431
  function parseGitHubUrl(url) {
@@ -39545,6 +40036,7 @@ async function resolveBounty(arg) {
39545
40036
  let founderPosting = false;
39546
40037
  let claimMode = "open";
39547
40038
  let runRequirements = null;
40039
+ let baseline = {};
39548
40040
  let job = findClaimableInCache(arg) ?? (looksLikeShortRef(arg) ? findClaimableByShortRef(arg) : null);
39549
40041
  let freshPool;
39550
40042
  if (!job && looksLikeShortRef(arg)) {
@@ -39552,6 +40044,7 @@ async function resolveBounty(arg) {
39552
40044
  if (freshPool) job = findByShortRefInPool(freshPool, arg);
39553
40045
  }
39554
40046
  if (job) {
40047
+ let testBudgetMinutes, baselineStatus, baselineSha;
39555
40048
  ({
39556
40049
  bountyId,
39557
40050
  title,
@@ -39562,8 +40055,16 @@ async function resolveBounty(arg) {
39562
40055
  openPRsAtDiscovery,
39563
40056
  founderPosting,
39564
40057
  claimMode,
39565
- runRequirements
40058
+ runRequirements,
40059
+ testBudgetMinutes,
40060
+ baselineStatus,
40061
+ baselineSha
39566
40062
  } = extractClaimableFields(job));
40063
+ baseline = {
40064
+ ...testBudgetMinutes === void 0 ? {} : { testBudgetMinutes },
40065
+ ...baselineStatus === void 0 ? {} : { baselineStatus },
40066
+ ...baselineSha === void 0 ? {} : { baselineSha }
40067
+ };
39567
40068
  indexNativeId = bountyId;
39568
40069
  } else {
39569
40070
  const parsed = parseGitHubUrl(arg);
@@ -39635,7 +40136,8 @@ async function resolveBounty(arg) {
39635
40136
  founderPosting,
39636
40137
  claimMode,
39637
40138
  // TERM-1259. Only a founder index hit carries these; every other path is null.
39638
- runRequirements: runRequirements ?? null
40139
+ runRequirements: runRequirements ?? null,
40140
+ ...baseline
39639
40141
  };
39640
40142
  }
39641
40143
  function fmtOpenPRsLine(b) {
@@ -40344,6 +40846,8 @@ async function cmdPreview(arg, { json } = {}) {
40344
40846
  openPRs: b.openPRs,
40345
40847
  assignees: b.assignees,
40346
40848
  contested: isContested(b),
40849
+ // TERM-1308. The posting's test budget and baseline, only when the index carried them.
40850
+ ...baselineFields(b),
40347
40851
  // Absent on a founder posting — see the scan above. A consumer must read
40348
40852
  // "no policy key" as "no policy step exists here", never as "clean".
40349
40853
  ...policy ? {
@@ -40374,6 +40878,8 @@ async function cmdPreview(arg, { json } = {}) {
40374
40878
  console.log(fmtOpenPRsLine(b));
40375
40879
  const previewContested = fmtContestedWarning(b);
40376
40880
  if (previewContested) console.log(previewContested);
40881
+ const previewBaseline = baselineLine(b);
40882
+ if (previewBaseline) console.log(` tests: ${previewBaseline}`);
40377
40883
  if (policy) printPolicySection(policy);
40378
40884
  if (process.stdout.isTTY) {
40379
40885
  try {
@@ -44042,6 +44548,8 @@ export {
44042
44548
  assertNoBooleanPath,
44043
44549
  attemptSliceDelivery,
44044
44550
  backgroundEnableFailed,
44551
+ baselineFields,
44552
+ baselineLine,
44045
44553
  beatFounderPresence,
44046
44554
  buildAssignmentComment,
44047
44555
  buildPatchSubmission,
@@ -44094,6 +44602,7 @@ export {
44094
44602
  pickBodySource,
44095
44603
  pickExistingPr,
44096
44604
  pickStartableClaim,
44605
+ postingTestBudgetMinutes,
44097
44606
  printNextSteps,
44098
44607
  printReleaseIsLocalOnly,
44099
44608
  printResolveHint,