@gjermundgaraba/upup 0.5.1 → 0.5.2

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/main.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { n as openFlags, r as reason, t as byName } from "./io-ns6xhFdF.mjs";
2
+ import { i as reason, n as decodeUtf8, r as openFlags, t as byName } from "./io-Dgi-IiVu.mjs";
3
3
  import { findPackageJSON } from "node:module";
4
4
  import { NodeRuntime, NodeServices, NodeStdio } from "@effect/platform-node";
5
5
  import { Cause, Clock, Config, Context, Data, Deferred, Duration, Effect, Equal, Exit, Fiber, FiberHandle, FiberSet, FileSystem, Layer, Option, Predicate, Queue, Ref, Result, Schedule, Schema, Semaphore, Stream, SubscriptionRef, identity } from "effect";
@@ -19,6 +19,7 @@ import lockfile from "proper-lockfile";
19
19
  import { BACKGROUND_CONTEXT, withAbortSignal } from "@earendil-works/chord/context";
20
20
  import { GenerationTask, Harness, LiveDoc, ROOT_CONVERSATION_ID, ToolTask, createRegistry, defineDoc, defineExtension, defineTool, hook, section } from "@earendil-works/pi-durable";
21
21
  import { openNodeSqliteStorage } from "@earendil-works/pi-durable/storage/sqlite/node";
22
+ import { absurd, constVoid } from "effect/Function";
22
23
  import { ChildProcess, ChildProcessSpawner, CommandLine } from "effect/process";
23
24
  import { Worker } from "node:worker_threads";
24
25
  import semver from "semver";
@@ -33,7 +34,6 @@ import { bundleFromJSON, isBundleWithDsseEnvelope } from "@sigstore/bundle";
33
34
  import { getTrustedRoot } from "@sigstore/tuf";
34
35
  import { Verifier, toSignedEntity, toTrustMaterial } from "@sigstore/verify";
35
36
  import { isDeepStrictEqual } from "node:util";
36
- import { constVoid } from "effect/Function";
37
37
  import { applyEdits, modify, parse as parse$1 } from "jsonc-parser";
38
38
  import { getStaticTOMLValue, parseTOML, traverseNodes } from "toml-eslint-parser";
39
39
  import { XMLParser } from "fast-xml-parser";
@@ -41,7 +41,7 @@ import * as ActionMcp from "@gjermundgaraba/effect-actions/ActionMcp";
41
41
  import * as Action from "@gjermundgaraba/effect-actions/Action";
42
42
  import { setup, types } from "xstate/fsm";
43
43
  // package.json
44
- var version$1 = "0.5.1";
44
+ var version$1 = "0.5.2";
45
45
  // src/ai/Setup.ts
