@whittlelabs/sifter 0.43.1 → 0.43.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/bin.js +271 -370
  2. package/bin.js.map +4 -4
  3. package/package.json +1 -1
package/bin.js CHANGED
@@ -9780,12 +9780,129 @@ var require_canonicalise = __commonJS({
9780
9780
  }
9781
9781
  });
9782
9782
 
9783
+ // ../../packages/contracts/dist/client.js
9784
+ var require_client = __commonJS({
9785
+ "../../packages/contracts/dist/client.js"(exports2) {
9786
+ "use strict";
9787
+ Object.defineProperty(exports2, "__esModule", { value: true });
9788
+ exports2.createContractClient = createContractClient;
9789
+ function createContractClient({ baseUrl, transport }) {
9790
+ const root = baseUrl.replace(/\/+$/, "");
9791
+ async function request(method, template, options = {}) {
9792
+ const headers = { Accept: "application/json" };
9793
+ if (options.body !== void 0)
9794
+ headers["Content-Type"] = "application/json";
9795
+ Object.assign(headers, options.headers);
9796
+ let response;
9797
+ try {
9798
+ response = await transport(`${root}${filledPath(template, options.path)}${queryString(options.query)}`, {
9799
+ method: method.toUpperCase(),
9800
+ headers,
9801
+ body: options.body === void 0 ? void 0 : JSON.stringify(options.body),
9802
+ signal: options.signal
9803
+ });
9804
+ } catch (err2) {
9805
+ return failure("NETWORK_ERROR", err2 instanceof Error ? err2.message : "Network request failed", null);
9806
+ }
9807
+ return answered(response);
9808
+ }
9809
+ const call = (method) => (path, options) => request(method, path, options);
9810
+ return {
9811
+ get: call("get"),
9812
+ post: call("post"),
9813
+ put: call("put"),
9814
+ patch: call("patch"),
9815
+ delete: call("delete")
9816
+ };
9817
+ }
9818
+ function filledPath(template, params) {
9819
+ const values = params ?? {};
9820
+ return template.replace(/\{([^}]+)\}/g, (_, name2) => {
9821
+ const value = values[name2];
9822
+ if (value === void 0 || value === null)
9823
+ throw new TypeError(`${template} needs the path parameter ${name2}`);
9824
+ return encodeURIComponent(String(value));
9825
+ });
9826
+ }
9827
+ function queryString(query) {
9828
+ const search = new URLSearchParams();
9829
+ for (const [key, value] of Object.entries(query ?? {})) {
9830
+ if (value === void 0 || value === null)
9831
+ continue;
9832
+ for (const item of Array.isArray(value) ? value : [value])
9833
+ search.append(key, String(item));
9834
+ }
9835
+ const text = search.toString();
9836
+ return text ? `?${text}` : "";
9837
+ }
9838
+ async function answered(response) {
9839
+ const { status } = response;
9840
+ let text;
9841
+ try {
9842
+ text = await response.text();
9843
+ } catch (err2) {
9844
+ return failure("NETWORK_ERROR", err2 instanceof Error ? err2.message : "The response could not be read", status);
9845
+ }
9846
+ let payload;
9847
+ let parsed = true;
9848
+ if (text !== "") {
9849
+ try {
9850
+ payload = JSON.parse(text);
9851
+ } catch {
9852
+ parsed = false;
9853
+ }
9854
+ }
9855
+ if (response.ok) {
9856
+ if (text === "")
9857
+ return { success: true, data: void 0, status };
9858
+ if (!parsed)
9859
+ return failure("SERVER_ERROR", `The ${status} response was not JSON`, status);
9860
+ if (isRecord(payload) && payload.success === true)
9861
+ return { success: true, data: payload.data, status };
9862
+ return { success: true, data: payload, status };
9863
+ }
9864
+ if (isRecord(payload) && payload.success === false && isRecord(payload.error)) {
9865
+ const { code, message } = payload.error;
9866
+ if (typeof code === "string" && typeof message === "string") {
9867
+ return failure(code, message, status, saysMore(payload, payload.error) ? payload : void 0);
9868
+ }
9869
+ }
9870
+ return failure("SERVER_ERROR", `Request failed with status ${status}`, status, text !== "" && parsed ? payload : void 0);
9871
+ }
9872
+ function saysMore(envelope, error) {
9873
+ return Object.keys(envelope).some((key) => key !== "success" && key !== "error") || Object.keys(error).some((key) => key !== "code" && key !== "message");
9874
+ }
9875
+ function failure(code, message, status, body2) {
9876
+ return { success: false, error: { code, message, status, ...body2 !== void 0 ? { body: body2 } : {} } };
9877
+ }
9878
+ function isRecord(value) {
9879
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9880
+ }
9881
+ }
9882
+ });
9883
+
9884
+ // ../../packages/contracts/dist/services/loom.js
9885
+ var require_loom = __commonJS({
9886
+ "../../packages/contracts/dist/services/loom.js"(exports2) {
9887
+ "use strict";
9888
+ Object.defineProperty(exports2, "__esModule", { value: true });
9889
+ exports2.service = void 0;
9890
+ exports2.createClient = createClient;
9891
+ var client_1 = require_client();
9892
+ exports2.service = "whittle-loom";
9893
+ function createClient(options) {
9894
+ return (0, client_1.createContractClient)(options);
9895
+ }
9896
+ }
9897
+ });
9898
+
9783
9899
  // ../../packages/loom/dist/seed/seed-workflows.js
9784
9900
  var require_seed_workflows = __commonJS({
9785
9901
  "../../packages/loom/dist/seed/seed-workflows.js"(exports2) {
9786
9902
  "use strict";
9787
9903
  Object.defineProperty(exports2, "__esModule", { value: true });
9788
9904
  exports2.seedWorkflows = seedWorkflows;
9905
+ var loom_1 = require_loom();
9789
9906
  var canonicalise_1 = require_canonicalise();
9790
9907
  var errors_1 = require_errors2();
9791
9908
  var NULL_LOGGER = {
@@ -9799,13 +9916,16 @@ var require_seed_workflows = __commonJS({
9799
9916
  async function seedWorkflows(opts) {
9800
9917
  const logger = opts.logger ?? NULL_LOGGER;
9801
9918
  const fetchFn = opts.fetchFn ?? fetch;
9802
- const baseUrl = opts.loomApiUrl.replace(/\/$/, "");
9803
- const headers = { "Content-Type": "application/json" };
9804
- if ("accessToken" in opts.auth) {
9805
- headers["Authorization"] = `Bearer ${opts.auth.accessToken}`;
9806
- } else {
9807
- headers["X-API-Key"] = opts.auth.apiKey;
9808
- }
9919
+ const credential = "accessToken" in opts.auth ? { Authorization: `Bearer ${opts.auth.accessToken}` } : { "X-API-Key": opts.auth.apiKey };
9920
+ const loom = (0, loom_1.createClient)({
9921
+ baseUrl: opts.loomApiUrl,
9922
+ transport: (url, init2) => {
9923
+ const headers = new Headers(init2.headers);
9924
+ for (const [name2, value] of Object.entries(credential))
9925
+ headers.set(name2, value);
9926
+ return fetchFn(url, { ...init2, headers });
9927
+ }
9928
+ });
9809
9929
  const results = [];
9810
9930
  for (const workflow of opts.workflows) {
9811
9931
  let ownerClass;
@@ -9841,17 +9961,12 @@ var require_seed_workflows = __commonJS({
9841
9961
  nodes: body2.nodes.length,
9842
9962
  transitions: body2.transitions.length
9843
9963
  });
9844
- const response = await fetchFn(`${baseUrl}/api/admin/seed-workflow`, {
9845
- method: "POST",
9846
- headers,
9847
- body: JSON.stringify(body2)
9848
- });
9849
- const json = await response.json();
9850
- if (!response.ok || !json.success || !json.data) {
9851
- const message = json.error?.message ?? `seedWorkflows: ${workflow.name} failed with HTTP ${response.status}`;
9964
+ const result = await loom.post("/api/admin/seed-workflow", { body: body2 });
9965
+ if (!result.success) {
9966
+ const message = result.error.code === "SERVER_ERROR" ? `seedWorkflows: ${workflow.name} failed with HTTP ${result.error.status}` : result.error.message;
9852
9967
  throw new errors_1.SeedWorkflowsError(workflow.name, message);
9853
9968
  }
9854
- results.push(json.data);
9969
+ results.push(result.data);
9855
9970
  }
9856
9971
  return { workflows: results };
9857
9972
  }
@@ -15171,7 +15286,6 @@ var require_code_index = __commonJS({
15171
15286
  };
15172
15287
  Object.defineProperty(exports2, "__esModule", { value: true });
15173
15288
  exports2.CodeIndex = exports2.PathOutsideRootError = void 0;
15174
- exports2.declarationsInSource = declarationsInSource;
15175
15289
  var node_child_process_1 = require("node:child_process");
15176
15290
  var node_fs_1 = require("node:fs");
15177
15291
  var node_path_1 = __importDefault(require("node:path"));
@@ -15252,18 +15366,6 @@ var require_code_index = __commonJS({
15252
15366
  }
15253
15367
  return cap(found, options.limit ?? 100);
15254
15368
  }
15255
- /**
15256
- * How many files mention the name as a whole word: a cheap read of how
15257
- * common it is, from the text alone, before any file is parsed for it.
15258
- */
15259
- async mentions(name2) {
15260
- return (await this.candidates(name2)).length;
15261
- }
15262
- /** The declarations in a file that overlap any of the given line ranges (1-based, inclusive). */
15263
- async symbolsOverlapping(file, ranges) {
15264
- const symbols = await this.outline(file);
15265
- return symbols.filter((s) => ranges.some(([start2, end]) => s.startLine <= end && s.endLine >= start2));
15266
- }
15267
15369
  // ── Listing and confinement ────────────────────────────────────────────
15268
15370
  async list() {
15269
15371
  const files = [];
@@ -15345,9 +15447,6 @@ var require_code_index = __commonJS({
15345
15447
  }
15346
15448
  };
15347
15449
  exports2.CodeIndex = CodeIndex;
15348
- async function declarationsInSource(file, source, wasmDir) {
15349
- return (await analyze(file, source, (0, runtime_1.resolveWasmDir)(wasmDir)))?.symbols ?? [];
15350
- }
15351
15450
  async function analyze(file, source, wasmDir) {
15352
15451
  const spec = (0, languages_1.languageFor)(file);
15353
15452
  if (!spec)
@@ -15508,14 +15607,11 @@ var require_dist2 = __commonJS({
15508
15607
  "../../packages/code-intel/dist/index.js"(exports2) {
15509
15608
  "use strict";
15510
15609
  Object.defineProperty(exports2, "__esModule", { value: true });
15511
- exports2.wasmFiles = exports2.resolveWasmDir = exports2.languageFor = exports2.LANGUAGES = exports2.PathOutsideRootError = exports2.declarationsInSource = exports2.CodeIndex = void 0;
15610
+ exports2.wasmFiles = exports2.resolveWasmDir = exports2.languageFor = exports2.LANGUAGES = exports2.PathOutsideRootError = exports2.CodeIndex = void 0;
15512
15611
  var code_index_1 = require_code_index();
15513
15612
  Object.defineProperty(exports2, "CodeIndex", { enumerable: true, get: function() {
15514
15613
  return code_index_1.CodeIndex;
15515
15614
  } });
15516
- Object.defineProperty(exports2, "declarationsInSource", { enumerable: true, get: function() {
15517
- return code_index_1.declarationsInSource;
15518
- } });
15519
15615
  Object.defineProperty(exports2, "PathOutsideRootError", { enumerable: true, get: function() {
15520
15616
  return code_index_1.PathOutsideRootError;
15521
15617
  } });
@@ -56719,7 +56815,6 @@ var require_overlay = __commonJS({
56719
56815
  Object.defineProperty(exports2, "__esModule", { value: true });
56720
56816
  exports2.overlayModifier = void 0;
56721
56817
  exports2.applyOverlay = applyOverlay;
56722
- exports2.safeDestination = safeDestination;
56723
56818
  exports2.normalizeOrigin = normalizeOrigin;
56724
56819
  exports2.fetchCapped = fetchCapped;
56725
56820
  var fs_1 = require("fs");
@@ -59001,7 +59096,7 @@ var require_http_api = __commonJS({
59001
59096
  });
59002
59097
 
59003
59098
  // ../../packages/keep/dist/client.js
59004
- var require_client = __commonJS({
59099
+ var require_client2 = __commonJS({
59005
59100
  "../../packages/keep/dist/client.js"(exports2) {
59006
59101
  "use strict";
59007
59102
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -59720,7 +59815,7 @@ var require_dist4 = __commonJS({
59720
59815
  "use strict";
59721
59816
  Object.defineProperty(exports2, "__esModule", { value: true });
59722
59817
  exports2.waitForKeepToken = exports2.KeepCredentialsRefusedError = exports2.KeepRateLimitedError = exports2.KeepUnavailableError = exports2.AgentCredentialRevokedError = exports2.AgentTokenManager = exports2.ServiceTokenManager = exports2.KeepClient = void 0;
59723
- var client_1 = require_client();
59818
+ var client_1 = require_client2();
59724
59819
  Object.defineProperty(exports2, "KeepClient", { enumerable: true, get: function() {
59725
59820
  return client_1.KeepClient;
59726
59821
  } });
@@ -68578,13 +68673,28 @@ var require_typed_exit = __commonJS({
68578
68673
  }
68579
68674
  });
68580
68675
 
68676
+ // ../../packages/contracts/dist/services/jobs.js
68677
+ var require_jobs = __commonJS({
68678
+ "../../packages/contracts/dist/services/jobs.js"(exports2) {
68679
+ "use strict";
68680
+ Object.defineProperty(exports2, "__esModule", { value: true });
68681
+ exports2.service = void 0;
68682
+ exports2.createClient = createClient;
68683
+ var client_1 = require_client();
68684
+ exports2.service = "whittle-jobs";
68685
+ function createClient(options) {
68686
+ return (0, client_1.createContractClient)(options);
68687
+ }
68688
+ }
68689
+ });
68690
+
68581
68691
  // ../../packages/jobs/dist/response.js
68582
68692
  var require_response = __commonJS({
68583
68693
  "../../packages/jobs/dist/response.js"(exports2) {
68584
68694
  "use strict";
68585
68695
  Object.defineProperty(exports2, "__esModule", { value: true });
68586
68696
  exports2.NonJsonResponseError = exports2.ServiceUnavailableError = void 0;
68587
- exports2.readJsonResponse = readJsonResponse;
68697
+ exports2.refuseUnreadable = refuseUnreadable;
68588
68698
  var EXCERPT_LIMIT = 120;
68589
68699
  var UNAVAILABLE_STATUSES = /* @__PURE__ */ new Set([502, 503, 504]);
68590
68700
  var UnreadableResponseError = class extends Error {
@@ -68638,31 +68748,31 @@ var require_response = __commonJS({
68638
68748
  return url;
68639
68749
  }
68640
68750
  }
68641
- async function readJsonResponse(response, url) {
68751
+ async function refuseUnreadable(response, url) {
68642
68752
  const contentType = response.headers.get("content-type");
68643
68753
  const unavailable = UNAVAILABLE_STATUSES.has(response.status);
68644
- if (unavailable || !isJson(contentType)) {
68645
- const body2 = await response.text().catch(() => "");
68646
- const detail = {
68647
- host: hostOf(url),
68648
- statusCode: response.status,
68649
- statusText: response.statusText ?? "",
68650
- contentType,
68651
- bodyExcerpt: excerpt(body2)
68652
- };
68653
- throw unavailable ? new ServiceUnavailableError(detail) : new NonJsonResponseError(detail);
68654
- }
68655
- return await response.json();
68754
+ if (!unavailable && isJson(contentType))
68755
+ return;
68756
+ const body2 = await response.text().catch(() => "");
68757
+ const detail = {
68758
+ host: hostOf(url),
68759
+ statusCode: response.status,
68760
+ statusText: response.statusText ?? "",
68761
+ contentType,
68762
+ bodyExcerpt: excerpt(body2)
68763
+ };
68764
+ throw unavailable ? new ServiceUnavailableError(detail) : new NonJsonResponseError(detail);
68656
68765
  }
68657
68766
  }
68658
68767
  });
68659
68768
 
68660
68769
  // ../../packages/jobs/dist/client.js
68661
- var require_client2 = __commonJS({
68770
+ var require_client3 = __commonJS({
68662
68771
  "../../packages/jobs/dist/client.js"(exports2) {
68663
68772
  "use strict";
68664
68773
  Object.defineProperty(exports2, "__esModule", { value: true });
68665
68774
  exports2.JobsClient = void 0;
68775
+ var jobs_1 = require_jobs();
68666
68776
  var response_1 = require_response();
68667
68777
  var JobsClient = class {
68668
68778
  baseUrl;
@@ -68702,19 +68812,26 @@ var require_client2 = __commonJS({
68702
68812
  * revoke-then-repair, clock skew) while the local expiry timer still thinks
68703
68813
  * it's valid. Force a re-mint and retry once before surfacing the failure.
68704
68814
  * `onResponse` (min-version contract) fires on the response we actually act
68705
- * on. `body` is a string, so it is safe to resend.
68815
+ * on. The contract client hands over its body as a string, so it is safe to resend.
68706
68816
  */
68707
- async fetchWithAuthRetry(method, url, body2) {
68708
- let response = await fetch(url, { method, headers: await this.buildHeaders(), body: body2 });
68817
+ async fetchWithAuthRetry(url, init2) {
68818
+ const send = async (forceRefresh) => {
68819
+ const headers = new Headers(init2.headers);
68820
+ for (const [name2, value] of Object.entries(await this.buildHeaders(forceRefresh)))
68821
+ headers.set(name2, value);
68822
+ return fetch(url, { ...init2, headers });
68823
+ };
68824
+ let response = await send(false);
68709
68825
  if (response.status === 401 && this.getAccessToken) {
68710
- response = await fetch(url, { method, headers: await this.buildHeaders(true), body: body2 });
68826
+ await response.body?.cancel().catch(() => void 0);
68827
+ response = await send(true);
68711
68828
  }
68712
68829
  this.onResponse?.(response);
68713
68830
  return response;
68714
68831
  }
68715
68832
  // ── Job Pools ──────────────────────────────────────────────
68716
68833
  async createJobPool(options) {
68717
- return this.request("POST", "/api/job-pools", options);
68834
+ return this.call((api) => api.post("/api/job-pools", { body: options }));
68718
68835
  }
68719
68836
  /**
68720
68837
  * Idempotent upsert by owner and name. When the
@@ -68722,7 +68839,7 @@ var require_client2 = __commonJS({
68722
68839
  * constraints. The Sift provisioning hook relies on this semantics.
68723
68840
  */
68724
68841
  async findOrCreateJobPool(options) {
68725
- return this.request("POST", "/api/job-pools?idempotent=true", options);
68842
+ return this.call((api) => api.post("/api/job-pools", { query: { idempotent: "true" }, body: options }));
68726
68843
  }
68727
68844
  /**
68728
68845
  * Every tenant's pools, which needs jobs:job-pools:read at operator context
@@ -68730,17 +68847,12 @@ var require_client2 = __commonJS({
68730
68847
  * one exact name; neither confers standing (BE-035).
68731
68848
  */
68732
68849
  async listJobPools(filters) {
68733
- const params = new URLSearchParams();
68734
- if (filters?.owner)
68735
- params.set("owner", `${filters.owner.principalClass}:${filters.owner.principalId}`);
68736
- if (filters?.name)
68737
- params.set("name", filters.name);
68738
- const qs = params.toString();
68739
- const { pools } = await this.request("GET", `/api/job-pools${qs ? `?${qs}` : ""}`);
68850
+ const owner = filters?.owner ? `${filters.owner.principalClass}:${filters.owner.principalId}` : void 0;
68851
+ const { pools } = await this.call((api) => api.get("/api/job-pools", { query: { owner, name: filters?.name } }));
68740
68852
  return pools;
68741
68853
  }
68742
68854
  async getJobPool(poolId) {
68743
- return this.request("GET", `/api/job-pools/${poolId}`);
68855
+ return this.call((api) => api.get("/api/job-pools/{id}", { path: { id: poolId } }));
68744
68856
  }
68745
68857
  /**
68746
68858
  * Resolve a pool by exact name. Names are unique per OWNER, not globally
@@ -68776,46 +68888,51 @@ var require_client2 = __commonJS({
68776
68888
  // A subscriber joins a pool by registering as a member of it. These
68777
68889
  // map to the server's `/api/job-pools/:poolId/members` routes.
68778
68890
  async listMembers(poolId) {
68779
- const { members } = await this.request("GET", `/api/job-pools/${poolId}/members`);
68891
+ const { members } = await this.call((api) => api.get("/api/job-pools/{poolId}/members", { path: { poolId } }));
68780
68892
  return members;
68781
68893
  }
68782
68894
  async registerMember(poolId, options) {
68783
- return this.request("POST", `/api/job-pools/${poolId}/members`, options);
68895
+ return this.call((api) => api.post("/api/job-pools/{poolId}/members", { path: { poolId }, body: options }));
68784
68896
  }
68785
68897
  async updateMember(poolId, memberId, options) {
68786
- return this.request("PUT", `/api/job-pools/${poolId}/members/${memberId}`, options);
68898
+ return this.call((api) => api.put("/api/job-pools/{poolId}/members/{mid}", { path: { poolId, mid: memberId }, body: options }));
68787
68899
  }
68788
68900
  // ── Jobs (producer) ──────────────────────────────────────────────
68789
68901
  /**
68790
68902
  * Enqueue a job into a job pool. The `inputs` are opaque to Jobs; a Shuttle
68791
68903
  * reads the strategy header inside them to decide how to run it (ADR 0005).
68904
+ * The body names its fields one by one, so nothing a caller spreads into
68905
+ * the options reaches the wire.
68792
68906
  */
68793
68907
  async enqueueJob(options) {
68794
68908
  const jobPoolId = await this.resolvePoolRef(options.pool);
68795
- const body2 = {
68796
- jobPoolId,
68797
- title: options.title,
68798
- progressChannel: options.progressChannel,
68799
- instructions: options.instructions,
68800
- maxAttempts: options.maxAttempts,
68801
- deadlineDuration: options.deadlineDuration,
68802
- claimDeadlineDuration: options.claimDeadlineDuration,
68803
- priority: options.priority,
68804
- tags: options.tags,
68805
- clientRef: options.clientRef,
68806
- clientContext: options.clientContext,
68807
- inputs: options.inputs
68808
- };
68809
- return this.request("POST", "/api/jobs", body2);
68909
+ return this.call((api) => api.post("/api/jobs", {
68910
+ body: {
68911
+ jobPoolId,
68912
+ title: options.title,
68913
+ progressChannel: options.progressChannel,
68914
+ instructions: options.instructions,
68915
+ maxAttempts: options.maxAttempts,
68916
+ deadlineDuration: options.deadlineDuration,
68917
+ claimDeadlineDuration: options.claimDeadlineDuration,
68918
+ priority: options.priority,
68919
+ tags: options.tags,
68920
+ clientRef: options.clientRef,
68921
+ clientContext: options.clientContext,
68922
+ inputs: options.inputs
68923
+ }
68924
+ }));
68810
68925
  }
68811
68926
  async getJob(jobId) {
68812
- return this.request("GET", `/api/jobs/${jobId}`);
68927
+ return this.call((api) => api.get("/api/jobs/{id}", { path: { id: jobId } }));
68813
68928
  }
68929
+ /** Answers the job without its inputs, as the route does. */
68814
68930
  async updateJob(jobId, options) {
68815
- return this.request("PATCH", `/api/jobs/${jobId}`, options);
68931
+ return this.call((api) => api.patch("/api/jobs/{id}", { path: { id: jobId }, body: options }));
68816
68932
  }
68933
+ /** Answers the job without its inputs, as the route does. */
68817
68934
  async cancelJob(jobId) {
68818
- return this.request("PUT", `/api/jobs/${jobId}`);
68935
+ return this.call((api) => api.put("/api/jobs/{id}", { path: { id: jobId } }));
68819
68936
  }
68820
68937
  /**
68821
68938
  * Permanently delete every job filed under `clientRefPrefix` (which must end
@@ -68823,14 +68940,14 @@ var require_client2 = __commonJS({
68823
68940
  * the caller needs `jobs:job-purges:write` at '*'. Idempotent.
68824
68941
  */
68825
68942
  async purgeJobs(clientRefPrefix) {
68826
- return this.request("POST", "/api/job-purges", { clientRefPrefix });
68943
+ return this.call((api) => api.post("/api/job-purges", { body: { clientRefPrefix } }));
68827
68944
  }
68828
68945
  // ── Outputs ──────────────────────────────────────────────────────
68829
68946
  async listOutputs(jobId) {
68830
68947
  const all = [];
68831
- const pageSize = 100;
68832
- for (let offset = 0; ; offset += pageSize) {
68833
- const { outputs, total } = await this.request("GET", `/api/jobs/${jobId}/output?limit=${pageSize}&offset=${offset}`);
68948
+ const limit = 100;
68949
+ for (let offset = 0; ; offset += limit) {
68950
+ const { outputs, total } = await this.call((api) => api.get("/api/jobs/{jobId}/output", { path: { jobId }, query: { limit, offset } }));
68834
68951
  all.push(...outputs);
68835
68952
  if (outputs.length === 0 || all.length >= total)
68836
68953
  break;
@@ -68838,7 +68955,7 @@ var require_client2 = __commonJS({
68838
68955
  return all;
68839
68956
  }
68840
68957
  async createOutput(jobId, options) {
68841
- return this.request("POST", `/api/jobs/${jobId}/output`, options);
68958
+ return this.call((api) => api.post("/api/jobs/{jobId}/output", { path: { jobId }, body: options }));
68842
68959
  }
68843
68960
  /**
68844
68961
  * Post a batch of live progress frames for a running job. Best-effort
@@ -68847,7 +68964,7 @@ var require_client2 = __commonJS({
68847
68964
  * let a dropped frame affect the job.
68848
68965
  */
68849
68966
  async postProgress(jobId, options) {
68850
- await this.request("POST", `/api/jobs/${jobId}/progress`, options);
68967
+ await this.call((api) => api.post("/api/jobs/{jobId}/progress", { path: { jobId }, body: options }));
68851
68968
  }
68852
68969
  // ── Attendance (subscriber dispatch lifecycle) ───────────────────
68853
68970
  /**
@@ -68856,7 +68973,10 @@ var require_client2 = __commonJS({
68856
68973
  * subscriber needs to execute and submit.
68857
68974
  */
68858
68975
  async acceptDispatch(jobId, options) {
68859
- return this.request("POST", `/api/jobs/${jobId}/attendance`, options?.shiftId ? { shiftId: options.shiftId } : void 0);
68976
+ return this.call((api) => api.post("/api/jobs/{jobId}/attendance", {
68977
+ path: { jobId },
68978
+ body: options?.shiftId ? { shiftId: options.shiftId } : void 0
68979
+ }));
68860
68980
  }
68861
68981
  // ── Shifts (whittlelabs ADR 0129) ────────────────────────────────
68862
68982
  /**
@@ -68864,39 +68984,33 @@ var require_client2 = __commonJS({
68864
68984
  * answers 404; the subscribe loop then claims without one.
68865
68985
  */
68866
68986
  async startShift() {
68867
- return this.request("POST", "/api/my/shifts");
68987
+ return this.call((api) => api.post("/api/my/shifts"));
68868
68988
  }
68869
68989
  /** End a shift, releasing every claim still taken on it back to the queue. */
68870
68990
  async endShift(shiftId) {
68871
- await this.request("DELETE", `/api/my/shifts/${shiftId}`);
68991
+ await this.call((api) => api.delete("/api/my/shifts/{shiftId}", { path: { shiftId } }));
68872
68992
  }
68873
68993
  async submit(jobId, options) {
68874
- return this.request("PUT", `/api/jobs/${jobId}/attendance`, options);
68994
+ return this.call((api) => api.put("/api/jobs/{jobId}/attendance", { path: { jobId }, body: options }));
68875
68995
  }
68876
68996
  // ── Discovery (rare) ─────────────────────────────────────────────
68877
68997
  async listDispatchableJobs(options) {
68878
- const params = new URLSearchParams();
68879
- if (options?.pools?.length)
68880
- params.set("pools", options.pools.join(","));
68881
- if (options?.tags?.length)
68882
- params.set("tags", options.tags.join(","));
68883
- if (options?.maxAttempts !== void 0)
68884
- params.set("maxAttempts", String(options.maxAttempts));
68885
- if (options?.minPriority !== void 0)
68886
- params.set("minPriority", String(options.minPriority));
68887
- if (options?.limit !== void 0)
68888
- params.set("limit", String(options.limit));
68889
- if (options?.offset !== void 0)
68890
- params.set("offset", String(options.offset));
68891
- if (options?.sort)
68892
- params.set("sort", options.sort);
68893
- if (options?.shift)
68894
- params.set("shift", options.shift);
68895
- const qs = params.toString();
68896
- const { jobs, total, directives, shift } = await this.request("GET", `/api/my/jobs${qs ? `?${qs}` : ""}`);
68998
+ const { jobs, total, directives, shift } = await this.call((api) => api.get("/api/my/jobs", {
68999
+ query: {
69000
+ pools: options?.pools?.length ? options.pools.join(",") : void 0,
69001
+ tags: options?.tags?.length ? options.tags.join(",") : void 0,
69002
+ maxAttempts: options?.maxAttempts === void 0 ? void 0 : String(options.maxAttempts),
69003
+ minPriority: options?.minPriority === void 0 ? void 0 : String(options.minPriority),
69004
+ limit: options?.limit,
69005
+ offset: options?.offset,
69006
+ sort: options?.sort || void 0,
69007
+ shift: options?.shift || void 0
69008
+ }
69009
+ }));
68897
69010
  return {
68898
69011
  data: jobs,
68899
69012
  pagination: { total, limit: options?.limit ?? 25, offset: options?.offset ?? 0 },
69013
+ // Absent from a Jobs that predates directives or shifts.
68900
69014
  directives: directives ?? null,
68901
69015
  shift: shift ?? null
68902
69016
  };
@@ -68910,38 +69024,47 @@ var require_client2 = __commonJS({
68910
69024
  async openHintStream(options = {}) {
68911
69025
  const qs = options.shift ? `?shift=${encodeURIComponent(options.shift)}` : "";
68912
69026
  const url = `${this.baseUrl}/api/my/jobs/stream${qs}`;
68913
- const headers = async (forceRefresh) => ({
68914
- ...await this.buildHeaders(forceRefresh),
68915
- Accept: "text/event-stream",
68916
- ...options.lastEventId ? { "Last-Event-ID": options.lastEventId } : {}
69027
+ return this.fetchWithAuthRetry(url, {
69028
+ headers: { Accept: "text/event-stream", ...options.lastEventId ? { "Last-Event-ID": options.lastEventId } : {} },
69029
+ signal: options.signal
68917
69030
  });
68918
- let response = await fetch(url, { headers: await headers(false), signal: options.signal });
68919
- if (response.status === 401 && this.getAccessToken) {
68920
- await response.body?.cancel().catch(() => void 0);
68921
- response = await fetch(url, { headers: await headers(true), signal: options.signal });
68922
- }
68923
- this.onResponse?.(response);
68924
- return response;
68925
69031
  }
68926
69032
  // ── HTTP plumbing ────────────────────────────────────────────────
68927
69033
  /**
68928
- * Every call's one reader. `readJsonResponse` refuses to parse a reply that
68929
- * is not the API's JSON — an ingress 502/503/504 while a pod rolls, or any
68930
- * non-JSON body — and throws a named failure instead of a parse error. What
68931
- * comes back here is the API's own envelope, success or not.
69034
+ * Every call's one path. The contract client builds the request and reads
69035
+ * the API's envelope; the transport under it authenticates (one retry on a
69036
+ * 401 for a token provider), fires `onResponse`, and refuses a reply that is
69037
+ * not the API's JSON with a named failure (`refuseUnreadable`: an ingress
69038
+ * 502/503/504 while a pod rolls, or any non-JSON body). A failure the
69039
+ * transport raises is rethrown as raised, so `ServiceUnavailableError` and
69040
+ * fetch's own `TypeError` reach the subscribe loop's outage handling rather
69041
+ * than arriving as a NETWORK_ERROR result. The API's own refusal throws an
69042
+ * `Error` carrying `code` and `statusCode`, as it always has.
68932
69043
  */
68933
- async request(method, path, body2) {
68934
- const url = `${this.baseUrl}${path}`;
68935
- const response = await this.fetchWithAuthRetry(method, url, body2 ? JSON.stringify(body2) : void 0);
68936
- const json = await (0, response_1.readJsonResponse)(response, url);
68937
- if (!response.ok || !json.success) {
68938
- const errorMessage = json.error?.message ?? `Request failed with status ${response.status}`;
68939
- const error = new Error(errorMessage);
68940
- error.code = json.error?.code;
68941
- error.statusCode = response.status;
68942
- throw error;
68943
- }
68944
- return json.data;
69044
+ async call(send) {
69045
+ let raised;
69046
+ const api = (0, jobs_1.createClient)({
69047
+ baseUrl: this.baseUrl,
69048
+ transport: async (url, init2) => {
69049
+ try {
69050
+ const response = await this.fetchWithAuthRetry(url, init2);
69051
+ await (0, response_1.refuseUnreadable)(response, url);
69052
+ return response;
69053
+ } catch (error2) {
69054
+ raised = { error: error2 };
69055
+ throw error2;
69056
+ }
69057
+ }
69058
+ });
69059
+ const result = await send(api);
69060
+ if (raised)
69061
+ throw raised.error;
69062
+ if (result.success)
69063
+ return result.data;
69064
+ const error = new Error(result.error.message);
69065
+ error.code = result.error.code;
69066
+ error.statusCode = result.error.status ?? void 0;
69067
+ throw error;
68945
69068
  }
68946
69069
  };
68947
69070
  exports2.JobsClient = JobsClient;
@@ -69824,7 +69947,7 @@ var require_dist7 = __commonJS({
69824
69947
  "use strict";
69825
69948
  Object.defineProperty(exports2, "__esModule", { value: true });
69826
69949
  exports2.subscribe = exports2.ServiceUnavailableError = exports2.NonJsonResponseError = exports2.JobsClient = void 0;
69827
- var client_1 = require_client2();
69950
+ var client_1 = require_client3();
69828
69951
  Object.defineProperty(exports2, "JobsClient", { enumerable: true, get: function() {
69829
69952
  return client_1.JobsClient;
69830
69953
  } });
@@ -73731,219 +73854,6 @@ var require_git = __commonJS({
73731
73854
  }
73732
73855
  });
73733
73856
 
73734
- // ../../packages/shuttle/dist/workspace/impact.js
73735
- var require_impact = __commonJS({
73736
- "../../packages/shuttle/dist/workspace/impact.js"(exports2) {
73737
- "use strict";
73738
- var __importDefault = exports2 && exports2.__importDefault || function(mod) {
73739
- return mod && mod.__esModule ? mod : { "default": mod };
73740
- };
73741
- Object.defineProperty(exports2, "__esModule", { value: true });
73742
- exports2.impactModifier = void 0;
73743
- exports2.applyImpact = applyImpact;
73744
- exports2.buildReport = buildReport;
73745
- var fs_1 = require("fs");
73746
- var path_1 = __importDefault(require("path"));
73747
- var code_intel_1 = require_dist2();
73748
- var overlay_1 = require_overlay();
73749
- var LIMITS = {
73750
- changedSymbols: 30,
73751
- usesShown: 8,
73752
- /** A name more files mention than this is too common for a list of its uses to help. */
73753
- commonInFiles: 80,
73754
- /** The report is read into the agent's context: past this it stops adding sections. */
73755
- reportBytes: 16 * 1024,
73756
- /** Work stops here and the report says it is partial; materialization must not eat the job. */
73757
- budgetMs: 6e4
73758
- };
73759
- var REMOVED_KINDS = /* @__PURE__ */ new Set([
73760
- "function",
73761
- "class",
73762
- "interface",
73763
- "type",
73764
- "enum",
73765
- "module",
73766
- "constant",
73767
- "macro"
73768
- ]);
73769
- exports2.impactModifier = { namespace: "impact", apply: applyImpact };
73770
- async function applyImpact(request, runRoot, _options) {
73771
- const result = { written: 0, writtenPaths: [], skipped: [] };
73772
- const parsed = readImpactRequest(request);
73773
- if ("error" in parsed) {
73774
- result.skipped.push({ path: String(request?.output ?? "?"), reason: parsed.error });
73775
- return result;
73776
- }
73777
- const { output, files } = parsed.request;
73778
- const rootReal = await fs_1.promises.realpath(runRoot);
73779
- const dest = (0, overlay_1.safeDestination)(rootReal, output);
73780
- if (!dest) {
73781
- result.skipped.push({ path: output, reason: "path escapes the workspace root" });
73782
- return result;
73783
- }
73784
- const primary = path_1.default.join(runRoot, "primary");
73785
- if ((await fs_1.promises.readdir(primary).catch(() => [])).length === 0) {
73786
- result.skipped.push({ path: output, reason: "no checkout to read: the primary repository did not materialize" });
73787
- return result;
73788
- }
73789
- try {
73790
- const report = await buildReport(runRoot, files);
73791
- await fs_1.promises.mkdir(path_1.default.dirname(dest), { recursive: true });
73792
- const handle2 = await fs_1.promises.open(dest, fs_1.constants.O_WRONLY | fs_1.constants.O_CREAT | fs_1.constants.O_TRUNC | fs_1.constants.O_NOFOLLOW, 420);
73793
- try {
73794
- await handle2.writeFile(report);
73795
- } finally {
73796
- await handle2.close();
73797
- }
73798
- result.written = 1;
73799
- result.writtenPaths.push(output);
73800
- } catch (err2) {
73801
- result.skipped.push({ path: output, reason: err2 instanceof Error ? err2.message : String(err2) });
73802
- }
73803
- return result;
73804
- }
73805
- function readImpactRequest(request) {
73806
- const r = request;
73807
- if (!r || typeof r.output !== "string" || !Array.isArray(r.files)) {
73808
- return { error: "malformed impact request (want { output, files })" };
73809
- }
73810
- const files = [];
73811
- for (const f of r.files) {
73812
- if (!f || typeof f.path !== "string" || !Array.isArray(f.changed))
73813
- continue;
73814
- files.push({
73815
- path: f.path,
73816
- status: typeof f.status === "string" ? f.status : "modified",
73817
- changed: f.changed.filter((c) => Array.isArray(c) && Number.isInteger(c[0]) && Number.isInteger(c[1])),
73818
- removed: Array.isArray(f.removed) ? f.removed.filter((t) => typeof t === "string") : []
73819
- });
73820
- }
73821
- return { request: { output: r.output, files } };
73822
- }
73823
- async function repositoriesOf(runRoot) {
73824
- const repos = ["primary"];
73825
- const context = await fs_1.promises.readdir(path_1.default.join(runRoot, "context"), { withFileTypes: true }).catch(() => []);
73826
- for (const entry of context)
73827
- if (entry.isDirectory())
73828
- repos.push(`context/${entry.name}`);
73829
- return repos;
73830
- }
73831
- function innermostOnly(symbols) {
73832
- return symbols.filter((s) => !symbols.some((o) => o !== s && o.startLine >= s.startLine && o.endLine <= s.endLine && (o.startLine !== s.startLine || o.endLine !== s.endLine)));
73833
- }
73834
- var useLine = (r) => ` - ${r.file}:${r.line} ${r.enclosing ? `in ${r.enclosing.kind} ${r.enclosing.name}` : "at top level"} | ${r.text}`;
73835
- async function buildReport(runRoot, files) {
73836
- const startedAt = Date.now();
73837
- const overBudget = () => Date.now() - startedAt > LIMITS.budgetMs;
73838
- const index = new code_intel_1.CodeIndex({ root: runRoot, repositories: await repositoriesOf(runRoot) });
73839
- let partial = false;
73840
- const removedSections = [];
73841
- for (const file of files) {
73842
- for (const block of file.removed ?? []) {
73843
- const lines = block.split("\n");
73844
- for (const symbol of await (0, code_intel_1.declarationsInSource)(file.path, block)) {
73845
- if (overBudget()) {
73846
- partial = true;
73847
- break;
73848
- }
73849
- const indented = /^\s/.test(lines[symbol.startLine - 1] ?? "");
73850
- if (indented || !REMOVED_KINDS.has(symbol.kind))
73851
- continue;
73852
- if ((await index.definitions(symbol.name, 1)).total > 0)
73853
- continue;
73854
- if (await index.mentions(symbol.name) > LIMITS.commonInFiles)
73855
- continue;
73856
- const uses = await index.references(symbol.name, { limit: LIMITS.usesShown, access: "bare" });
73857
- if (uses.total === 0)
73858
- continue;
73859
- removedSections.push([
73860
- `- ${symbol.kind} \`${symbol.name}\`, removed from primary/${file.path}, is still used ${times(uses.total)}:`,
73861
- ...uses.items.map(useLine),
73862
- ...uses.truncated ? [` - and ${uses.total - uses.items.length} more`] : []
73863
- ].join("\n"));
73864
- }
73865
- }
73866
- }
73867
- const changed = [];
73868
- const seen = /* @__PURE__ */ new Set();
73869
- for (const file of files) {
73870
- if (file.status === "removed" || file.changed.length === 0)
73871
- continue;
73872
- const touched = innermostOnly(await index.symbolsOverlapping(`primary/${file.path}`, file.changed).catch(() => []));
73873
- for (const s of touched) {
73874
- const key = `${s.file}:${s.startLine}:${s.name}`;
73875
- if (!seen.has(key)) {
73876
- seen.add(key);
73877
- changed.push(s);
73878
- }
73879
- }
73880
- }
73881
- if (changed.length > LIMITS.changedSymbols)
73882
- partial = true;
73883
- const changedSections = [];
73884
- const unused = [];
73885
- const common = [];
73886
- for (const symbol of changed.slice(0, LIMITS.changedSymbols)) {
73887
- if (overBudget()) {
73888
- partial = true;
73889
- break;
73890
- }
73891
- const label = `${symbol.kind} \`${symbol.container ? `${symbol.container}.` : ""}${symbol.name}\``;
73892
- if (await index.mentions(symbol.name) > LIMITS.commonInFiles) {
73893
- common.push(label);
73894
- continue;
73895
- }
73896
- const access = symbol.kind === "method" || symbol.kind === "field" ? "member" : "bare";
73897
- const uses = (await index.references(symbol.name, { limit: 500, access })).items.filter((r) => !(r.file === symbol.file && r.line >= symbol.startLine && r.line <= symbol.endLine));
73898
- if (uses.length === 0) {
73899
- unused.push(label);
73900
- continue;
73901
- }
73902
- const shown = uses.slice(0, LIMITS.usesShown);
73903
- changedSections.push([
73904
- `### ${label} (${symbol.file}:${symbol.startLine}-${symbol.endLine})`,
73905
- `Used ${times(uses.length)} outside its own body:`,
73906
- ...shown.map(useLine),
73907
- ...uses.length > shown.length ? [` - and ${uses.length - shown.length} more`] : []
73908
- ].join("\n"));
73909
- }
73910
- const head = [
73911
- "# Change impact",
73912
- "",
73913
- "Computed from the syntax tree of the checkout at the head of this change, before the review began.",
73914
- "A use is an identifier with the same name: two declarations that share a name are not told apart, so read a use before relying on it."
73915
- ];
73916
- const body2 = [
73917
- "## Removed, but still used",
73918
- "",
73919
- removedSections.length ? removedSections.join("\n") : "Nothing the change removed is still used.",
73920
- "",
73921
- "## Changed declarations and where they are used",
73922
- "",
73923
- ...changedSections.length ? [changedSections.join("\n\n"), ""] : [],
73924
- ...unused.length ? [`Changed, with no uses elsewhere: ${unused.join(", ")}.`, ""] : [],
73925
- ...common.length ? [`Changed, with names too common to list uses for (read their callers directly): ${common.join(", ")}.`, ""] : [],
73926
- ...changedSections.length + unused.length + common.length === 0 ? ["The change touches no declaration this could read.", ""] : []
73927
- ].join("\n");
73928
- let text = body2;
73929
- if (Buffer.byteLength(text) > LIMITS.reportBytes) {
73930
- text = `${Buffer.from(text).subarray(0, LIMITS.reportBytes).toString("utf-8").replace(/\n[^\n]*$/, "")}
73931
- `;
73932
- partial = true;
73933
- }
73934
- return [
73935
- ...head,
73936
- ...partial ? ["", "This report is partial: the change touches more than it could cover."] : [],
73937
- "",
73938
- text
73939
- ].join("\n");
73940
- }
73941
- function times(n) {
73942
- return `${n} time${n === 1 ? "" : "s"}`;
73943
- }
73944
- }
73945
- });
73946
-
73947
73857
  // ../../packages/shuttle/dist/workspace/materialize.js
73948
73858
  var require_materialize = __commonJS({
73949
73859
  "../../packages/shuttle/dist/workspace/materialize.js"(exports2) {
@@ -73959,7 +73869,6 @@ var require_materialize = __commonJS({
73959
73869
  var os_1 = __importDefault(require("os"));
73960
73870
  var path_1 = __importDefault(require("path"));
73961
73871
  var git_1 = require_git();
73962
- var impact_1 = require_impact();
73963
73872
  var overlay_1 = require_overlay();
73964
73873
  var materializers = /* @__PURE__ */ new Map();
73965
73874
  var modifiers = /* @__PURE__ */ new Map();
@@ -73971,7 +73880,6 @@ var require_materialize = __commonJS({
73971
73880
  }
73972
73881
  registerMaterializer(git_1.gitMaterializer);
73973
73882
  registerModifier(overlay_1.overlayModifier);
73974
- registerModifier(impact_1.impactModifier);
73975
73883
  async function materializeWorkspace(bag, options = {}) {
73976
73884
  const tmpBase = options.tmpDir ?? os_1.default.tmpdir();
73977
73885
  const runRoot = await fs_1.promises.mkdtemp(path_1.default.join(tmpBase, "shuttle-ws-"));
@@ -74024,7 +73932,7 @@ var require_workspace = __commonJS({
74024
73932
  "../../packages/shuttle/dist/workspace/index.js"(exports2) {
74025
73933
  "use strict";
74026
73934
  Object.defineProperty(exports2, "__esModule", { value: true });
74027
- exports2.applyImpact = exports2.impactModifier = exports2.applyOverlay = exports2.overlayModifier = exports2.buildGitAuthEnv = exports2.gitMaterializer = exports2.registerModifier = exports2.registerMaterializer = exports2.materializeWorkspace = void 0;
73935
+ exports2.applyOverlay = exports2.overlayModifier = exports2.buildGitAuthEnv = exports2.gitMaterializer = exports2.registerModifier = exports2.registerMaterializer = exports2.materializeWorkspace = void 0;
74028
73936
  var materialize_1 = require_materialize();
74029
73937
  Object.defineProperty(exports2, "materializeWorkspace", { enumerable: true, get: function() {
74030
73938
  return materialize_1.materializeWorkspace;
@@ -74049,13 +73957,6 @@ var require_workspace = __commonJS({
74049
73957
  Object.defineProperty(exports2, "applyOverlay", { enumerable: true, get: function() {
74050
73958
  return overlay_1.applyOverlay;
74051
73959
  } });
74052
- var impact_1 = require_impact();
74053
- Object.defineProperty(exports2, "impactModifier", { enumerable: true, get: function() {
74054
- return impact_1.impactModifier;
74055
- } });
74056
- Object.defineProperty(exports2, "applyImpact", { enumerable: true, get: function() {
74057
- return impact_1.applyImpact;
74058
- } });
74059
73960
  }
74060
73961
  });
74061
73962
 
@@ -76487,7 +76388,7 @@ var import_shuttle2 = __toESM(require_dist8());
76487
76388
 
76488
76389
  // src/brand.ts
76489
76390
  var import_shuttle = __toESM(require_dist8());
76490
- var buildVersion = true ? "0.43.1" : pkg.version;
76391
+ var buildVersion = true ? "0.43.3" : pkg.version;
76491
76392
  var AGENT_EXECUTORS = ["claude-code", "codex", "copilot"];
76492
76393
  var sifterBrand = {
76493
76394
  product: {