46
46
  const Thinking = Schema.Literals([
47
47
  "off",
@@ -427,13 +427,15 @@ const withTarget = (pkg, target) => ({
427
427
  const isUnaudited = Schema.is(UnauditedEcosystem);
428
428
  const isApp$1 = Schema.is(AppEcosystem);
429
429
  const isRuntime = Schema.is(RuntimeEcosystem);
430
+ const isOs = Schema.is(OsEcosystem);
430
431
  const isUnauditedUpdate = (pkg) => isUnaudited(pkg.ecosystem);
431
432
  const isAppUpdate = (pkg) => isApp$1(pkg.ecosystem);
432
- const unauditedKind = (pkg) => {
433
- if (isApp$1(pkg.ecosystem)) return "app";
434
- if (isRuntime(pkg.ecosystem)) return "runtime";
435
- if (isUnaudited(pkg.ecosystem)) return "os";
433
+ const ecosystemKind = (ecosystem) => {
434
+ if (isApp$1(ecosystem)) return "app";
435
+ if (isRuntime(ecosystem)) return "runtime";
436
+ return isOs(ecosystem) ? "os" : absurd(ecosystem);
436
437
  };
438
+ const unauditedKind = (pkg) => isUnaudited(pkg.ecosystem) ? ecosystemKind(pkg.ecosystem) : void 0;
437
439
  // src/inventory/Provider.ts
438
440
  var OutputMalformed = class extends Schema.TaggedError()("OutputMalformed", {
439
441
  command: Schema.String,
@@ -559,7 +561,8 @@ const layer$6 = Layer.effect(Exec, Effect.gen(function* () {
559
561
  const nodeLayer = layer$6.pipe(Layer.provide(NodeServices.layer));
560
562
  // src/brew/Brew.ts
561
563
  const caskPrefix = "cask:";
562
- const isCask = (pkg) => pkg.id.startsWith(caskPrefix);
564
+ const Kind = Schema.Literals(["formula", "cask"]);
565
+ const kindOf$2 = (pkg) => pkg.id.startsWith(caskPrefix) ? "cask" : "formula";
563
566
  const Keg = Schema.Struct({
564
567
  version: Schema.String,
565
568
  installed_on_request: Schema.Boolean,
@@ -603,13 +606,12 @@ const Outdated$1 = Schema.Struct({
603
606
  formulae: Schema.Array(OutdatedEntry),
604
607
  casks: Schema.Array(OutdatedEntry)
605
608
  });
606
- const brew$1 = (args, refreshMetadata = false) => ({
609
+ const brew = (args, refreshMetadata = false) => ({
607
610
  command: "brew",
608
611
  args,
609
612
  cwd: "/",
610
613
  env: refreshMetadata ? {} : { HOMEBREW_NO_AUTO_UPDATE: "1" }
611
614
  });
612
- const kindOf$2 = (pkg) => pkg.id.startsWith("cask:") ? "cask" : "formula";
613
615
  const activeKeg = (formula) => formula.installed.find((keg) => keg.version === formula.linked_keg) ?? formula.installed.at(-1);
614
616
  const describedAs = (desc) => desc === void 0 || desc === null || desc === "" ? {} : { description: desc };
615
617
  const packages = (installed, outdated) => {
@@ -656,19 +658,19 @@ const make$14 = Effect.gen(function* () {
656
658
  const exec = yield* Exec;
657
659
  const json = (schema, invocation) => exec.stdout(invocation).pipe(Effect.flatMap(decodeJson(schema, commandLine(invocation))));
658
660
  const list = Effect.gen(function* () {
659
- const installed = yield* json(Installed, brew$1([
661
+ const installed = yield* json(Installed, brew([
660
662
  "info",
661
663
  "--json=v2",
662
664
  "--installed"
663
665
  ]));
664
- const outdated = yield* json(Outdated$1, brew$1(["outdated", "--json=v2"], true));
666
+ const outdated = yield* json(Outdated$1, brew(["outdated", "--json=v2"], true));
665
667
  return {
666
668
  packages: packages(installed, outdated),
667
669
  notices: []
668
670
  };
669
671
  }).pipe(Effect.withSpan("Brew.list"));
670
672
  const observe = Effect.fn("Brew.observe")(function* (pkg) {
671
- const installed = yield* json(Installed, brew$1([
673
+ const installed = yield* json(Installed, brew([
672
674
  "info",
673
675
  "--json=v2",
674
676
  `--${kindOf$2(pkg)}`,
@@ -681,7 +683,7 @@ const make$14 = Effect.gen(function* () {
681
683
  return Option.fromUndefinedOr(observed.find((candidate) => candidate.id === pkg.id));
682
684
  });
683
685
  const offered = Effect.fn("Brew.offered")(function* (pkg) {
684
- const invocation = brew$1([
686
+ const invocation = brew([
685
687
  "outdated",
686
688
  "--json=v2",
687
689
  `--${kindOf$2(pkg)}`,
@@ -709,14 +711,14 @@ const make$14 = Effect.gen(function* () {
709
711
  expected: target,
710
712
  ...now === void 0 ? {} : { offered: now }
711
713
  });
712
- yield* exec.stdout(brew$1([
714
+ yield* exec.stdout(brew([
713
715
  "upgrade",
714
716
  `--${kindOf$2(pkg)}`,
715
717
  pkg.name
716
718
  ]));
717
719
  }),
718
720
  uninstall: Effect.fn("Brew.uninstall")(function* (pkg) {
719
- yield* exec.stdout(brew$1([
721
+ yield* exec.stdout(brew([
720
722
  "uninstall",
721
723
  `--${kindOf$2(pkg)}`,
722
724
  pkg.name
@@ -761,65 +763,88 @@ const Report = Schema.Struct({
761
763
  findings: Schema.Array(Finding),
762
764
  unverified: Schema.Array(Schema.String).annotate({ description: "Each thing you could not verify." })
763
765
  });
766
+ const Context$1 = Schema.TaggedUnion({
767
+ None: {},
768
+ Brew: { kind: Kind },
769
+ Python: { python: Schema.String },
770
+ Revision: { revision: Schema.String }
771
+ });
772
+ const sameContext = Schema.toEquivalence(Context$1);
764
773
  Schema.Struct({
765
774
  mode: Mode,
766
775
  delta: Delta,
767
- context: Schema.Record(Schema.String, Schema.String)
776
+ context: Context$1
768
777
  });
769
778
  var ChangesUnavailable = class extends Schema.TaggedError()("ChangesUnavailable", { issue: Schema.String }) {};
770
779
  const isRegistry = Schema.is(RegistryEcosystem);
771
- const deltaOf = (pkg) => pkg.target === void 0 || !isRegistry(pkg.ecosystem) ? void 0 : {
772
- ecosystem: pkg.ecosystem,
773
- artifact: pkg.artifact,
774
- from: pkg.current,
775
- to: pkg.target
776
- };
777
780
  const contextOf = (pkg) => {
778
- if (pkg.ecosystem === "brew") return { kind: isCask(pkg) ? "cask" : "formula" };
779
- if (pkg.ecosystem === "pypi" && pkg.runtime !== void 0) return { python: pkg.runtime };
780
- if (pkg.ecosystem === "github-actions" && pkg.revision !== void 0) return { revision: pkg.revision };
781
- return {};
781
+ if (pkg.ecosystem === "brew") return {
782
+ _tag: "Brew",
783
+ kind: kindOf$2(pkg)
784
+ };
785
+ if (pkg.ecosystem === "pypi" && pkg.runtime !== void 0) return {
786
+ _tag: "Python",
787
+ python: pkg.runtime
788
+ };
789
+ if (pkg.ecosystem === "github-actions" && pkg.revision !== void 0) return {
790
+ _tag: "Revision",
791
+ revision: pkg.revision
792
+ };
793
+ return { _tag: "None" };
782
794
  };
783
- const landingOf = (pkg, mode) => pkg.privateRegistry === void 0 && isRegistry(pkg.ecosystem) ? {
795
+ const unauditedReasons = {
796
+ app: "an app update",
797
+ runtime: "a Node.js runtime version",
798
+ os: "an operating-system update"
799
+ };
800
+ const registryOf = (pkg) => {
801
+ if (!isRegistry(pkg.ecosystem)) return Result.fail(`it is ${unauditedReasons[ecosystemKind(pkg.ecosystem)]}, which upup does not audit`);
802
+ return pkg.privateRegistry === void 0 ? Result.succeed(pkg.ecosystem) : Result.fail(`it comes from the private registry ${pkg.privateRegistry}, which upup does not audit`);
803
+ };
804
+ const deltaOf = (pkg) => {
805
+ const { target } = pkg;
806
+ return target === void 0 ? Result.fail("no update is offered") : Result.map(registryOf(pkg), (ecosystem) => ({
807
+ ecosystem,
808
+ artifact: pkg.artifact,
809
+ from: pkg.current,
810
+ to: target
811
+ }));
812
+ };
813
+ const auditOf = (pkg, mode) => Result.map(deltaOf(pkg), (delta) => ({
814
+ mode,
815
+ delta,
816
+ context: contextOf(pkg)
817
+ }));
818
+ const identityOf = (pkg, mode) => Result.getOrUndefined(auditOf(pkg, mode));
819
+ const unauditable = (pkg) => Result.getOrUndefined(Result.flip(deltaOf(pkg)));
820
+ const landingOf = (pkg, mode) => Result.getOrUndefined(Result.map(registryOf(pkg), (ecosystem) => ({
784
821
  mode,
785
- ecosystem: pkg.ecosystem,
822
+ ecosystem,
786
823
  artifact: pkg.artifact,
787
824
  version: pkg.current,
788
825
  context: contextOf(pkg)
789
- } : void 0;
826
+ })));
827
+ const landingAt = (identity) => ({
828
+ mode: identity.mode,
829
+ ecosystem: identity.delta.ecosystem,
830
+ artifact: identity.delta.artifact,
831
+ version: identity.delta.to,
832
+ context: identity.context
833
+ });
790
834
  const sha256 = (value) => createHash("sha256").update(value).digest("hex");
791
835
  const landingKey = (landing) => sha256(JSON.stringify([
792
836
  landing.ecosystem,
793
837
  landing.artifact,
794
838
  landing.version,
795
- Object.entries(landing.context).toSorted(([left], [right]) => left.localeCompare(right))
839
+ landing.context
796
840
  ]));
797
- const unauditedReasons = {
798
- app: "an app update",
799
- runtime: "a Node.js runtime version",
800
- os: "an operating-system update"
801
- };
802
- const unauditable = (pkg) => {
803
- if (pkg.target === void 0) return "no update is offered";
804
- const kind = unauditedKind(pkg);
805
- if (kind !== void 0) return `it is ${unauditedReasons[kind]}, which upup does not audit`;
806
- return pkg.privateRegistry === void 0 ? void 0 : `it comes from the private registry ${pkg.privateRegistry}, which upup does not audit`;
807
- };
808
- const identityOf = (pkg, mode) => {
809
- const delta = deltaOf(pkg);
810
- return delta === void 0 || pkg.privateRegistry !== void 0 ? void 0 : {
811
- mode,
812
- delta,
813
- context: contextOf(pkg)
814
- };
815
- };
816
841
  const deltaKey = (delta) => sha256(JSON.stringify([
817
842
  delta.ecosystem,
818
843
  delta.artifact,
819
844
  delta.from,
820
845
  delta.to
821
846
  ]));
822
- const workspaceKey = (identity) => sha256(JSON.stringify([deltaKey(identity.delta), Object.entries(identity.context).toSorted(([left], [right]) => left.localeCompare(right))]));
847
+ const workspaceKey = (identity) => sha256(JSON.stringify([deltaKey(identity.delta), identity.context]));
823
848
  // src/audit/tools.ts
824
849
  const limits$1 = {
825
850
  resultBytes: 262144,
@@ -832,16 +857,6 @@ const limits$1 = {
832
857
  longLineMatches: 1025
833
858
  };
834
859
  const grepTimeoutMs = 3e4;
835
- const names$1 = {
836
- read: "read",
837
- grep: "grep",
838
- list: "list"
839
- };
840
- const descriptions = {
841
- read: "Read a bounded byte range from one regular UTF-8 file.",
842
- grep: "Find a JavaScript regular expression in UTF-8 regular files below a path. Each line is matched whole. Lines over 1 KiB show a bounded window around each match with its file byte offset; a file stops at a line over 16 MiB.",
843
- list: "List workspace entries below a directory to a bounded depth."
844
- };
845
860
  const count$1 = (maximum) => Schema.Number.pipe(Schema.check(Schema.isInt(), Schema.isBetween({
846
861
  minimum: 1,
847
862
  maximum
@@ -894,7 +909,6 @@ const respond = async (signal, run) => {
894
909
  const checkPath = (name) => {
895
910
  if (!(name === "." || name !== "" && !name.includes("\\") && !name.includes("\0") && name.split("/").every((segment) => segment !== "" && segment !== "." && segment !== ".."))) fail$1(`path ${JSON.stringify(name)} is not workspace-relative`);
896
911
  };
897
- const isCount = (value, min, max) => Number.isSafeInteger(value) && value >= min && value <= max;
898
912
  const kindOf$1 = (info) => {
899
913
  if (info.isFile()) return "file";
900
914
  return info.isDirectory() ? "directory" : "other";
@@ -930,20 +944,9 @@ const trimIncompleteUtf8 = (data) => {
930
944
  return data;
931
945
  }
932
946
  };
933
- const decodeUtf8 = (data) => {
934
- try {
935
- return new TextDecoder("utf-8", {
936
- fatal: true,
937
- ignoreBOM: true
938
- }).decode(data);
939
- } catch {
940
- return;
941
- }
942
- };
943
947
  const read$2 = async (workspace, args, signal) => await respond(signal, async () => {
944
948
  const name = args.path;
945
949
  checkPath(name);
946
- if (!isCount(args.offset, 0, Number.MAX_SAFE_INTEGER) || !isCount(args.limit, 1, limits$1.readBytes)) fail$1("offset or limit is outside the allowed range");
947
950
  const located = await locate$4(workspace, name, `open ${name}`);
948
951
  if (located.kind !== "file") fail$1("path is not a regular file");
949
952
  signal?.throwIfAborted();
@@ -977,7 +980,6 @@ const read$2 = async (workspace, args, signal) => await respond(signal, async ()
977
980
  });
978
981
  const list = async (workspace, args, signal) => await respond(signal, async () => {
979
982
  checkPath(args.path);
980
- if (!isCount(args.depth, 1, limits$1.listDepth)) fail$1("depth is outside the allowed range");
981
983
  const located = await locate$4(workspace, args.path, `list workspace: walk ${args.path}`);
982
984
  if (located.kind !== "directory") return "no entries";
983
985
  let result = "";
@@ -1062,15 +1064,29 @@ const ends = (offset, limit) => {
1062
1064
  const [from, to] = [span(offset), span(offset + limit)];
1063
1065
  return from === to ? `${from} +${span(limit)}` : `${from}–${to}`;
1064
1066
  };
1067
+ const tool = (description, Args, run, detail) => {
1068
+ const decode = Schema.decodeUnknownSync(Args);
1069
+ return {
1070
+ description,
1071
+ Args,
1072
+ call: async (workspace, args, signal) => await run(workspace, decode(args), signal),
1073
+ detail
1074
+ };
1075
+ };
1076
+ const tools$3 = {
1077
+ read: tool("Read a bounded byte range from one regular UTF-8 file.", ReadArgs, read$2, ({ path: shown, range }) => `${shown}${range}`),
1078
+ grep: tool("Find a JavaScript regular expression in UTF-8 regular files below a path. Each line is matched whole. Lines over 1 KiB show a bounded window around each match with its file byte offset; a file stops at a line over 16 MiB.", GrepArgs, async (workspace, args, signal) => await grep(workspace, args, signal === void 0 ? {} : { signal }), ({ path: shown, pattern }) => `${pattern} ${shown}`),
1079
+ list: tool("List workspace entries below a directory to a bounded depth.", ListArgs, list, ({ path: shown }) => shown)
1080
+ };
1081
+ const isTool = (name) => Object.hasOwn(tools$3, name);
1065
1082
  const describeCall = (name, args) => {
1066
- if (!Predicate.isObject(args)) return name;
1083
+ if (!Predicate.isObject(args) || !isTool(name)) return name;
1067
1084
  const { path: target, pattern, offset, limit } = args;
1068
- const shownPath = Predicate.isString(target) ? target : "";
1069
- const range = Predicate.isNumber(offset) && Predicate.isNumber(limit) ? ` bytes ${ends(offset, limit)}` : "";
1070
- let detail = "";
1071
- if (name === names$1.read) detail = `${shownPath}${range}`;
1072
- else if (name === names$1.list) detail = shownPath;
1073
- else if (name === names$1.grep) detail = `${Predicate.isString(pattern) ? pattern : ""} ${shownPath}`;
1085
+ const detail = tools$3[name].detail({
1086
+ path: Predicate.isString(target) ? target : "",
1087
+ pattern: Predicate.isString(pattern) ? pattern : "",
1088
+ range: Predicate.isNumber(offset) && Predicate.isNumber(limit) ? ` bytes ${ends(offset, limit)}` : ""
1089
+ });
1074
1090
  return detail === "" ? name : `${name} ${detail}`;
1075
1091
  };
1076
1092
  // src/audit/hashed-names.ts
@@ -1728,7 +1744,7 @@ const Meta = Schema.Struct({
1728
1744
  ecosystem: Delta.fields.ecosystem,
1729
1745
  from: Schema.String,
1730
1746
  to: Schema.String,
1731
- context: Schema.Record(Schema.String, Schema.String),
1747
+ context: Context$1,
1732
1748
  evidence: Grade,
1733
1749
  evidence_detail: Schema.String,
1734
1750
  preparation_key: Schema.String
@@ -1739,7 +1755,7 @@ const failed$1 = (workspace) => (cause) => new WorkspaceFailed({
1739
1755
  issue: cause instanceof Error ? cause.message : String(cause)
1740
1756
  });
1741
1757
  const listed$2 = async (workspace, name) => await readFile(path.join(workspace, name), "utf-8").catch(() => void 0);
1742
- const write = (workspace, identity, diff, evidence, detail, preparationKey) => Effect.tryPromise({
1758
+ const write = (workspace, identity, { diff, evidence, detail }, preparationKey) => Effect.tryPromise({
1743
1759
  try: async () => {
1744
1760
  await writeFile(path.join(workspace, files.diff), diff, { mode: 384 });
1745
1761
  await writeFile(path.join(workspace, files.index), buildIndex(diff), { mode: 384 });
@@ -1762,7 +1778,6 @@ const readMeta = (workspace) => Effect.tryPromise({
1762
1778
  try: async () => await readFile(path.join(workspace, files.meta), "utf-8"),
1763
1779
  catch: failed$1(workspace)
1764
1780
  }).pipe(Effect.flatMap((text) => Schema.decodeEffect(MetaJson)(text).pipe(Effect.mapError(failed$1(workspace)))));
1765
- const sameContext = (left, right) => JSON.stringify(Object.entries(left).toSorted(([a], [b]) => a.localeCompare(b))) === JSON.stringify(Object.entries(right).toSorted(([a], [b]) => a.localeCompare(b)));
1766
1781
  const describes = (meta, identity) => meta.name === identity.delta.artifact && meta.ecosystem === identity.delta.ecosystem && meta.from === identity.delta.from && meta.to === identity.delta.to && sameContext(meta.context, identity.context);
1767
1782
  const readable = (workspace, identity) => readMeta(workspace).pipe(Effect.flatMap((meta) => describes(meta, identity) ? Effect.promise(async () => await stat(path.join(workspace, files.diff)).then(() => true, () => false)) : Effect.succeed(false)), Effect.orElseSucceed(() => false));
1768
1783
  const prune = (workspace) => Effect.promise(async () => {
@@ -1906,7 +1921,7 @@ const Brief = Schema.Struct({
1906
1921
  artifact: Schema.String,
1907
1922
  from: Schema.String,
1908
1923
  to: Schema.String,
1909
- context: Schema.Record(Schema.String, Schema.String),
1924
+ context: Context$1,
1910
1925
  workspace: Schema.String,
1911
1926
  evidence: Grade,
1912
1927
  evidenceDetail: Schema.String,
@@ -1927,9 +1942,6 @@ const BriefDoc = defineDoc({
1927
1942
  checkpointWhen: () => false
1928
1943
  });
1929
1944
  const parameters = (schema) => Schema.toJsonSchemaDocument(schema).schema;
1930
- const decodeRead = Schema.decodeUnknownSync(ReadArgs);
1931
- const decodeGrep = Schema.decodeUnknownSync(GrepArgs);
1932
- const decodeList = Schema.decodeUnknownSync(ListArgs);
1933
1945
  const decodeSubmitted = Schema.decodeUnknownSync(Report);
1934
1946
  const encodeReport = (submitted) => ({
1935
1947
  ...submitted,
@@ -1959,27 +1971,13 @@ const answer = (result) => {
1959
1971
  const auditExtension = (workspace) => {
1960
1972
  let steps = 0;
1961
1973
  let nudged = false;
1962
- const read = defineTool({
1963
- name: "read",
1964
- description: descriptions.read,
1965
- replay: "safe",
1966
- parameters: parameters(ReadArgs),
1967
- execute: async (args, _api, invocation) => answer(await read$2(workspace, decodeRead(args), invocation.abortSignal))
1968
- });
1969
- const grep$1 = defineTool({
1970
- name: "grep",
1971
- description: descriptions.grep,
1972
- replay: "safe",
1973
- parameters: parameters(GrepArgs),
1974
- execute: async (args, _api, invocation) => answer(await grep(workspace, decodeGrep(args), invocation.abortSignal === void 0 ? {} : { signal: invocation.abortSignal }))
1975
- });
1976
- const list$1 = defineTool({
1977
- name: "list",
1978
- description: descriptions.list,
1974
+ const investigate = Object.entries(tools$3).map(([name, tool]) => defineTool({
1975
+ name,
1976
+ description: tool.description,
1979
1977
  replay: "safe",
1980
- parameters: parameters(ListArgs),
1981
- execute: async (args, _api, invocation) => answer(await list(workspace, decodeList(args), invocation.abortSignal))
1982
- });
1978
+ parameters: parameters(tool.Args),
1979
+ execute: async (args, _api, invocation) => answer(await tool.call(workspace, args, invocation.abortSignal))
1980
+ }));
1983
1981
  const submit = defineTool({
1984
1982
  name: "submit_report",
1985
1983
  description: "Submit the audit report. Call exactly once, when the investigation is done; the audit ends.",
@@ -1999,12 +1997,7 @@ const auditExtension = (workspace) => {
1999
1997
  });
2000
1998
  return defineExtension({
2001
1999
  name: "upup.audit",
2002
- tools: [
2003
- read,
2004
- grep$1,
2005
- list$1,
2006
- submit
2007
- ],
2000
+ tools: [...investigate, submit],
2008
2001
  sections: [section("audit", () => instructions$1)],
2009
2002
  hooks: [hook(GenerationTask, {
2010
2003
  beforeRequest: (request) => {
@@ -2035,17 +2028,15 @@ const policyKey = (prompt, setup) => sha256(JSON.stringify([
2035
2028
  setup.modelId,
2036
2029
  setup.thinking
2037
2030
  ]));
2031
+ const said = (kind, content) => content.trim() === "" ? [] : [{
2032
+ kind,
2033
+ text: content
2034
+ }];
2038
2035
  const entriesOf$1 = (message) => {
2039
2036
  if (message.role === "assistant") {
2040
2037
  const blocks = message.content.flatMap((block) => {
2041
- if (block.type === "thinking") return block.thinking.trim() === "" ? [] : [{
2042
- kind: "thinking",
2043
- text: block.thinking
2044
- }];
2045
- if (block.type === "text") return block.text.trim() === "" ? [] : [{
2046
- kind: "text",
2047
- text: block.text
2048
- }];
2038
+ if (block.type === "thinking") return said("thinking", block.thinking);
2039
+ if (block.type === "text") return said("text", block.text);
2049
2040
  return [{
2050
2041
  kind: "tool",
2051
2042
  text: describeCall(block.name, block.arguments)
@@ -2081,14 +2072,8 @@ const decodeLive = Schema.decodeUnknownOption(Schema.Struct({ generation: Schema
2081
2072
  retry: Schema.optional(Schema.Struct({ error: Schema.String }))
2082
2073
  })) }));
2083
2074
  const streamed = (block) => {
2084
- if (isThinkingBlock(block)) return block.thinking.trim() === "" ? [] : [{
2085
- kind: "thinking",
2086
- text: block.thinking
2087
- }];
2088
- if (isTextBlock(block)) return block.text.trim() === "" ? [] : [{
2089
- kind: "text",
2090
- text: block.text
2091
- }];
2075
+ if (isThinkingBlock(block)) return said("thinking", block.thinking);
2076
+ if (isTextBlock(block)) return said("text", block.text);
2092
2077
  return [];
2093
2078
  };
2094
2079
  var Transcript = class {
@@ -2359,6 +2344,42 @@ const lookup$5 = Effect.fn("go.lookup")(function* (client, wanted) {
2359
2344
  withdrawn: {}
2360
2345
  };
2361
2346
  });
2347
+ // src/registry/npm.ts
2348
+ const Version = Schema.Struct({ deprecated: Schema.optionalKey(Schema.Unknown) });
2349
+ const Packument$1 = Schema.Struct({
2350
+ description: Schema.optionalKey(Schema.String),
2351
+ homepage: Schema.optionalKey(Schema.String),
2352
+ repository: Schema.optionalKey(Schema.Union([Schema.String, Schema.Struct({ url: Schema.optionalKey(Schema.String) })])),
2353
+ "dist-tags": Schema.optionalKey(Schema.Struct({ latest: Schema.optionalKey(Schema.String) })),
2354
+ time: Schema.optionalKey(Schema.Record(Schema.String, Schema.String)),
2355
+ versions: Schema.optionalKey(Schema.Record(Schema.String, Version))
2356
+ });
2357
+ const Downloads = Schema.Struct({ downloads: Schema.Finite });
2358
+ const message$2 = (version) => {
2359
+ const deprecated = version?.deprecated;
2360
+ return Predicate.isString(deprecated) && deprecated !== "" ? Option.some(deprecated) : Option.none();
2361
+ };
2362
+ const npmRegistry = "https://registry.npmjs.org";
2363
+ const npmPath = (segment) => encodeURIComponent(segment).replaceAll(/%(?:24|26|2B|3A|3D|40)/gu, (escaped) => decodeURIComponent(escaped));
2364
+ const lookup$4 = Effect.fn("npm.lookup")(function* (client, wanted) {
2365
+ const [packument, downloads] = yield* Effect.all([getJson(client, `${npmRegistry}/${npmPath(wanted.name)}`, Packument$1), getJson(client, `https://api.npmjs.org/downloads/point/last-week/${wanted.name}`, Downloads).pipe(Effect.option)], { concurrency: "unbounded" });
2366
+ const versions = packument.versions ?? {};
2367
+ const latest = packument["dist-tags"]?.latest;
2368
+ const repository = Predicate.isString(packument.repository) ? packument.repository : packument.repository?.url;
2369
+ const deprecated = latest === void 0 ? Option.none() : message$2(versions[latest]);
2370
+ return {
2371
+ ...packument.description === void 0 ? {} : { description: packument.description },
2372
+ ...packument.homepage === void 0 ? {} : { homepage: packument.homepage },
2373
+ ...repository === void 0 ? {} : { repository: repositoryUrl(repository) },
2374
+ ...Option.isSome(downloads) ? { popularity: `${count(downloads.value.downloads)} downloads last week` } : {},
2375
+ ...Option.isSome(deprecated) ? { deprecated: deprecated.value } : {},
2376
+ released: pick$1(packument.time ?? {}, wanted.versions),
2377
+ withdrawn: Object.fromEntries(wanted.versions.flatMap((version) => Option.match(message$2(versions[version]), {
2378
+ onNone: () => [],
2379
+ onSome: (reason) => [[version, reason]]
2380
+ })))
2381
+ };
2382
+ });
2362
2383
  // src/audit/binary.ts
2363
2384
  const startsWith$1 = (prefix, magic) => magic.every((byte, index) => prefix[index] === byte);
2364
2385
  const binaryKind = (prefix) => {
@@ -4273,11 +4294,11 @@ var EvidenceUnavailable = class extends Schema.TaggedError()("EvidenceUnavailabl
4273
4294
  reason: Schema.optionalKey(Schema.Literals(["gone", "mismatch"]))
4274
4295
  }) {};
4275
4296
  const maxDownloadBytes = 8 * 1024 ** 3;
4276
- const message$2 = (cause) => cause instanceof Error ? cause.message : String(cause);
4297
+ const message$1 = (cause) => cause instanceof Error ? cause.message : String(cause);
4277
4298
  const download = Effect.fn("evidence.download")(function* (client, url, limit = maxDownloadBytes, stall) {
4278
4299
  const unavailable = (cause) => new EvidenceUnavailable({
4279
4300
  source: url,
4280
- issue: message$2(cause)
4301
+ issue: message$1(cause)
4281
4302
  });
4282
4303
  const stalled = (after) => unavailable(`sent nothing for ${Duration.format(after)}, so upup gave up`);
4283
4304
  const directory = yield* Effect.acquireRelease(Effect.tryPromise({
@@ -4323,7 +4344,7 @@ const download = Effect.fn("evidence.download")(function* (client, url, limit =
4323
4344
  sha256: hash.digest("hex")
4324
4345
  };
4325
4346
  });
4326
- const canonicalSha256$1 = (raw) => {
4347
+ const canonicalSha256 = (raw) => {
4327
4348
  const value = raw.trim().toLowerCase();
4328
4349
  return /^[0-9a-f]{64}$/u.test(value) ? value : void 0;
4329
4350
  };
@@ -4345,7 +4366,7 @@ const crate = Effect.fn("evidence.crate")(function* (client, name, version) {
4345
4366
  source,
4346
4367
  issue: `no crates index entry for ${name} ${version}`
4347
4368
  });
4348
- const sum = canonicalSha256$1(entry.cksum);
4369
+ const sum = canonicalSha256(entry.cksum);
4349
4370
  if (sum === void 0) return yield* new EvidenceUnavailable({
4350
4371
  source,
4351
4372
  issue: `crates index publishes an invalid checksum for ${name} ${version}`
@@ -4399,8 +4420,6 @@ const NpmVersion = Schema.Struct({
4399
4420
  shasum: Schema.optionalKey(Schema.String)
4400
4421
  }))
4401
4422
  });
4402
- const npmRegistry = "https://registry.npmjs.org";
4403
- const npmPath = (segment) => encodeURIComponent(segment).replaceAll(/%(?:24|26|2B|3A|3D|40)/gu, (escaped) => decodeURIComponent(escaped));
4404
4423
  const strictBase64 = (value) => {
4405
4424
  const decoded = Buffer.from(value, "base64");
4406
4425
  return decoded.toString("base64") === value ? decoded : void 0;
@@ -4451,7 +4470,7 @@ const npmPlan = Effect.fn("evidence.npmPlan")(function* (name, version, metadata
4451
4470
  try: () => npmDigest(dist.integrity ?? "", dist.shasum ?? ""),
4452
4471
  catch: (error) => new EvidenceUnavailable({
4453
4472
  source,
4454
- issue: `npm ${name}@${version}: ${message$2(error)}`
4473
+ issue: `npm ${name}@${version}: ${message$1(error)}`
4455
4474
  })
4456
4475
  })
4457
4476
  };
@@ -4514,7 +4533,7 @@ const fetchVerified = Effect.fn("evidence.fetchVerified")(function* (client, url
4514
4533
  try: async () => await computedDigest(digest, downloaded),
4515
4534
  catch: (error) => new EvidenceUnavailable({
4516
4535
  source: url,
4517
- issue: message$2(error)
4536
+ issue: message$1(error)
4518
4537
  })
4519
4538
  });
4520
4539
  if (computed === void 0) return yield* new EvidenceUnavailable({
@@ -4567,6 +4586,10 @@ const preparationPlan = (subject, evidence) => {
4567
4586
  plan: `${JSON.stringify(plan, null, 2)}\n`
4568
4587
  };
4569
4588
  };
4589
+ const prepareWorkspace = Effect.fn("evidence.prepareWorkspace")(function* (workspace, subject, preparation) {
4590
+ const prepared = yield* preparation.materialize(workspace);
4591
+ return yield* write(workspace, subject, prepared, preparation.key);
4592
+ });
4570
4593
  // src/registry/github.ts
4571
4594
  const githubApi = "https://api.github.com";
4572
4595
  const githubRepository = (name) => `https://github.com/${name}.git`;
@@ -4629,18 +4652,11 @@ const sourceOf$1 = (entry) => ({
4629
4652
  tapGitHead: entry.tap_git_head ?? "",
4630
4653
  rubySourceChecksum: entry.ruby_source_checksum?.sha256 ?? ""
4631
4654
  });
4632
- const brew = (args) => ({
4633
- command: "brew",
4634
- args,
4635
- cwd: "/",
4636
- env: { HOMEBREW_NO_AUTO_UPDATE: "1" }
4637
- });
4638
4655
  const unavailable$2 = (source, issue) => new EvidenceUnavailable({
4639
4656
  source,
4640
4657
  issue
4641
4658
  });
4642
4659
  const query = Effect.fn("brew.query")(function* (kind, name) {
4643
- if (kind !== "formula" && kind !== "cask") return yield* unavailable$2("brew", `unsupported brew package variant ${JSON.stringify(kind)}`);
4644
4660
  const raw = yield* (yield* Exec).stdout(brew([
4645
4661
  "info",
4646
4662
  "--json=v2",
@@ -4790,9 +4806,7 @@ const nextPage = (current, link) => {
4790
4806
  const githubGet = (client, url, token) => Effect.gen(function* () {
4791
4807
  const headers = {
4792
4808
  "user-agent": userAgent,
4793
- accept: "application/vnd.github+json",
4794
- "x-github-api-version": "2022-11-28",
4795
- ...token !== void 0 && url.startsWith(`https://api.github.com/`) ? { authorization: `Bearer ${token}` } : {}
4809
+ ...githubHeaders(url.startsWith(`https://api.github.com/`) ? token : void 0)
4796
4810
  };
4797
4811
  const response = yield* get(client, url, headers);
4798
4812
  const fail = (issue) => Effect.fail(new EvidenceUnavailable({
@@ -6098,17 +6112,17 @@ const provenanceSource = (statement, integrity) => {
6098
6112
  commit
6099
6113
  };
6100
6114
  };
6101
- const message$1 = (cause) => cause instanceof Error ? cause.message : String(cause);
6115
+ const message = (cause) => cause instanceof Error ? cause.message : String(cause);
6102
6116
  const sigstoreVerifier = Effect.gen(function* () {
6103
- const cachePath = path.join(yield* cacheDir.pipe(Effect.mapError((error) => `no cache directory: ${message$1(error)}`)), "sigstore");
6117
+ const cachePath = path.join(yield* cacheDir.pipe(Effect.mapError((error) => `no cache directory: ${message(error)}`)), "sigstore");
6104
6118
  const root = yield* Effect.tryPromise({
6105
6119
  try: async () => await getTrustedRoot({ cachePath }),
6106
- catch: (error) => `the Sigstore trust root could not be loaded: ${message$1(error)}`
6120
+ catch: (error) => `the Sigstore trust root could not be loaded: ${message(error)}`
6107
6121
  });
6108
6122
  const verifier = new Verifier(toTrustMaterial(root));
6109
6123
  return (bundle) => Effect.try({
6110
6124
  try: () => verifier.verify(toSignedEntity(bundle), {}),
6111
- catch: (error) => `its Sigstore verification failed: ${message$1(error)}`
6125
+ catch: (error) => `its Sigstore verification failed: ${message(error)}`
6112
6126
  });
6113
6127
  });
6114
6128
  const sourceRepositoryUri = "1.3.6.1.4.1.57264.1.12";
@@ -6343,7 +6357,7 @@ const sourceEvidence = Effect.fn("brew.sourceEvidence")(function* (client, works
6343
6357
  detail: ["Checksum-verified publisher source archive Delta is included.", partial("source archive", source.omitted)].filter((sentence) => sentence !== "").join(" ")
6344
6358
  };
6345
6359
  });
6346
- const materialize$1 = Effect.fn("brew.materialize")(function* (client, workspace, identity, plan, key) {
6360
+ const materialize$1 = Effect.fn("brew.materialize")(function* (client, workspace, identity, plan) {
6347
6361
  const [formula, source, payload] = yield* Effect.all([
6348
6362
  materializeFormula(workspace, plan.formula),
6349
6363
  sourceEvidence(client, workspace, archives(plan.formula), plan.sourceGap),
@@ -6359,17 +6373,26 @@ const materialize$1 = Effect.fn("brew.materialize")(function* (client, workspace
6359
6373
  goSource?.detail ?? "",
6360
6374
  repository?.detail ?? ""
6361
6375
  ].filter((sentence) => sentence !== "").join(" ");
6362
- return yield* write(workspace, identity, Buffer.concat([
6363
- formula,
6364
- source.diff,
6365
- payload.diff,
6366
- goSource?.diff ?? Buffer.alloc(0),
6367
- repository?.diff ?? Buffer.alloc(0)
6368
- ]), evidence, plan.formula.fromGap === "" ? detail : `The installed version's formula snapshot is unavailable: ${plan.formula.fromGap}. ${detail}`, key);
6376
+ return {
6377
+ diff: Buffer.concat([
6378
+ formula,
6379
+ source.diff,
6380
+ payload.diff,
6381
+ goSource?.diff ?? Buffer.alloc(0),
6382
+ repository?.diff ?? Buffer.alloc(0)
6383
+ ]),
6384
+ evidence,
6385
+ detail: plan.formula.fromGap === "" ? detail : `The installed version's formula snapshot is unavailable: ${plan.formula.fromGap}. ${detail}`
6386
+ };
6369
6387
  });
6370
6388
  const resolve$2 = Effect.fn("brew.resolve")(function* (client, identity) {
6371
6389
  const { artifact, from, to } = identity.delta;
6372
- const kind = identity.context["kind"] ?? "";
6390
+ const { context } = identity;
6391
+ if (context._tag !== "Brew") return yield* new EvidenceUnavailable({
6392
+ source: "brew",
6393
+ issue: `a brew package's audit needs its kind, not ${context._tag}`
6394
+ });
6395
+ const { kind } = context;
6373
6396
  const { source, formula: info } = yield* query(kind, artifact);
6374
6397
  const formula = yield* resolveFormula(client, source, from, to);
6375
6398
  const formulaGap = formula.fromGap === "" ? archiveUnavailableReason(archives(formula)) : "the installed version's formula snapshot is unavailable";
@@ -6388,25 +6411,21 @@ const resolve$2 = Effect.fn("brew.resolve")(function* (client, identity) {
6388
6411
  ...repository === void 0 ? {} : { repository }
6389
6412
  };
6390
6413
  });
6391
- const prepareBrewWith = (exec) => (client, identity) => resolve$2(client, identity).pipe(Effect.provide(exec), Effect.map((plan) => {
6392
- const { key, plan: recorded } = preparationPlan(identity, {
6414
+ const prepareBrewWith = (exec) => (client, identity) => resolve$2(client, identity).pipe(Effect.provide(exec), Effect.map((plan) => ({
6415
+ ...preparationPlan(identity, {
6393
6416
  kind: "brew",
6394
6417
  plan: planKey(plan)
6395
- });
6396
- return {
6397
- key,
6398
- plan: recorded,
6399
- artifacts: artifactsOf(plan),
6400
- materialize: (workspace) => materialize$1(client, workspace, identity, plan, key).pipe(Effect.provide(exec))
6401
- };
6402
- }));
6418
+ }),
6419
+ artifacts: artifactsOf(plan),
6420
+ materialize: (workspace) => materialize$1(client, workspace, identity, plan).pipe(Effect.provide(exec))
6421
+ })));
6403
6422
  const prepareBrew = prepareBrewWith(nodeLayer);
6404
6423
  // src/audit/github-actions.ts
6405
6424
  const unavailable$1 = (error) => new EvidenceUnavailable({
6406
6425
  source: error.repository,
6407
6426
  issue: error.issue
6408
6427
  });
6409
- const workspaceOf$1 = Effect.fn("githubActions.workspace")(function* (workspace, identity, repository, commits, key, options) {
6428
+ const workspaceOf = Effect.fn("githubActions.workspace")(function* (workspace, identity, repository, commits, options) {
6410
6429
  const trees = {
6411
6430
  from: path.join(workspace, files.actionFrom),
6412
6431
  to: path.join(workspace, files.actionTo)
@@ -6431,17 +6450,25 @@ const workspaceOf$1 = Effect.fn("githubActions.workspace")(function* (workspace,
6431
6450
  const { from, to } = identity.delta;
6432
6451
  const commitsSentence = `In ${repository}, ${from} is ${commits.from} and ${to} is ${commits.to}.`;
6433
6452
  const modes = yield* writeFileDiff(workspace, files.actionModesFrom, files.actionModesTo, json(Object.fromEntries(changed.modes.map((change) => [change.path, change.from]))), json(Object.fromEntries(changed.modes.map((change) => [change.path, change.to]))));
6434
- if (changed.omitted !== "") return yield* write(workspace, identity, modes, "metadata-only", `${commitsSentence} The changed files were not fetched: ${changed.omitted}.`, key);
6453
+ if (changed.omitted !== "") return {
6454
+ diff: modes,
6455
+ evidence: "metadata-only",
6456
+ detail: `${commitsSentence} The changed files were not fetched: ${changed.omitted}.`
6457
+ };
6435
6458
  const { diff, omitted } = yield* treeDiff(workspace, files.actionFrom, files.actionTo);
6436
6459
  const submodules = changed.modes.flatMap((change) => change.submodule ? [change.path] : []);
6437
6460
  const gaps = [partial("action", omitted), submodules.length === 0 ? "" : `Submodules changed, and their contents were not fetched: ${submodules.join(", ")}.`].filter((sentence) => sentence !== "");
6438
- return yield* write(workspace, identity, Buffer.concat([diff, modes]), gaps.length === 0 ? "full" : "partial", [commitsSentence, ...gaps].join(" "), key);
6461
+ return {
6462
+ diff: Buffer.concat([diff, modes]),
6463
+ evidence: gaps.length === 0 ? "full" : "partial",
6464
+ detail: [commitsSentence, ...gaps].join(" ")
6465
+ };
6439
6466
  });
6440
6467
  const githubActionPreparer = (repositoryOf, options = {}) => Effect.fn("githubActions.prepare")(function* (_client, identity) {
6441
6468
  const { artifact, from, to } = identity.delta;
6442
6469
  const repository = repositoryOf(artifact);
6443
6470
  const [fromCommit, toCommit] = yield* Effect.all([tagCommit(repository, from, options), tagCommit(repository, to, options)], { concurrency: "unbounded" }).pipe(Effect.mapError(unavailable$1));
6444
- const listed = identity.context["revision"];
6471
+ const listed = identity.context._tag === "Revision" ? identity.context.revision : void 0;
6445
6472
  if (listed !== toCommit) return yield* new EvidenceUnavailable({
6446
6473
  source: repository,
6447
6474
  issue: listed === void 0 ? `upup listed no commit for ${to}; refresh` : `${to} now points at ${toCommit}, not the ${listed} upup listed; refresh`,
@@ -6451,16 +6478,14 @@ const githubActionPreparer = (repositoryOf, options = {}) => Effect.fn("githubAc
6451
6478
  from: fromCommit,
6452
6479
  to: toCommit
6453
6480
  };
6454
- const { key, plan } = preparationPlan(identity, {
6455
- kind: "github-actions",
6456
- repository,
6457
- commits
6458
- });
6459
6481
  return {
6460
- key,
6461
- plan,
6482
+ ...preparationPlan(identity, {
6483
+ kind: "github-actions",
6484
+ repository,
6485
+ commits
6486
+ }),
6462
6487
  artifacts: [`from ${fromCommit}`, `to ${toCommit}`],
6463
- materialize: (workspace) => workspaceOf$1(workspace, identity, repository, commits, key, options)
6488
+ materialize: (workspace) => workspaceOf(workspace, identity, repository, commits, options)
6464
6489
  };
6465
6490
  });
6466
6491
  const prepareGitHubAction = githubActionPreparer(githubRepository);
@@ -6474,10 +6499,6 @@ const Release$2 = Schema.Struct({ urls: optional(Schema.Array(Schema.Struct({
6474
6499
  digests: optional(Schema.Struct({ sha256: optional(Schema.String) }))
6475
6500
  }))) });
6476
6501
  const releaseUrl = (name, version) => `https://pypi.org/pypi/${encodeURIComponent(name)}/${encodeURIComponent(version)}/json`;
6477
- const canonicalSha256 = (raw) => {
6478
- const value = raw.trim().toLowerCase();
6479
- return /^[0-9a-f]{64}$/u.test(value) ? value : void 0;
6480
- };
6481
6502
  const formatOf = (url) => {
6482
6503
  try {
6483
6504
  return formatOf$1(url);
@@ -6626,7 +6647,7 @@ const trees = [
6626
6647
  files.wheelFrom,
6627
6648
  files.wheelTo
6628
6649
  ];
6629
- const pypiWorkspace = Effect.fn("pypi.workspace")(function* (client, workspace, identity, plans, key) {
6650
+ const pypiWorkspace = Effect.fn("pypi.workspace")(function* (client, workspace, plans) {
6630
6651
  yield* Effect.tryPromise({
6631
6652
  try: async () => await Promise.all(trees.map(async (tree) => await mkdir(path.join(workspace, tree), {
6632
6653
  recursive: true,
@@ -6646,38 +6667,40 @@ const pypiWorkspace = Effect.fn("pypi.workspace")(function* (client, workspace,
6646
6667
  writeFileDiff(workspace, files.binaryWheelFrom, files.binaryWheelTo, json(binarySummaries(from.compatible_universal_wheel?.archive_manifest)), json(binarySummaries(to.compatible_universal_wheel?.archive_manifest)))
6647
6668
  ], { concurrency: "unbounded" });
6648
6669
  const detail = `Checksum-verified PyPI publisher artifacts; ${pairedDetail("source distributions", from.publisher_sdist, to.publisher_sdist)}; ${pairedDetail("compatible universal wheel candidates", from.compatible_universal_wheel, to.compatible_universal_wheel)}; the installer’s exact resolver selection is not known.`;
6649
- return yield* write(workspace, identity, Buffer.concat([
6650
- sources.diff,
6651
- records,
6652
- sourceBinaries,
6653
- wheels.diff,
6654
- wheelBinaries
6655
- ]), "partial", [
6656
- detail,
6657
- partial("source distribution", sources.omitted),
6658
- partial("wheel", wheels.omitted)
6659
- ].filter((sentence) => sentence !== "").join(" "), key);
6670
+ return {
6671
+ diff: Buffer.concat([
6672
+ sources.diff,
6673
+ records,
6674
+ sourceBinaries,
6675
+ wheels.diff,
6676
+ wheelBinaries
6677
+ ]),
6678
+ evidence: "partial",
6679
+ detail: [
6680
+ detail,
6681
+ partial("source distribution", sources.omitted),
6682
+ partial("wheel", wheels.omitted)
6683
+ ].filter((sentence) => sentence !== "").join(" ")
6684
+ };
6660
6685
  });
6661
6686
  const preparePyPI = Effect.fn("pypi.prepare")(function* (client, identity) {
6662
6687
  const { artifact, from, to } = identity.delta;
6663
- const python = identity.context["python"] ?? "";
6688
+ const python = identity.context._tag === "Python" ? identity.context.python : "";
6664
6689
  const [fromPlan, toPlan] = yield* Effect.all([resolve$1(client, artifact, from, python), resolve$1(client, artifact, to, python)], { concurrency: "unbounded" });
6665
6690
  const plans = {
6666
6691
  from: fromPlan,
6667
6692
  to: toPlan
6668
6693
  };
6669
- const { key, plan } = preparationPlan(identity, {
6670
- kind: "pypi",
6671
- ...plans
6672
- });
6673
6694
  return {
6674
- key,
6675
- plan,
6695
+ ...preparationPlan(identity, {
6696
+ kind: "pypi",
6697
+ ...plans
6698
+ }),
6676
6699
  artifacts: ["from", "to"].flatMap((side) => [plans[side].publisher_sdist, plans[side].compatible_universal_wheel].map((selected) => `${side} ${selected === null ? "none" : `sha256:${selected.sha256}`}`)),
6677
- materialize: (workspace) => pypiWorkspace(client, workspace, identity, plans, key)
6700
+ materialize: (workspace) => pypiWorkspace(client, workspace, plans)
6678
6701
  };
6679
6702
  });
6680
- const Packument$1 = Schema.Struct({
6703
+ const Packument = Schema.Struct({
6681
6704
  "dist-tags": Schema.optionalKey(Schema.NullOr(Schema.Record(Schema.String, Schema.String))),
6682
6705
  versions: Schema.Record(Schema.String, Schema.Unknown)
6683
6706
  });
@@ -6709,7 +6732,7 @@ const resolveSide = (client, host, name, declared, options = {}) => Effect.gen(f
6709
6732
  issue: `declares ${JSON.stringify(declared.spec)}, which is not a registry version range`
6710
6733
  });
6711
6734
  const source = `${npmRegistry}/${npmPath(name)}`;
6712
- const packument = yield* getJson(client, source, Packument$1, { accept: "application/vnd.npm.install-v1+json" }).pipe(Effect.mapError((error) => new EvidenceUnavailable({
6735
+ const packument = yield* getJson(client, source, Packument, { accept: "application/vnd.npm.install-v1+json" }).pipe(Effect.mapError((error) => new EvidenceUnavailable({
6713
6736
  source,
6714
6737
  issue: error.issue
6715
6738
  })));
@@ -6902,87 +6925,102 @@ const materializeDependencies = Effect.fn("npmDependencies.materialize")(functio
6902
6925
  });
6903
6926
  // src/audit/registry-evidence.ts
6904
6927
  const registryDetail = "Both published packages were downloaded and their checksums verified. File types and modes, and any native binaries, are summarised without running them.";
6905
- const registryWorkspace = Effect.fn("evidence.registryWorkspace")(function* (client, workspace, identity, plans, npm, key) {
6928
+ const sentences = (parts) => parts.filter((sentence) => sentence !== "").join(" ");
6929
+ const artifactEvidence = Effect.fn("evidence.artifactEvidence")(function* (client, workspace, plans) {
6906
6930
  const [from, to] = yield* Effect.all([materialize$2(client, plans.from, path.join(workspace, files.from)), materialize$2(client, plans.to, path.join(workspace, files.to))], { concurrency: "unbounded" });
6907
6931
  const [artifacts, trees, binaries] = yield* Effect.all([
6908
6932
  writeFileDiff(workspace, files.artifactFrom, files.artifactTo, manifestJson(from), manifestJson(to)),
6909
6933
  treeDiff(workspace, files.from, files.to),
6910
6934
  writeFileDiff(workspace, files.binaryFrom, files.binaryTo, json(binarySummaries(from)), json(binarySummaries(to)))
6911
6935
  ], { concurrency: "unbounded" });
6912
- const treeDetail = [registryDetail, partial("artifact", trees.omitted)].filter((sentence) => sentence !== "").join(" ");
6913
- if (npm === void 0) return yield* write(workspace, identity, Buffer.concat([
6914
- artifacts,
6915
- trees.diff,
6916
- binaries
6917
- ]), trees.omitted === "" ? "full" : "partial", treeDetail, key);
6918
- const changed = yield* materializeDependencies(client, workspace, npm.dependencies);
6919
- const linked = yield* Effect.forEach(changed.binaries, (binary) => releaseAssetSources(client, binary.repository, [binary.from, binary.to]).pipe(Effect.map(([fromRelease, toRelease]) => ({
6920
- label: binary.label,
6921
- from: fromRelease ?? { gap: "no release lookup" },
6922
- to: toRelease ?? { gap: "no release lookup" }
6923
- }))), { concurrency: 2 });
6924
- const source = yield* materializeSources(workspace, [npm.source, ...linked]);
6925
- return yield* write(workspace, identity, Buffer.concat([
6926
- artifacts,
6927
- trees.diff,
6928
- binaries,
6929
- changed.diff,
6930
- source.diff
6931
- ]), "partial", [
6932
- treeDetail,
6933
- changed.detail,
6934
- source.detail
6935
- ].filter((sentence) => sentence !== "").join(" "), key);
6936
- });
6937
- const npmExtras = Effect.fn("evidence.npmExtras")(function* (client, name, from, to, manifests) {
6938
- const fromManifest = manifests.from;
6939
- const toManifest = manifests.to;
6940
- const dependencies = yield* resolveDependencies(client, manifests, {
6941
- os: process.platform,
6942
- cpu: process.arch
6943
- });
6944
- const verifier = yield* Effect.cached(sigstoreVerifier);
6945
- const [fromSource, toSource] = yield* Effect.all([npmSource(client, name, from, fromManifest, verifier), npmSource(client, name, to, toManifest, verifier)], { concurrency: "unbounded" });
6946
- const { directory } = npmRepository(toManifest);
6947
6936
  return {
6948
- dependencies,
6949
- source: {
6950
- label: name,
6951
- ...directory === void 0 ? {} : { directory },
6952
- from: fromSource,
6953
- to: toSource
6954
- }
6937
+ diff: Buffer.concat([
6938
+ artifacts,
6939
+ trees.diff,
6940
+ binaries
6941
+ ]),
6942
+ evidence: trees.omitted === "" ? "full" : "partial",
6943
+ detail: sentences([registryDetail, partial("artifact", trees.omitted)])
6955
6944
  };
6956
6945
  });
6946
+ const digests = (plans) => [`from ${plans.from.published_digest}`, `to ${plans.to.published_digest}`];
6957
6947
  const prepareRegistry = Effect.fn("evidence.prepareRegistry")(function* (client, identity) {
6958
6948
  const { ecosystem, artifact, from, to } = identity.delta;
6959
- const manifests = ecosystem === "npm" ? yield* Effect.all({
6960
- from: npmVersion(client, artifact, from),
6961
- to: npmVersion(client, artifact, to)
6962
- }, { concurrency: "unbounded" }) : void 0;
6963
- const [fromPlan, toPlan] = yield* Effect.all(manifests === void 0 ? [resolve$3(client, ecosystem, artifact, from), resolve$3(client, ecosystem, artifact, to)] : [npmPlan(artifact, from, manifests.from), npmPlan(artifact, to, manifests.to)], { concurrency: "unbounded" });
6949
+ const [fromPlan, toPlan] = yield* Effect.all([resolve$3(client, ecosystem, artifact, from), resolve$3(client, ecosystem, artifact, to)], { concurrency: "unbounded" });
6964
6950
  const plans = {
6965
6951
  from: fromPlan,
6966
6952
  to: toPlan
6967
6953
  };
6968
- const npm = manifests === void 0 ? void 0 : yield* npmExtras(client, artifact, from, to, manifests);
6969
- const { key, plan } = preparationPlan(identity, {
6970
- kind: "registry",
6971
- ...plans,
6972
- ...npm
6973
- });
6974
6954
  return {
6975
- key,
6976
- plan,
6977
- artifacts: [`from ${fromPlan.published_digest}`, `to ${toPlan.published_digest}`],
6978
- materialize: (workspace) => registryWorkspace(client, workspace, identity, plans, npm, key)
6955
+ ...preparationPlan(identity, {
6956
+ kind: "registry",
6957
+ ...plans
6958
+ }),
6959
+ artifacts: digests(plans),
6960
+ materialize: (workspace) => artifactEvidence(client, workspace, plans)
6979
6961
  };
6980
6962
  });
6981
6963
  // src/audit/prepare.ts
6982
6964
  const preparers = {
6983
6965
  crates: prepareRegistry,
6984
6966
  go: prepareRegistry,
6985
- npm: prepareRegistry,
6967
+ npm: Effect.fn("evidence.prepareNpm")(function* (client, identity) {
6968
+ const { artifact, from, to } = identity.delta;
6969
+ const manifests = yield* Effect.all({
6970
+ from: npmVersion(client, artifact, from),
6971
+ to: npmVersion(client, artifact, to)
6972
+ }, { concurrency: "unbounded" });
6973
+ const [fromPlan, toPlan] = yield* Effect.all([npmPlan(artifact, from, manifests.from), npmPlan(artifact, to, manifests.to)], { concurrency: "unbounded" });
6974
+ const dependencies = yield* resolveDependencies(client, manifests, {
6975
+ os: process.platform,
6976
+ cpu: process.arch
6977
+ });
6978
+ const verifier = yield* Effect.cached(sigstoreVerifier);
6979
+ const [fromSource, toSource] = yield* Effect.all([npmSource(client, artifact, from, manifests.from, verifier), npmSource(client, artifact, to, manifests.to, verifier)], { concurrency: "unbounded" });
6980
+ const { directory } = npmRepository(manifests.to);
6981
+ const source = {
6982
+ label: artifact,
6983
+ ...directory === void 0 ? {} : { directory },
6984
+ from: fromSource,
6985
+ to: toSource
6986
+ };
6987
+ const plans = {
6988
+ from: fromPlan,
6989
+ to: toPlan
6990
+ };
6991
+ return {
6992
+ ...preparationPlan(identity, {
6993
+ kind: "npm",
6994
+ ...plans,
6995
+ dependencies,
6996
+ source
6997
+ }),
6998
+ artifacts: digests(plans),
6999
+ materialize: (workspace) => Effect.gen(function* () {
7000
+ const artifacts = yield* artifactEvidence(client, workspace, plans);
7001
+ const changed = yield* materializeDependencies(client, workspace, dependencies);
7002
+ const linked = yield* Effect.forEach(changed.binaries, (binary) => releaseAssetSources(client, binary.repository, [binary.from, binary.to]).pipe(Effect.map(([fromRelease, toRelease]) => ({
7003
+ label: binary.label,
7004
+ from: fromRelease ?? { gap: "no release lookup" },
7005
+ to: toRelease ?? { gap: "no release lookup" }
7006
+ }))), { concurrency: 2 });
7007
+ const sources = yield* materializeSources(workspace, [source, ...linked]);
7008
+ return {
7009
+ diff: Buffer.concat([
7010
+ artifacts.diff,
7011
+ changed.diff,
7012
+ sources.diff
7013
+ ]),
7014
+ evidence: "partial",
7015
+ detail: sentences([
7016
+ artifacts.detail,
7017
+ changed.detail,
7018
+ sources.detail
7019
+ ])
7020
+ };
7021
+ })
7022
+ };
7023
+ }),
6986
7024
  pypi: preparePyPI,
6987
7025
  brew: prepareBrew,
6988
7026
  "github-actions": prepareGitHubAction
@@ -7068,7 +7106,7 @@ const within = (effect, what) => effect.pipe(Effect.timeoutOrElse({
7068
7106
  issue: `evidence preparation timed out after 15 minutes`
7069
7107
  }))
7070
7108
  }));
7071
- const ensure = (workspace, preparation) => Effect.gen(function* () {
7109
+ const ensure = (workspace, identity, preparation) => Effect.gen(function* () {
7072
7110
  yield* Effect.tryPromise({
7073
7111
  try: async () => {
7074
7112
  await rm(workspace, {
@@ -7086,23 +7124,21 @@ const ensure = (workspace, preparation) => Effect.gen(function* () {
7086
7124
  issue: issueOf$1(error)
7087
7125
  })
7088
7126
  });
7089
- yield* within(preparation.materialize(workspace), "prepare workspace").pipe(Effect.onError(() => Effect.promise(async () => {
7127
+ yield* within(prepareWorkspace(workspace, identity, preparation), "prepare workspace").pipe(Effect.onError(() => Effect.promise(async () => {
7090
7128
  await rm(workspace, {
7091
7129
  recursive: true,
7092
7130
  force: true
7093
7131
  });
7094
7132
  })));
7095
7133
  });
7096
- const workspaceOf = (file) => path.join(path.dirname(path.dirname(path.dirname(file))), "workspaces", path.basename(path.dirname(file)));
7097
7134
  const sameArtifacts = (left, right) => left.length === right.length && left.every((artifact, index) => artifact === right[index]);
7098
- const reportedFrom = (file, stored) => Option.isSome(stored.report) && Option.isSome(stored.brief) ? Option.some({
7135
+ const reportedFrom = (paths, stored) => Option.isSome(stored.report) && Option.isSome(stored.brief) ? Option.some({
7099
7136
  _tag: "Reported",
7100
7137
  report: stored.report.value,
7101
7138
  evidence: stored.brief.value.evidence,
7102
7139
  detail: stored.brief.value.evidenceDetail,
7103
7140
  model: `${stored.brief.value.provider}/${stored.brief.value.modelId}`,
7104
- file,
7105
- workspace: workspaceOf(file)
7141
+ ...paths
7106
7142
  }) : Option.none();
7107
7143
  const audits$1 = (stored, artifacts) => Option.isSome(stored.brief) && sameArtifacts(stored.brief.value.artifacts, artifacts);
7108
7144
  const children = (directory) => Effect.promise(async () => await readdir(directory).catch(() => []));
@@ -7143,13 +7179,7 @@ const layer$5 = (cacheDir) => Layer.effect(Audits, Effect.gen(function* () {
7143
7179
  });
7144
7180
  const landingFile = (landing) => path.join(root, "landed", landing.mode, `${landingKey(landing)}.json`);
7145
7181
  const land = (identity) => Effect.tryPromise(async () => {
7146
- const file = landingFile({
7147
- mode: identity.mode,
7148
- ecosystem: identity.delta.ecosystem,
7149
- artifact: identity.delta.artifact,
7150
- version: identity.delta.to,
7151
- context: identity.context
7152
- });
7182
+ const file = landingFile(landingAt(identity));
7153
7183
  await mkdir(path.dirname(file), {
7154
7184
  recursive: true,
7155
7185
  mode: 448
@@ -7163,10 +7193,15 @@ const layer$5 = (cacheDir) => Layer.effect(Audits, Effect.gen(function* () {
7163
7193
  const note = JSON.parse(text);
7164
7194
  return Predicate.hasProperty(note, "from") && Predicate.hasProperty(note, "to") && Predicate.isString(note.from) && note.to === landing.version ? Option.some(note.from) : Option.none();
7165
7195
  }, () => Option.none()));
7166
- const fileOf = (identity, policyKey) => path.join(root, identity.mode, workspaceKey(identity), `${policyKey}.sqlite`);
7167
- const storedIn = (file) => Effect.scoped(Effect.gen(function* () {
7168
- if (!(yield* tryHold(workspaceOf(file)))) return Option.none();
7169
- return Option.some(yield* stored(file, ai.models));
7196
+ const workspaces = path.join(root, "workspaces");
7197
+ const workspaceFor = (identity) => path.join(workspaces, workspaceKey(identity));
7198
+ const pathsOf = (identity, policyKey) => ({
7199
+ file: path.join(root, identity.mode, workspaceKey(identity), `${policyKey}.sqlite`),
7200
+ workspace: workspaceFor(identity)
7201
+ });
7202
+ const storedIn = (paths) => Effect.scoped(Effect.gen(function* () {
7203
+ if (!(yield* tryHold(paths.workspace))) return Option.none();
7204
+ return Option.some(yield* stored(paths.file, ai.models));
7170
7205
  })).pipe(Effect.orElseSucceed(() => Option.none()));
7171
7206
  const attempt = Effect.fn("Audits.attempt")(function* (identity, options) {
7172
7207
  let transcript = [];
@@ -7185,8 +7220,8 @@ const layer$5 = (cacheDir) => Layer.effect(Audits, Effect.gen(function* () {
7185
7220
  const prompt = promptFor(identity.mode);
7186
7221
  const policyKey$1 = policyKey(prompt, setup.value);
7187
7222
  const preparation = yield* within(prepare(client, identity), "resolve evidence");
7188
- const file = fileOf(identity, policyKey$1);
7189
- const workspace = workspaceOf(file);
7223
+ const paths = pathsOf(identity, policyKey$1);
7224
+ const { file, workspace } = paths;
7190
7225
  yield* hold(workspace);
7191
7226
  const model = `${setup.value.provider}/${setup.value.modelId}`;
7192
7227
  if (existsSync(file)) {
@@ -7194,7 +7229,7 @@ const layer$5 = (cacheDir) => Layer.effect(Audits, Effect.gen(function* () {
7194
7229
  brief: Option.none(),
7195
7230
  report: Option.none()
7196
7231
  })));
7197
- const found = reportedFrom(file, stored$1);
7232
+ const found = reportedFrom(paths, stored$1);
7198
7233
  if (!options.force && Option.isSome(found) && audits$1(stored$1, preparation.artifacts)) {
7199
7234
  yield* land(identity);
7200
7235
  return found.value;
@@ -7208,7 +7243,7 @@ const layer$5 = (cacheDir) => Layer.effect(Audits, Effect.gen(function* () {
7208
7243
  }, file);
7209
7244
  }
7210
7245
  yield* Effect.addFinalizer(() => prune(workspace));
7211
- yield* ensure(workspace, preparation);
7246
+ yield* ensure(workspace, identity, preparation);
7212
7247
  const meta = yield* readMeta(workspace);
7213
7248
  yield* Effect.tryPromise({
7214
7249
  try: async () => await mkdir(path.dirname(file), {
@@ -7273,8 +7308,7 @@ const layer$5 = (cacheDir) => Layer.effect(Audits, Effect.gen(function* () {
7273
7308
  evidence: meta.evidence,
7274
7309
  detail: meta.evidence_detail,
7275
7310
  model,
7276
- file,
7277
- workspace
7311
+ ...paths
7278
7312
  };
7279
7313
  });
7280
7314
  const audit = (identity, options) => Effect.sync(() => {
@@ -7293,8 +7327,8 @@ const layer$5 = (cacheDir) => Layer.effect(Audits, Effect.gen(function* () {
7293
7327
  const recall = Effect.fn("Audits.recall")(function* (identity) {
7294
7328
  const setup = yield* ai.setup;
7295
7329
  if (Option.isNone(setup)) return Option.none();
7296
- const file = fileOf(identity, policyKey(promptFor(identity.mode), setup.value));
7297
- const found = Option.flatMap(yield* storedIn(file), (stored) => reportedFrom(file, stored));
7330
+ const paths = pathsOf(identity, policyKey(promptFor(identity.mode), setup.value));
7331
+ const found = Option.flatMap(yield* storedIn(paths), (stored) => reportedFrom(paths, stored));
7298
7332
  if (Option.isSome(found)) yield* land(identity);
7299
7333
  return found;
7300
7334
  });
@@ -7302,26 +7336,26 @@ const layer$5 = (cacheDir) => Layer.effect(Audits, Effect.gen(function* () {
7302
7336
  const setup = yield* ai.setup;
7303
7337
  if (Option.isNone(setup)) return Option.none();
7304
7338
  const preparation = yield* within(prepare(client, identity), "resolve evidence");
7305
- const file = fileOf(identity, policyKey(promptFor(identity.mode), setup.value));
7306
- return Option.filter(yield* storedIn(file), (stored) => audits$1(stored, preparation.artifacts)).pipe(Option.flatMap((stored) => reportedFrom(file, stored)));
7339
+ const paths = pathsOf(identity, policyKey(promptFor(identity.mode), setup.value));
7340
+ return Option.filter(yield* storedIn(paths), (stored) => audits$1(stored, preparation.artifacts)).pipe(Option.flatMap((stored) => reportedFrom(paths, stored)));
7307
7341
  }).pipe(Effect.catch((error) => Effect.succeedSome({
7308
7342
  _tag: "Failed",
7309
7343
  issue: `the update's artifacts could not be resolved: ${issueOf$1(error)}`,
7310
7344
  transcript: []
7311
7345
  })));
7312
7346
  const changes = (identity) => Effect.scoped(Effect.gen(function* () {
7313
- const workspace = path.join(root, "workspaces", workspaceKey(identity));
7347
+ const workspace = workspaceFor(identity);
7314
7348
  const held = yield* tryHold(workspace);
7315
7349
  if (yield* readable(workspace, identity)) return yield* read$1(workspace);
7316
7350
  if (!held) return yield* new ChangesUnavailable({ issue: "A running Audit is preparing this evidence." });
7317
7351
  const preparation = yield* within(prepare(client, identity), "resolve evidence");
7318
- yield* ensure(workspace, preparation);
7352
+ yield* ensure(workspace, identity, preparation);
7319
7353
  const view = yield* read$1(workspace);
7320
7354
  yield* prune(workspace);
7321
7355
  return view;
7322
7356
  })).pipe(Effect.mapError((error) => new ChangesUnavailable({ issue: issueOf$1(error) })));
7323
7357
  const majors = (identity) => Effect.gen(function* () {
7324
- const workspace = path.join(root, "workspaces", workspaceKey(identity));
7358
+ const workspace = workspaceFor(identity);
7325
7359
  if (!(yield* readable(workspace, identity))) return [];
7326
7360
  return yield* readMajors(workspace);
7327
7361
  }).pipe(Effect.orElseSucceed(() => []));
@@ -7334,9 +7368,8 @@ const layer$5 = (cacheDir) => Layer.effect(Audits, Effect.gen(function* () {
7334
7368
  mode: 448
7335
7369
  }));
7336
7370
  const started = yield* Clock.currentTimeMillis;
7337
- const base = path.join(root, "workspaces");
7338
- yield* Effect.forEach((yield* children(base)).filter((name) => !name.endsWith(".lock")), (name) => {
7339
- const workspace = path.join(base, name);
7371
+ yield* Effect.forEach((yield* children(workspaces)).filter((name) => !name.endsWith(".lock")), (name) => {
7372
+ const workspace = path.join(workspaces, name);
7340
7373
  return Effect.scoped(Effect.gen(function* () {
7341
7374
  if (!(yield* tryHold(workspace))) return;
7342
7375
  const prepared = yield* Effect.promise(async () => await stat(path.join(workspace, files.meta)).then((info) => info.mtimeMs, () => 0));
@@ -7367,8 +7400,8 @@ const layer$5 = (cacheDir) => Layer.effect(Audits, Effect.gen(function* () {
7367
7400
  changes,
7368
7401
  majors,
7369
7402
  landed,
7370
- transcript: (file) => Effect.scoped(Effect.gen(function* () {
7371
- if (!(yield* tryHold(workspaceOf(file)))) return yield* new AgentFailed({ issue: "the Audit is running" });
7403
+ transcript: ({ file, workspace }) => Effect.scoped(Effect.gen(function* () {
7404
+ if (!(yield* tryHold(workspace))) return yield* new AgentFailed({ issue: "the Audit is running" });
7372
7405
  return yield* transcript(file, ai.models);
7373
7406
  }))
7374
7407
  }));
@@ -7384,6 +7417,18 @@ const replaceText = Effect.fn("replaceText")(function* (file, text) {
7384
7417
  yield* fs.writeFileString(temporary, text, { mode }).pipe(Effect.mapError(fileFailed("write", temporary)));
7385
7418
  yield* fs.rename(temporary, file).pipe(Effect.mapError(fileFailed("replace", file)));
7386
7419
  });
7420
+ const editFiles = Effect.fn("editFiles")(function* (root, edits) {
7421
+ const edited = /* @__PURE__ */ new Map();
7422
+ for (const { file, edit } of edits) {
7423
+ const next = edit(edited.get(file) ?? (yield* readText(path.join(root, file))));
7424
+ if (Result.isFailure(next)) return yield* new EditRefused({
7425
+ manifest: file,
7426
+ issue: next.failure
7427
+ });
7428
+ edited.set(file, next.success);
7429
+ }
7430
+ for (const [file, text] of edited) yield* replaceText(path.join(root, file), text);
7431
+ });
7387
7432
  // src/project/find-up.ts
7388
7433
  const findUp = Effect.fn("findUp")(function* (start, name) {
7389
7434
  const fs = yield* FileSystem.FileSystem;
@@ -7781,7 +7826,6 @@ const publicTags = (url) => tags(url, { timeout: lsRemoteTimeout }).pipe(Effect.
7781
7826
  const make$13 = (root, origin, readPublic = publicTags) => Effect.gen(function* () {
7782
7827
  const exec = yield* Exec;
7783
7828
  const fs = yield* FileSystem.FileSystem;
7784
- const read = (file) => readText(file).pipe(Effect.provideService(FileSystem.FileSystem, fs));
7785
7829
  const scanned = Effect.gen(function* () {
7786
7830
  const files = yield* workflowFiles(root).pipe(Effect.provideService(FileSystem.FileSystem, fs));
7787
7831
  return {
@@ -7868,17 +7912,10 @@ const make$13 = (root, origin, readPublic = publicTags) => Effect.gen(function*
7868
7912
  expected: pinned && pkg.revision !== void 0 ? `${target} at ${short(pkg.revision)}` : target,
7869
7913
  ...sha === void 0 ? {} : { offered: `${target} at ${short(sha)}` }
7870
7914
  });
7871
- const edited = /* @__PURE__ */ new Map();
7872
- for (const use of group.uses) {
7873
- const text = edited.get(use.file) ?? (yield* read(path.join(root, use.file)));
7874
- const next = pinned ? editYamlScalar(text, use.path, `${use.name}@${sha}`, movedComment(use.comment, target)) : editYamlScalar(text, use.path, `${use.name}@${target}`);
7875
- if (Result.isFailure(next)) return yield* new EditRefused({
7876
- manifest: use.file,
7877
- issue: next.failure
7878
- });
7879
- edited.set(use.file, next.success);
7880
- }
7881
- for (const [file, text] of edited) yield* replaceText(path.join(root, file), text).pipe(Effect.provideService(FileSystem.FileSystem, fs));
7915
+ yield* editFiles(root, group.uses.map((use) => ({
7916
+ file: use.file,
7917
+ edit: (text) => pinned ? editYamlScalar(text, use.path, `${use.name}@${sha}`, movedComment(use.comment, target)) : editYamlScalar(text, use.path, `${use.name}@${target}`)
7918
+ }))).pipe(Effect.provideService(FileSystem.FileSystem, fs));
7882
7919
  });
7883
7920
  return {
7884
7921
  name: providerName$1,
@@ -9019,7 +9056,7 @@ const versionsOf = (kind, installed, offer) => {
9019
9056
  target: withBuild(offer.short, offer.version)
9020
9057
  };
9021
9058
  };
9022
- const caskInfo = brew$1([
9059
+ const caskInfo = brew([
9023
9060
  "info",
9024
9061
  "--json=v2",
9025
9062
  "--installed",
@@ -9281,7 +9318,7 @@ const makeWith = (options) => Effect.gen(function* () {
9281
9318
  const queue = yield* Ref.make([]);
9282
9319
  const using = options.tools ?? tools$2;
9283
9320
  const helper = options.sparkle === void 0 ? locate$3.pipe(Effect.provideService(FileSystem.FileSystem, fs)) : Effect.succeed(options.sparkle);
9284
- const sparkleOf = (pkg, install, build) => {
9321
+ const sparkleHelper = (pkg, install) => {
9285
9322
  const refused = (reason) => new InstallRefused({
9286
9323
  package: pkg.name,
9287
9324
  reason
@@ -9292,10 +9329,8 @@ const makeWith = (options) => Effect.gen(function* () {
9292
9329
  if (Option.isNone(found)) return yield* refused("this copy of upup has no upup-sparkle, which installs Sparkle apps' updates; build it with `vp run sparkle`");
9293
9330
  if (install.installationType === "package" && found.value.team === null) return yield* refused("its update is an installer package, which Sparkle installs only through an upup-sparkle signed with a Developer ID, and this copy of upup's is signed ad hoc");
9294
9331
  return {
9295
- kind: "sparkle",
9296
- helper: found.value,
9297
- build,
9298
- feed: install.feed
9332
+ ...install,
9333
+ helper: found.value
9299
9334
  };
9300
9335
  });
9301
9336
  };
@@ -9320,21 +9355,23 @@ const makeWith = (options) => Effect.gen(function* () {
9320
9355
  const inside = yield* hosts.pipe(Effect.mapError((error) => refused(`upup could not tell which app it runs in, so it risks none: ${describeError(error)}`)));
9321
9356
  if (inside.paths.has(pkg.id) || inside.ids.has(bundle.bundleId)) return yield* refused(`upup is running inside ${pkg.name}; update it from another terminal app`);
9322
9357
  yield* unchanged$1(pkg, plist);
9323
- const planned = install.kind === "sparkle" ? yield* sparkleOf(pkg, install, offer.version) : install;
9358
+ const planned = install.kind === "sparkle" ? yield* sparkleHelper(pkg, install) : install;
9324
9359
  const writable = yield* Effect.isSuccess(Effect.all([fs.access(pkg.id, { writable: true }), fs.access(path.dirname(pkg.id), { writable: true })]));
9325
- const ownsApp = install.kind === "sparkle" && (yield* owned(pkg.id));
9326
9360
  const quitAllowed = permission?.quitApps === true;
9327
9361
  yield* gate(pkg, bundle.bundleId, quitAllowed);
9328
9362
  const team = yield* teamOf(pkg.id);
9329
9363
  if (team === void 0) return yield* refused("it is not signed by a developer team, so upup cannot check its update");
9330
- const version = {
9331
- key: install.kind === "sparkle" ? "CFBundleVersion" : "CFBundleShortVersionString",
9332
- value: offer.version
9333
- };
9334
9364
  const prepared = planned.kind === "sparkle" ? {
9335
- ...planned,
9336
- requirement: requirement(bundle.bundleId, team)
9337
- } : yield* stage(client, {
9365
+ update: {
9366
+ kind: "sparkle",
9367
+ helper: planned.helper,
9368
+ build: offer.version,
9369
+ feed: planned.feed,
9370
+ requirement: requirement(bundle.bundleId, team)
9371
+ },
9372
+ dir: yield* room(pkg.name),
9373
+ root: !writable || planned.installationType === "package" || !(yield* owned(pkg.id))
9374
+ } : yield* Effect.map(stage(client, {
9338
9375
  name: pkg.name,
9339
9376
  url: planned.url,
9340
9377
  ...planned.size === void 0 ? {} : { size: planned.size },
@@ -9343,18 +9380,19 @@ const makeWith = (options) => Effect.gen(function* () {
9343
9380
  team,
9344
9381
  arch: listing.here.arch,
9345
9382
  version: offer.version
9346
- });
9347
- const dir = prepared.kind === "app" ? prepared.dir : yield* room(pkg.name);
9383
+ }), (staged) => ({
9384
+ update: staged,
9385
+ dir: staged.dir,
9386
+ root: !writable
9387
+ }));
9348
9388
  yield* Ref.update(queue, (all) => [...all, {
9349
9389
  pkg,
9350
9390
  bundleId: bundle.bundleId,
9351
9391
  executable: path.join("Contents", "MacOS", bundle.executable ?? pkg.name),
9352
9392
  installed: plist,
9353
- update: prepared,
9354
- dir,
9393
+ ...prepared,
9355
9394
  quitAllowed,
9356
- root: !writable || install.kind === "sparkle" && (install.installationType === "package" || !ownsApp),
9357
- version,
9395
+ version: offer.version,
9358
9396
  team
9359
9397
  }]);
9360
9398
  }, provide);
@@ -9364,9 +9402,10 @@ const makeWith = (options) => Effect.gen(function* () {
9364
9402
  if (now.value === item.installed) return replaceable(now.value);
9365
9403
  const { info, digest } = yield* readInfoStable(item.pkg.id);
9366
9404
  if (Option.isNone(info) || digest !== now.value || info.value.CFBundleIdentifier !== item.bundleId || (yield* teamOf(item.pkg.id)) !== item.team) return changed;
9367
- const version = info.value[item.version.key];
9368
- if (version === item.version.value) return alreadyUpdated;
9369
- return version !== void 0 && (item.version.key === "CFBundleVersion" ? isNewer(item.version.value, version) : isNewerRelease(item.version.value, version)) ? replaceable(now.value) : changed;
9405
+ const sparkle = item.update.kind === "sparkle";
9406
+ const version = sparkle ? info.value.CFBundleVersion : info.value.CFBundleShortVersionString;
9407
+ if (version === item.version) return alreadyUpdated;
9408
+ return version !== void 0 && (sparkle ? isNewer(item.version, version) : isNewerRelease(item.version, version)) ? replaceable(now.value) : changed;
9370
9409
  }).pipe(Effect.orElseSucceed(() => changed));
9371
9410
  const ready = (item) => Effect.gen(function* () {
9372
9411
  const now = yield* installedNow(item);
@@ -9471,7 +9510,7 @@ const makeWith = (options) => Effect.gen(function* () {
9471
9510
  uninstall: Effect.fn("Apps.uninstall")(function* (pkg) {
9472
9511
  const token = (yield* Ref.get(memory))?.rows.get(pkg.id)?.cask;
9473
9512
  if (token === void 0) return yield* new Unsupported({ reason: unremovable$1 });
9474
- yield* exec.stdout(brew$1([
9513
+ yield* exec.stdout(brew([
9475
9514
  "uninstall",
9476
9515
  "--cask",
9477
9516
  token
@@ -9925,6 +9964,11 @@ const matchesGoPattern = (pattern, path) => {
9925
9964
  const prefix = segments.slice(0, parts.length).join("/");
9926
9965
  return new RegExp(`^${parts.map((part) => RegExp.escape(part).replaceAll("\\*", "[^/]*").replaceAll("\\?", "[^/]")).join("/")}$`, "u").test(prefix);
9927
9966
  };
9967
+ const GoProxyEnv = Schema.Struct({
9968
+ GOPROXY: Schema.optionalKey(Schema.String),
9969
+ GOPRIVATE: Schema.optionalKey(Schema.String),
9970
+ GONOPROXY: Schema.optionalKey(Schema.String)
9971
+ });
9928
9972
  const privateGoModule = (env, path) => {
9929
9973
  if (goPatterns(env.GONOPROXY === void 0 || env.GONOPROXY === "" ? env.GOPRIVATE ?? "" : env.GONOPROXY).some((pattern) => matchesGoPattern(pattern, path))) return "a GOPRIVATE module";
9930
9974
  const [proxy] = goPatterns((env.GOPROXY ?? "https://proxy.golang.org,direct").replaceAll("|", ","));
@@ -9942,7 +9986,21 @@ const privatePypiIndex = (env = process.env) => {
9942
9986
  const index = env["UV_DEFAULT_INDEX"] ?? env["UV_INDEX_URL"] ?? env["PIP_INDEX_URL"];
9943
9987
  return index === void 0 || index === "" || publicPypi.has(trailing(index)) ? void 0 : index;
9944
9988
  };
9989
+ const withPypiOrigin = (index) => (pkg) => index === void 0 ? pkg : {
9990
+ ...pkg,
9991
+ privateRegistry: index
9992
+ };
9945
9993
  // src/go/go-json.ts
9994
+ const go = (cwd, args, env = {}) => ({
9995
+ command: "go",
9996
+ args,
9997
+ cwd,
9998
+ env: {
9999
+ GOWORK: "off",
10000
+ GIT_TERMINAL_PROMPT: "0",
10001
+ ...env
10002
+ }
10003
+ });
9946
10004
  const decodeGoJson = (schema, command) => decodeJson(Schema.toCodecGoJson(schema), command);
9947
10005
  const jsonObjects = (text) => {
9948
10006
  const objects = [];
@@ -9971,22 +10029,14 @@ const jsonObjects = (text) => {
9971
10029
  const GoEnv = Schema.Struct({
9972
10030
  GOBIN: Schema.String,
9973
10031
  GOPATH: Schema.String,
9974
- GOPROXY: Schema.optionalKey(Schema.String),
9975
- GOPRIVATE: Schema.optionalKey(Schema.String),
9976
- GONOPROXY: Schema.optionalKey(Schema.String)
10032
+ ...GoProxyEnv.fields
9977
10033
  });
9978
- const envKeys = [
9979
- "GOBIN",
9980
- "GOPATH",
9981
- "GOPROXY",
9982
- "GOPRIVATE",
9983
- "GONOPROXY"
9984
- ];
10034
+ const envKeys = Object.keys(GoEnv.fields);
9985
10035
  const BuildInfo = Schema.Struct({
9986
- Path: Schema.String,
10036
+ Path: Schema.optionalKey(Schema.String),
9987
10037
  Main: Schema.Struct({
9988
- Path: Schema.String,
9989
- Version: Schema.String,
10038
+ Path: Schema.optionalKey(Schema.String),
10039
+ Version: Schema.optionalKey(Schema.String),
9990
10040
  Sum: Schema.optionalKey(Schema.String),
9991
10041
  Replace: Schema.optionalKey(Schema.Unknown)
9992
10042
  })
@@ -9996,36 +10046,27 @@ const ModuleQuery = Schema.Struct({
9996
10046
  Version: Schema.String,
9997
10047
  Error: Schema.optionalKey(Schema.Struct({ Err: Schema.String }))
9998
10048
  });
9999
- const go = (args, env = {}) => ({
10000
- command: "go",
10001
- args,
10002
- cwd: "/",
10003
- env: {
10004
- GOWORK: "off",
10005
- GIT_TERMINAL_PROMPT: "0",
10006
- ...env
10007
- }
10008
- });
10009
10049
  const executableName = (importPath) => {
10010
10050
  const elements = importPath.split("/");
10011
10051
  const last = elements.at(-1) ?? importPath;
10012
10052
  return /^v\d+$/u.test(last) && elements.length > 1 ? elements.at(-2) ?? last : last;
10013
10053
  };
10014
10054
  const fromBuildInfo = (info, file) => {
10015
- const { Main: main } = info;
10016
- return main.Sum !== void 0 && main.Sum !== "" && main.Replace === void 0 && semver.valid(main.Version) !== null && (info.Path === main.Path || info.Path.startsWith(`${main.Path}/`)) && executableName(info.Path) === file ? Option.some({
10055
+ const { Path: importPath, Main: main } = info;
10056
+ const { Path: module, Version: version } = main;
10057
+ return importPath !== void 0 && module !== void 0 && version !== void 0 && main.Sum !== void 0 && main.Replace === void 0 && semver.valid(version) !== null && (importPath === module || importPath.startsWith(`${module}/`)) && executableName(importPath) === file ? Option.some({
10017
10058
  provider: "go-install",
10018
- id: info.Path,
10059
+ id: importPath,
10019
10060
  name: file,
10020
10061
  ecosystem: "go",
10021
- artifact: main.Path,
10022
- current: main.Version
10062
+ artifact: module,
10063
+ current: version
10023
10064
  }) : Option.none();
10024
10065
  };
10025
10066
  const make$8 = Effect.gen(function* () {
10026
10067
  const exec = yield* Exec;
10027
10068
  const fs = yield* FileSystem.FileSystem;
10028
- const envInvocation = go([
10069
+ const envInvocation = go("/", [
10029
10070
  "env",
10030
10071
  "-json",
10031
10072
  ...envKeys
@@ -10033,7 +10074,7 @@ const make$8 = Effect.gen(function* () {
10033
10074
  const goEnv = yield* Effect.cached(exec.stdout(envInvocation).pipe(Effect.flatMap(decodeGoJson(GoEnv, commandLine(envInvocation)))));
10034
10075
  const binDir = goEnv.pipe(Effect.map((env) => env.GOBIN || path.join(env.GOPATH.split(":")[0] ?? env.GOPATH, "bin")));
10035
10076
  const inspect = Effect.fn("GoInstall.inspect")(function* (dir, file) {
10036
- const invocation = go([
10077
+ const invocation = go("/", [
10037
10078
  "version",
10038
10079
  "-m",
10039
10080
  "-json",
@@ -10055,7 +10096,7 @@ const make$8 = Effect.gen(function* () {
10055
10096
  });
10056
10097
  const latest = Effect.fn("GoInstall.latest")(function* (modules) {
10057
10098
  if (modules.length === 0) return /* @__PURE__ */ new Map();
10058
- const invocation = go([
10099
+ const invocation = go("/", [
10059
10100
  "list",
10060
10101
  "-m",
10061
10102
  "-e",
@@ -10105,7 +10146,7 @@ const make$8 = Effect.gen(function* () {
10105
10146
  }),
10106
10147
  update: Effect.fn("GoInstall.update")(function* (pkg, target) {
10107
10148
  const dir = yield* binDir;
10108
- yield* exec.stdout(go([
10149
+ yield* exec.stdout(go("/", [
10109
10150
  "install",
10110
10151
  "--",
10111
10152
  `${pkg.id}@${target}`
@@ -10138,27 +10179,12 @@ const WorkFile = Schema.Struct({
10138
10179
  Use: Schema.optionalKey(Schema.Array(Schema.Struct({ DiskPath: Schema.String }))),
10139
10180
  Replace: Schema.optionalKey(Schema.Array(Schema.Struct({ Old: Schema.Struct({ Path: Schema.String }) })))
10140
10181
  });
10141
- const goEnv = {
10142
- GOWORK: "off",
10143
- GIT_TERMINAL_PROMPT: "0"
10144
- };
10145
- const GoProxyEnv = Schema.Struct({
10146
- GOPROXY: Schema.optionalKey(Schema.String),
10147
- GOPRIVATE: Schema.optionalKey(Schema.String),
10148
- GONOPROXY: Schema.optionalKey(Schema.String)
10149
- });
10150
10182
  const directRequirements = (mod, local) => {
10151
10183
  const replaced = new Set((mod.Replace ?? []).map((replace) => replace.Old.Path));
10152
10184
  return (mod.Require ?? []).filter((requirement) => requirement.Indirect !== true && !replaced.has(requirement.Path) && !local.has(requirement.Path));
10153
10185
  };
10154
- const goIn = (cwd, args) => ({
10155
- command: "go",
10156
- args,
10157
- cwd,
10158
- env: goEnv
10159
- });
10160
10186
  const readModFile = (exec, cwd) => {
10161
- const edit = goIn(cwd, [
10187
+ const edit = go(cwd, [
10162
10188
  "mod",
10163
10189
  "edit",
10164
10190
  "-json"
@@ -10168,13 +10194,13 @@ const readModFile = (exec, cwd) => {
10168
10194
  const make$7 = (root, moduleDir, place = "", local = /* @__PURE__ */ new Set()) => Effect.gen(function* () {
10169
10195
  const exec = yield* Exec;
10170
10196
  const manifest = path.join(place, path.relative(root, path.join(moduleDir, "go.mod")));
10171
- const go = (args) => goIn(moduleDir, args);
10197
+ const inModule = (args) => go(moduleDir, args);
10172
10198
  const requirements = Effect.gen(function* () {
10173
10199
  return directRequirements(yield* readModFile(exec, moduleDir), local);
10174
10200
  });
10175
10201
  const selected = Effect.fn("GoModule.selected")(function* (paths, withUpdates) {
10176
10202
  if (paths.length === 0) return /* @__PURE__ */ new Map();
10177
- const list = go([
10203
+ const list = inModule([
10178
10204
  "list",
10179
10205
  "-m",
10180
10206
  ...withUpdates ? ["-u"] : [],
@@ -10200,12 +10226,10 @@ const make$7 = (root, moduleDir, place = "", local = /* @__PURE__ */ new Set())
10200
10226
  selector: requirement.Version
10201
10227
  }
10202
10228
  });
10203
- const env = go([
10229
+ const env = inModule([
10204
10230
  "env",
10205
10231
  "-json",
10206
- "GOPROXY",
10207
- "GOPRIVATE",
10208
- "GONOPROXY"
10232
+ ...Object.keys(GoProxyEnv.fields)
10209
10233
  ]);
10210
10234
  const origin = exec.stdout(env).pipe(Effect.flatMap(decodeGoJson(GoProxyEnv, commandLine(env))), Effect.map(withGoOrigin));
10211
10235
  const list = Effect.gen(function* () {
@@ -10236,16 +10260,16 @@ const make$7 = (root, moduleDir, place = "", local = /* @__PURE__ */ new Set())
10236
10260
  return Option.some(toPackage(requirement, entries.get(requirement.Path)));
10237
10261
  });
10238
10262
  const update = Effect.fn("GoModule.update")(function* (pkg, target) {
10239
- yield* exec.stdout(go(["get", `${pkg.id}@${target}`]));
10263
+ yield* exec.stdout(inModule(["get", `${pkg.id}@${target}`]));
10240
10264
  });
10241
10265
  const uninstall = Effect.fn("GoModule.uninstall")(function* (pkg) {
10242
- yield* exec.stdout(go([
10266
+ yield* exec.stdout(inModule([
10243
10267
  "mod",
10244
10268
  "edit",
10245
10269
  `-droprequire=${pkg.id}`
10246
10270
  ]));
10247
10271
  });
10248
- const reconcile = () => exec.stdout(go(["mod", "tidy"])).pipe(Effect.as(void 0));
10272
+ const reconcile = () => exec.stdout(inModule(["mod", "tidy"])).pipe(Effect.as(void 0));
10249
10273
  return {
10250
10274
  name: manifest,
10251
10275
  mode: "project",
@@ -10661,7 +10685,6 @@ const makeVp = makeGlobal({
10661
10685
  list: [
10662
10686
  "list",
10663
10687
  "-g",
10664
- "--depth=0",
10665
10688
  "--json"
10666
10689
  ],
10667
10690
  outdated: [
@@ -10794,7 +10817,6 @@ const make$6 = (root, dir = root, nested = []) => Effect.gen(function* () {
10794
10817
  const client = yield* HttpClient.HttpClient;
10795
10818
  const context = yield* Effect.context();
10796
10819
  const scanned = scan(root, dir, nested).pipe(Effect.provideContext(context));
10797
- const read = (file) => readText(path.join(root, file)).pipe(Effect.provideService(FileSystem.FileSystem, fs));
10798
10820
  const list = Effect.gen(function* () {
10799
10821
  const pins = yield* scanned;
10800
10822
  const notices = [];
@@ -10833,19 +10855,15 @@ const make$6 = (root, dir = root, nested = []) => Effect.gen(function* () {
10833
10855
  manifest: pkg.declaration?.manifest ?? "",
10834
10856
  issue: `Node.js ${pkg.current} is no longer pinned there`
10835
10857
  });
10836
- const edited = /* @__PURE__ */ new Map();
10837
- for (const pin of group) {
10838
- const text = edited.get(pin.file) ?? (yield* read(pin.file));
10839
- const next = `${pin.prefix}${target}`;
10840
- const { parts, at } = versionLine(text);
10841
- const moved = pin.path === void 0 ? Result.succeed(parts.map((part, index) => index === at ? part.replace(`${pin.prefix}${pin.version}`, next) : part).join("")) : editYamlScalar(text, pin.path, next);
10842
- if (Result.isFailure(moved)) return yield* new EditRefused({
10843
- manifest: pin.file,
10844
- issue: moved.failure
10845
- });
10846
- edited.set(pin.file, moved.success);
10847
- }
10848
- for (const [file, text] of edited) yield* replaceText(path.join(root, file), text).pipe(Effect.provideService(FileSystem.FileSystem, fs));
10858
+ yield* editFiles(root, group.map((pin) => ({
10859
+ file: pin.file,
10860
+ edit: (text) => {
10861
+ const next = `${pin.prefix}${target}`;
10862
+ if (pin.path !== void 0) return editYamlScalar(text, pin.path, next);
10863
+ const { parts, at } = versionLine(text);
10864
+ return Result.succeed(parts.map((part, index) => index === at ? part.replace(`${pin.prefix}${pin.version}`, next) : part).join(""));
10865
+ }
10866
+ }))).pipe(Effect.provideService(FileSystem.FileSystem, fs));
10849
10867
  });
10850
10868
  return {
10851
10869
  name: providerName,
@@ -11156,7 +11174,7 @@ const make$5 = (root, manager, place = "", claimed = []) => Effect.gen(function*
11156
11174
  message: line.failure
11157
11175
  });
11158
11176
  const target = Result.getOrUndefined(line);
11159
- if (target === void 0 || !isNewerStable(pkg.current, target)) return pkg;
11177
+ if (target === void 0) return pkg;
11160
11178
  const selector = parseSelector(pkg.name, pkg.declaration?.selector ?? "");
11161
11179
  if (selector === void 0 || moveTo(selector, target) === void 0) {
11162
11180
  notices.push({
@@ -11827,13 +11845,10 @@ const make = Effect.gen(function* () {
11827
11845
  "--show-python"
11828
11846
  ])).pipe(Effect.map(parseTools))], { concurrency: "unbounded" });
11829
11847
  const targets = new Map(outdated.flatMap((tool) => tool.fromPyPI && tool.latest !== void 0 ? [[tool.pkg.id, tool.latest]] : []));
11830
- const index = privatePypiIndex();
11848
+ const origin = withPypiOrigin(privatePypiIndex());
11831
11849
  return {
11832
11850
  packages: packages.map((listed) => {
11833
- const pkg = index === void 0 ? listed : {
11834
- ...listed,
11835
- privateRegistry: index
11836
- };
11851
+ const pkg = origin(listed);
11837
11852
  const target = targets.get(pkg.id);
11838
11853
  return target === void 0 ? pkg : withTarget(pkg, target);
11839
11854
  }),
@@ -11925,12 +11940,12 @@ const deprecationOf = (formula) => {
11925
11940
  if (formula.disabled) return `Homebrew disabled it (${formula.disable_reason ?? "no reason given"})`;
11926
11941
  if (formula.deprecated) return formula.deprecation_reason ?? "no reason given";
11927
11942
  };
11928
- const lookup$4 = Effect.fn("brew.lookup")(function* (client, wanted) {
11943
+ const lookup$3 = Effect.fn("brew.lookup")(function* (client, wanted) {
11929
11944
  if (wanted.name.includes("/")) return yield* new MetadataUnavailable({
11930
11945
  source: wanted.name,
11931
11946
  issue: "Homebrew's API only describes formulae and casks from the core taps"
11932
11947
  });
11933
- const kind = wanted.kind === "cask" ? "cask" : "formula";
11948
+ const kind = wanted.kind ?? "formula";
11934
11949
  const formula = yield* getJson(client, `https://formulae.brew.sh/api/${kind}/${wanted.name}.json`, Formula);
11935
11950
  const installs = formula.analytics?.install["30d"][wanted.name];
11936
11951
  const deprecated = deprecationOf(formula);
@@ -11958,7 +11973,7 @@ const Crate = Schema.Struct({
11958
11973
  yank_message: Schema.NullOr(Schema.String)
11959
11974
  }))
11960
11975
  });
11961
- const lookup$3 = Effect.fn("crates.lookup")(function* (client, wanted) {
11976
+ const lookup$2 = Effect.fn("crates.lookup")(function* (client, wanted) {
11962
11977
  const { crate, versions } = yield* getJson(client, `https://crates.io/api/v1/crates/${wanted.name}`, Crate);
11963
11978
  const relevant = versions.filter((version) => wanted.versions.includes(version.num));
11964
11979
  return {
@@ -11979,7 +11994,7 @@ const Repository = Schema.Struct({
11979
11994
  archived: Schema.Boolean
11980
11995
  });
11981
11996
  const Release = Schema.Struct({ published_at: Schema.NullOr(Schema.String) });
11982
- const lookup$2 = Effect.fn("githubActions.lookup")(function* (client, wanted) {
11997
+ const lookup$1 = Effect.fn("githubActions.lookup")(function* (client, wanted) {
11983
11998
  const token = yield* githubToken.pipe(Effect.provide(nodeLayer));
11984
11999
  const headers = githubHeaders(token);
11985
12000
  const base = `${githubApi}/repos/${wanted.name}`;
@@ -11995,41 +12010,6 @@ const lookup$2 = Effect.fn("githubActions.lookup")(function* (client, wanted) {
11995
12010
  withdrawn: {}
11996
12011
  };
11997
12012
  });
11998
- // src/registry/npm.ts
11999
- const Version = Schema.Struct({ deprecated: Schema.optionalKey(Schema.Unknown) });
12000
- const Packument = Schema.Struct({
12001
- description: Schema.optionalKey(Schema.String),
12002
- homepage: Schema.optionalKey(Schema.String),
12003
- repository: Schema.optionalKey(Schema.Union([Schema.String, Schema.Struct({ url: Schema.optionalKey(Schema.String) })])),
12004
- "dist-tags": Schema.optionalKey(Schema.Struct({ latest: Schema.optionalKey(Schema.String) })),
12005
- time: Schema.optionalKey(Schema.Record(Schema.String, Schema.String)),
12006
- versions: Schema.optionalKey(Schema.Record(Schema.String, Version))
12007
- });
12008
- const Downloads = Schema.Struct({ downloads: Schema.Finite });
12009
- const message = (version) => {
12010
- const deprecated = version?.deprecated;
12011
- return Predicate.isString(deprecated) && deprecated !== "" ? Option.some(deprecated) : Option.none();
12012
- };
12013
- const lookup$1 = Effect.fn("npm.lookup")(function* (client, wanted) {
12014
- const path = wanted.name.replace("/", "%2F");
12015
- const [packument, downloads] = yield* Effect.all([getJson(client, `https://registry.npmjs.org/${path}`, Packument), getJson(client, `https://api.npmjs.org/downloads/point/last-week/${wanted.name}`, Downloads).pipe(Effect.option)], { concurrency: "unbounded" });
12016
- const versions = packument.versions ?? {};
12017
- const latest = packument["dist-tags"]?.latest;
12018
- const repository = Predicate.isString(packument.repository) ? packument.repository : packument.repository?.url;
12019
- const deprecated = latest === void 0 ? Option.none() : message(versions[latest]);
12020
- return {
12021
- ...packument.description === void 0 ? {} : { description: packument.description },
12022
- ...packument.homepage === void 0 ? {} : { homepage: packument.homepage },
12023
- ...repository === void 0 ? {} : { repository: repositoryUrl(repository) },
12024
- ...Option.isSome(downloads) ? { popularity: `${count(downloads.value.downloads)} downloads last week` } : {},
12025
- ...Option.isSome(deprecated) ? { deprecated: deprecated.value } : {},
12026
- released: pick$1(packument.time ?? {}, wanted.versions),
12027
- withdrawn: Object.fromEntries(wanted.versions.flatMap((version) => Option.match(message(versions[version]), {
12028
- onNone: () => [],
12029
- onSome: (reason) => [[version, reason]]
12030
- })))
12031
- };
12032
- });
12033
12013
  // src/registry/pypi.ts
12034
12014
  const Project = Schema.Struct({
12035
12015
  info: Schema.Struct({
@@ -12079,12 +12059,12 @@ const vendor = (source, issue) => (_, wanted) => Effect.fail(new MetadataUnavail
12079
12059
  issue: `${wanted.name} ${issue}`
12080
12060
  }));
12081
12061
  const sources = {
12082
- brew: lookup$4,
12083
- npm: lookup$1,
12084
- crates: lookup$3,
12062
+ brew: lookup$3,
12063
+ npm: lookup$4,
12064
+ crates: lookup$2,
12085
12065
  go: lookup$5,
12086
12066
  pypi: lookup,
12087
- "github-actions": lookup$2,
12067
+ "github-actions": lookup$1,
12088
12068
  macos: vendor("Software Update", "comes from Apple's Software Update, which has no registry"),
12089
12069
  apt: vendor("apt", "comes from your distribution's apt repositories, which upup does not read"),
12090
12070
  sparkle: vendor("Sparkle", "updates from its own Sparkle feed, which has no registry"),
@@ -12101,7 +12081,7 @@ const freshFor = 864e5;
12101
12081
  const wanted = (pkg) => ({
12102
12082
  name: pkg.artifact,
12103
12083
  versions: [pkg.current, ...pkg.target === void 0 ? [] : [pkg.target]],
12104
- ...isCask(pkg) ? { kind: "cask" } : {}
12084
+ ...kindOf$2(pkg) === "cask" ? { kind: "cask" } : {}
12105
12085
  });
12106
12086
  const layer$2 = (cacheDir) => Layer.effect(Registry, Effect.gen(function* () {
12107
12087
  const client = yield* HttpClient.HttpClient;
@@ -12438,15 +12418,16 @@ const auditTools = (home) => Action.implement(AuditActions, Effect.gen(function*
12438
12418
  const audits = yield* Audits;
12439
12419
  const ai = yield* Ai;
12440
12420
  const status = (pkg, mode) => Effect.gen(function* () {
12441
- const identity = identityOf(pkg, mode);
12442
- if (identity === void 0) {
12443
- const reason = unauditable(pkg) ?? "no update is offered";
12421
+ const audit = auditOf(pkg, mode);
12422
+ if (Result.isFailure(audit)) {
12423
+ const reason = audit.failure;
12444
12424
  return {
12445
12425
  key: rowKey(pkg),
12446
12426
  status: "not-auditable",
12447
12427
  reason: `${reason.charAt(0).toUpperCase()}${reason.slice(1)}.`
12448
12428
  };
12449
12429
  }
12430
+ const identity = audit.success;
12450
12431
  const job = (yield* audits.jobs)[jobKey(identity)];
12451
12432
  if (job?._tag === "Running") return {
12452
12433
  key: rowKey(pkg),
@@ -12479,17 +12460,17 @@ const auditTools = (home) => Action.implement(AuditActions, Effect.gen(function*
12479
12460
  }),
12480
12461
  package_changes: ({ key, path, ...where }) => Effect.gen(function* () {
12481
12462
  const { scope, pkg } = yield* resolve.one(where, key);
12482
- const identity = identityOf(pkg, scope.mode);
12483
- if (identity === void 0) {
12463
+ const audit = auditOf(pkg, scope.mode);
12464
+ if (Result.isFailure(audit)) {
12484
12465
  if (pkg.target === void 0) return yield* new ChangesUnavailable({ issue: "No update is offered." });
12485
- if (pkg.notes === void 0) return yield* new ChangesUnavailable({ issue: `Nothing to compare: ${unauditable(pkg) ?? "it has no exact update to compare"}${isAppUpdate(pkg) ? ", and its feed has no release notes" : ""}.` });
12466
+ if (pkg.notes === void 0) return yield* new ChangesUnavailable({ issue: `Nothing to compare: ${audit.failure}${isAppUpdate(pkg) ? ", and its feed has no release notes" : ""}.` });
12486
12467
  if (path !== void 0) return yield* new ChangesUnavailable({ issue: `${pkg.name}'s update has release notes but no files to diff; leave out path.` });
12487
12468
  return {
12488
12469
  ...versions(pkg),
12489
12470
  notes: pkg.notes
12490
12471
  };
12491
12472
  }
12492
- const view = yield* audits.changes(identity);
12473
+ const view = yield* audits.changes(audit.success);
12493
12474
  const files = view.sections.map((section) => ({
12494
12475
  path: sectionPath(section),
12495
12476
  ...changedLines(section),
@@ -12794,33 +12775,34 @@ const reasonFor = (result) => Option.match(result, {
12794
12775
  onNone: () => "it has no finished audit of these exact artifacts",
12795
12776
  onSome: (found) => found._tag === "Failed" ? `its audit failed: ${found.issue}` : `its audit found ${found.report.verdict}`
12796
12777
  });
12778
+ const approves = (result) => Option.isSome(result) && result.value._tag === "Reported" && result.value.report.verdict === "ok";
12797
12779
  const screen = (targets, mode, resultOf) => Effect.gen(function* () {
12798
- const identities = /* @__PURE__ */ new Map();
12799
- for (const target of targets) {
12800
- const identity = identityOf(target.pkg, mode);
12801
- if (identity !== void 0) identities.set(workspaceKey(identity), identity);
12802
- }
12803
- const results = new Map(yield* Effect.forEach(identities, ([key, identity]) => Effect.map(resultOf(identity), (result) => [key, result]), { concurrency: 4 }));
12804
- const approved = [];
12805
12780
  const refused = {};
12781
+ const groups = /* @__PURE__ */ new Map();
12806
12782
  for (const target of targets) {
12807
- if (isUnauditedUpdate(target.pkg)) {
12808
- approved.push(target);
12783
+ if (isUnauditedUpdate(target.pkg)) continue;
12784
+ const audit = auditOf(target.pkg, mode);
12785
+ if (Result.isFailure(audit)) {
12786
+ refused[rowKey(target.pkg)] = Outcome.Excluded({ reason: audit.failure });
12809
12787
  continue;
12810
12788
  }
12811
- const identity = identityOf(target.pkg, mode);
12812
- const result = identity === void 0 ? void 0 : results.get(workspaceKey(identity));
12813
- if (result !== void 0 && Option.isSome(result)) {
12814
- const found = result.value;
12815
- if (found._tag === "Reported" && found.report.verdict === "ok") {
12816
- approved.push(target);
12817
- continue;
12818
- }
12819
- }
12820
- refused[rowKey(target.pkg)] = Outcome.Excluded({ reason: result === void 0 ? unauditable(target.pkg) ?? "it has no exact update to audit" : reasonFor(result) });
12789
+ const key = workspaceKey(audit.success);
12790
+ const group = groups.get(key) ?? {
12791
+ identity: audit.success,
12792
+ members: []
12793
+ };
12794
+ group.members.push(target);
12795
+ groups.set(key, group);
12821
12796
  }
12797
+ yield* Effect.forEach(groups.values(), ({ identity, members }) => Effect.map(resultOf(identity), (found) => {
12798
+ if (approves(found)) return;
12799
+ for (const target of members) refused[rowKey(target.pkg)] = Outcome.Excluded({ reason: reasonFor(found) });
12800
+ }), {
12801
+ concurrency: 4,
12802
+ discard: true
12803
+ });
12822
12804
  return {
12823
- approved,
12805
+ approved: targets.filter((target) => !Object.hasOwn(refused, rowKey(target.pkg))),
12824
12806
  refused
12825
12807
  };
12826
12808
  });
@@ -13163,8 +13145,7 @@ const initial = (mode, scope, preferences, audits = {}, settingsIssue) => ({
13163
13145
  selected: [],
13164
13146
  operation: machine$1.initialState,
13165
13147
  activity: {},
13166
- outcomes: {},
13167
- actions: {},
13148
+ ran: {},
13168
13149
  installed: {},
13169
13150
  removed: {},
13170
13151
  password: void 0,
@@ -13457,8 +13438,7 @@ const layer = Layer.effect(Session, Effect.gen(function* () {
13457
13438
  const reset = (mode, scope) => SubscriptionRef.update(state, (current) => {
13458
13439
  places.set(`${current.mode}:${current.scope}`, {
13459
13440
  selected: current.selected,
13460
- outcomes: current.outcomes,
13461
- actions: current.actions,
13441
+ ran: current.ran,
13462
13442
  installed: current.installed,
13463
13443
  removed: current.removed
13464
13444
  });
@@ -13529,13 +13509,12 @@ const layer = Layer.effect(Session, Effect.gen(function* () {
13529
13509
  const confirmed = targets.filter((target) => accepted.includes(rowKey(target.pkg)));
13530
13510
  const chosen = vetting ? vet(targets.filter((target) => !accepted.includes(rowKey(target.pkg))), current.mode).pipe(Effect.flatMap(({ approved, refused }) => update((latest) => ({
13531
13511
  ...latest,
13532
- outcomes: {
13533
- ...latest.outcomes,
13534
- ...refused
13535
- },
13536
- actions: {
13537
- ...latest.actions,
13538
- ...Object.fromEntries(Object.keys(refused).map((key) => [key, action]))
13512
+ ran: {
13513
+ ...latest.ran,
13514
+ ...Object.fromEntries(Object.entries(refused).map(([key, outcome]) => [key, {
13515
+ action,
13516
+ outcome
13517
+ }]))
13539
13518
  }
13540
13519
  })).pipe(Effect.as([...confirmed, ...approved])))) : Effect.succeed(targets);
13541
13520
  yield* FiberHandle.run(operation, chosen.pipe(Effect.tap((approved) => update((latest) => ({
@@ -13553,13 +13532,12 @@ const layer = Layer.effect(Session, Effect.gen(function* () {
13553
13532
  } : {
13554
13533
  ...latest,
13555
13534
  activity: without(latest.activity, event.key),
13556
- outcomes: {
13557
- ...latest.outcomes,
13558
- [event.key]: event.outcome
13559
- },
13560
- actions: {
13561
- ...latest.actions,
13562
- [event.key]: action
13535
+ ran: {
13536
+ ...latest.ran,
13537
+ [event.key]: {
13538
+ action,
13539
+ outcome: event.outcome
13540
+ }
13563
13541
  },
13564
13542
  installed: installedAfter(latest.installed, approved, event),
13565
13543
  removed: removedAfter(latest.removed, approved, event),
@@ -13585,10 +13563,10 @@ const layer = Layer.effect(Session, Effect.gen(function* () {
13585
13563
  if ((yield* SubscriptionRef.get(state)).about[key] !== void 0) return;
13586
13564
  yield* about.load(key, registry.lookup(pkg));
13587
13565
  });
13588
- const readTranscript = Effect.fn("Session.readTranscript")(function* (file) {
13589
- const known = (yield* SubscriptionRef.get(state)).transcripts[file];
13566
+ const readTranscript = Effect.fn("Session.readTranscript")(function* (paths) {
13567
+ const known = (yield* SubscriptionRef.get(state)).transcripts[paths.file];
13590
13568
  if (known !== void 0 && known._tag !== "Unavailable") return;
13591
- yield* transcripts.load(file, audits.transcript(file));
13569
+ yield* transcripts.load(paths.file, audits.transcript(paths));
13592
13570
  });
13593
13571
  const readChanges = Effect.fn("Session.readChanges")(function* (pkg) {
13594
13572
  const current = yield* SubscriptionRef.get(state);
@@ -13749,7 +13727,7 @@ const silenceNodeWarningsWhileTuiDraws = () => {
13749
13727
  };
13750
13728
  const loadTui = Effect.suspend(() => {
13751
13729
  silenceNodeWarningsWhileTuiDraws();
13752
- return process.getBuiltinModule("node:ffi") === void 0 ? Effect.die(`upup's TUI needs Node 26.13 or newer; this is Node ${process.versions.node}`) : Effect.promise(async () => await import("./Tui-DV_6wbrQ.mjs"));
13730
+ return process.getBuiltinModule("node:ffi") === void 0 ? Effect.die(`upup's TUI needs Node 26.13 or newer; this is Node ${process.versions.node}`) : Effect.promise(async () => await import("./Tui-Ri-yz-Sx.mjs"));
13753
13731
  });
13754
13732
  const open$1 = (begin, projectDir) => Effect.gen(function* () {
13755
13733
  const { launch } = yield* loadTui;