@miroshnik/elves 2026.1018.0 → 2026.1018.1

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.
@@ -149,8 +149,9 @@ and prints the same steps:
149
149
 
150
150
  1. `est fact <N>` computes this session's part of the actual from its
151
151
  transcript in the container (linking — the branch `<type>/<N>-…` or the
152
- only `#N` in the first prompt, section "The brief") and prints an
153
- `Actual (cloud)` comment
152
+ only `#N` in the first prompt, section "The brief"; the session's PR in
153
+ its own repository, by the clone's `origin`, doesn't break it, #617) and
154
+ prints an `Actual (cloud)` comment
154
155
  with hours, tokens, a link to the session and a part marker — post it to
155
156
  the issue with a GitHub tool. Project fields (`Actual, h`, tokens, cost)
156
157
  are set by the next local session: `est fact <N> --write` or
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miroshnik/elves",
3
- "version": "2026.1018.0",
3
+ "version": "2026.1018.1",
4
4
  "type": "module",
5
5
  "description": "Elves — a development flow with AI agents: shared rules and skills for any agent; install — bunx @miroshnik/elves install -g; the installer and skill scripts — TypeScript under Bun, tests — bun test in tests/",
6
6
  "repository": {
@@ -66,5 +66,5 @@
66
66
  "eslint-plugin-boundaries": "^7.2.0",
67
67
  "knip": "^6.41.0"
68
68
  },
69
- "gitHead": "e6b283ff871be91cc91f33e2a5637893d1f5e538"
69
+ "gitHead": "28a9863fbdf5acfd5706b44b8d3c3ecd87fd39fd"
70
70
  }
@@ -136,7 +136,8 @@ its own part; GraphQL and GitHub Projects are closed there. When a task is
136
136
  closed, `est fact <N>` in the cloud takes this session's transcript in the
137
137
  container (linking — the branch `<type>/<N>-…` or the only "#N" in the first
138
138
  prompt, its background block included — `docs/cloud-sessions.md`, "The brief";
139
- PRs and commits aren't visible from there) and prints the comment
139
+ PRs and commits aren't visible from there: a PR of the clone's `origin`
140
+ repository keeps the link, another repository's breaks it) and prints the comment
140
141
  «Actual (cloud): …» with hours, tokens and a link to the session, with a part
141
142
  marker (`cloud`) (or «Actual unavailable (cloud)», if the transcript isn't linked
142
143
  to the task) — the agent writes it into the issue with a GitHub tool. Other
@@ -2732,6 +2732,8 @@ export function computeFact(repo: FactRepo, number: number, prObjs: PR[], closer
2732
2732
  return head ? (s.wt_branches ?? []).findIndex((bs) => bs.includes(head)) + 1 : 0;
2733
2733
  };
2734
2734
  const anchors: Anchor[] = [];
2735
+ // a PR of our repository that isn't the task's is a foreign anchor: every PR of the repository is known here. Mirror:
2736
+ // cloudOwnPart — the cloud knows none, its own repository's pr-links aren't anchors there
2735
2737
  for (const [ts, pr] of links) {
2736
2738
  const own = ourPrs.has(pr);
2737
2739
  // Claude Code repeats the pr-link of the PR bound to the session even after the merge — that's the app's status,
@@ -3338,15 +3340,26 @@ const cloudSid = (sessionId: string | undefined) => (sessionId ? "session_" + se
3338
3340
  /**
3339
3341
  * This cloud session's part of the actual — from its transcript in the container (the work directory is the git
3340
3342
  * root). Without GitHub: linking is by the `<type>/<N>-…` branch, a "#N" first prompt, a subagent assignment; PRs and
3341
- * commits aren't visible from here.
3343
+ * commits aren't visible from here. The repository — `owner/repo` of the clone's origin, as for a local one (#617):
3344
+ * otherwise a PR of its own repository the session opened looks like another repository's and unlinks the first
3345
+ * prompt's period; no origin — "cloud", any PR with a repository is then another's.
3342
3346
  */
3343
3347
  function cloudOwnPart(number: number, session: string, gap: number): CloudPart | null {
3344
3348
  const top = spawnSync("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8" });
3345
3349
  const cwd = top.status === 0 && top.stdout.trim() ? top.stdout.trim() : process.cwd();
3346
- const sessions = transcriptFilesIn([cwd]).map((f) => parseSessionFile(f)).filter((x) => x.ev.length); // don't discard a routine one: a cloud session is often a single prompt
3350
+ const origin = spawnSync("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
3351
+ const full = (origin.status === 0 && originRepo(origin.stdout)) || "cloud";
3352
+ // The cloud sees no PRs, so a PR of its own repository is neither the task's nor another task's: computeFact would
3353
+ // take it for a foreign anchor and end the first prompt's period when the session opened it — CI, the merge and the
3354
+ // actual after it are the task's too. Mirror: computeFact, the pr-link anchors — locally every PR is known.
3355
+ const ownLink = (r?: string) => !r || r.toLowerCase() === full.toLowerCase();
3356
+ const sessions = transcriptFilesIn([cwd])
3357
+ .map((f) => parseSessionFile(f))
3358
+ .filter((x) => x.ev.length) // don't discard a routine one: a cloud session is often a single prompt
3359
+ .map((s) => ({ ...s, prlinks: s.prlinks.filter(([, , r]) => !ownLink(r)) }));
3347
3360
  if (!sessions.length) return null;
3348
3361
  const stub: FactRepo = {
3349
- full: "cloud",
3362
+ full,
3350
3363
  prs: () => new Map(),
3351
3364
  openPrs: () => ({ at: 0, items: {} }),
3352
3365
  closers: () => ({ oid: {}, pr: {} }),
@@ -125,9 +125,9 @@ removes it for good). Routes follow Docker events — no reload; an unknown host
125
125
  skill update brings the container to the new compose file. Its network, health and the API the dashboard reads
126
126
  addresses from — reference.md, "The proxy".
127
127
 
128
- **Before the rename** the proxy was `ai-dev-proxy`. For one release `proxy up` (and `up`, which starts the proxy)
129
- takes its place: removes its container, which holds the port, and joins its network, so stacks brought up before the
130
- rename keep their addresses; the network isn't removed or created — reference.md, "The proxy before the rename".
128
+ **Before the rename** the proxy was `ai-dev-proxy`. `proxy up` (and `up`, which starts the proxy) takes its place:
129
+ removes its container, which holds the port. Its network the proxy doesn't join: a stack brought up before the rename
130
+ gets its addresses back by `up` again — reference.md, "The proxy before the rename".
131
131
 
132
132
  ## Compose file isolation rules
133
133
 
@@ -146,9 +146,7 @@ each with `task-env check`, a violation is code 1 of `check`.
146
146
 
147
147
  A bind mount of a machine resource — the Docker socket, `/etc/localtime`, a
148
148
  device (a source in `/var/run`, `/run`, `/etc`, `/dev`, `/proc`, `/sys`) — is
149
- not code and is fine; `~/…` is an absolute path. A network named `ai-dev-proxy`
150
- (the proxy before the rename, written by hand) — its own `❌` line: remove it,
151
- `up` adds the proxy's network to the `env.http` services itself.
149
+ not code and is fine; `~/…` is an absolute path.
152
150
 
153
151
  `task-env check` reads `docker compose config`, not the YAML, so every form of writing is covered, and needs no Docker
154
152
  daemon — reference.md, "What the compose file check reads".
@@ -1,10 +1,8 @@
1
1
  # The machine's proxy (the task-env skill, SKILL.md, «The proxy»): one Traefik per machine routes
2
2
  # http://<service>.<N>.<project>.localhost to containers of task stacks by their labels. Started by
3
- # `task-env proxy up`. The name — the Compose project and the network —, the port and the provider's default network
4
- # come from ELVES_PROXY, ELVES_PROXY_PORT and ELVES_PROXY_DEFAULT_NETWORK, which scripts/task-env.ts passes explicitly
5
- # (`composeEnv`): their defaults live only there, so the file has none (`${…:?}`) and doesn't come up without the
6
- # script. The network of the proxy before the rename (`FORMER_PROXY` of the script) the script adds on Compose's stdin
7
- # when it is on the machine — it isn't always, so it isn't here.
3
+ # `task-env proxy up`. The name — the Compose project and the network — and the port come from ELVES_PROXY and
4
+ # ELVES_PROXY_PORT, which scripts/task-env.ts passes explicitly (`composeEnv`): their defaults live only there, so the
5
+ # file has none (`${…:?}`) and doesn't come up without the script.
8
6
  name: ${ELVES_PROXY:?set by task-env.ts}
9
7
  services:
10
8
  traefik:
@@ -17,9 +15,8 @@ services:
17
15
  # a container is published only with traefik.enable=true
18
16
  - --providers.docker.exposedByDefault=false
19
17
  # task containers sit in several networks: a task stack's service names the proxy's one by the label
20
- # traefik.docker.network; a container without the label is reached through this one — the proxy's own, or the
21
- # network of the proxy before the rename while the proxy is in it (its stacks carry no label)
22
- - --providers.docker.network=${ELVES_PROXY_DEFAULT_NETWORK:?set by task-env.ts}
18
+ # traefik.docker.network; a container without the label is reached through the proxy's own
19
+ - --providers.docker.network=${ELVES_PROXY:?set by task-env.ts}
23
20
  # the API — the routes for the dashboard: on the web entry point by the host <name>.localhost (labels below),
24
21
  # without Traefik's own UI
25
22
  - --api=true
@@ -83,7 +83,7 @@ unavailable or (`prune`) a task's state isn't checked; `proxy status` — 0 runn
83
83
  | image | `traefik:v3.7` |
84
84
  | provider | Docker, `exposedByDefault: false` — a container without `traefik.enable=true` isn't published |
85
85
  | network | `elves-proxy`: the proxy reaches a task stack's service through it — the service names it by the label `traefik.docker.network` |
86
- | default network | `providers.docker.network` — for a container without the label: `elves-proxy`, `ai-dev-proxy` while the proxy is in it (below, section "The proxy before the rename") |
86
+ | default network | `providers.docker.network` — for a container without the label: `elves-proxy`, the proxy's own |
87
87
  | entry point | `web` — HTTP on `127.0.0.1:80`; not on all interfaces: task apps aren't visible on the local network |
88
88
  | restart | `unless-stopped` — comes back with Docker; `proxy down` removes it for good |
89
89
  | health | `traefik healthcheck --ping`: `proxy up` returns when the proxy serves |
@@ -110,39 +110,28 @@ drop.
110
110
  ## The proxy before the rename
111
111
 
112
112
  Before the rename of the flow (ai-dev → Elves) the proxy was the Compose
113
- project `ai-dev-proxy` with the network of that name, and stacks brought up
114
- then are in that network, their services without a network label. For one
115
- release `proxy up` — and `up`, which starts a proxy that isn't running —
116
- takes its place:
113
+ project `ai-dev-proxy` with the network of that name. `proxy up` — and `up`,
114
+ which starts a proxy that isn't running — takes its place:
117
115
 
118
116
  - **its container goes** — it holds the port; the new one is created first
119
117
  (the image pulled), so a failed pull leaves the former proxy serving:
120
118
  `proxy ai-dev-proxy: removed — the proxy before the rename; elves-proxy
121
- takes its port, its network ai-dev-proxy stays`;
122
- - **its network stays and the proxy joins it** — an override on Compose's
123
- stdin, as for a task stack: the stacks in it keep their addresses; a
124
- machine without that network — the proxy is in its own only, the network
125
- isn't created; `proxy status` names it: `(network elves-proxy; and
126
- ai-dev-proxy — …)`;
127
- - **the network a container is reached through is deterministic**: a
128
- container without the proxy's network among its own gets from Traefik "the
129
- first" of them in Go's map order — a random one per configuration, and a
130
- stack in its `default` and the proxy's network answers or not by chance. So
131
- a task stack's service names the network by the label (section "Labels of
132
- a task stack's service"), and the provider's default is `ai-dev-proxy`
133
- while the proxy is in it — the stacks before the rename carry no label;
134
- - **`check`** names a network `ai-dev-proxy` written in a project's compose
135
- file (`networks: [default, ai-dev-proxy]`, `external: true`) with its fix:
136
- remove it, `up` adds the proxy's network itself. The project moves by a
137
- task of its own; until then the network is there and its stack comes up;
119
+ takes its port`;
120
+ - **its network isn't joined** — the proxy is in its own network only, even
121
+ with `ai-dev-proxy` on the machine; a stack brought up before the rename,
122
+ left in `ai-dev-proxy` alone, gets its addresses back by `up` again: the
123
+ override puts its HTTP services into `elves-proxy`;
124
+ - **`check`** holds a network `ai-dev-proxy` written in a project's compose
125
+ file (`networks: [default, ai-dev-proxy]`, `external: true`) to the rule of
126
+ any external network; the fix — remove it, `up` adds the proxy's network
127
+ itself;
138
128
  - **the port** — `ELVES_PROXY_PORT`, unset — `AI_DEV_PROXY_PORT` of the
139
129
  profile; **the settings** — `.agents/elves.json`, before `update` moves it
140
130
  — `.agents/ai-dev.json`.
141
131
 
142
132
  The former proxy is `ai-dev-proxy` only for the proxy of the default name;
143
133
  a proxy of another name (a test's) has `<name>-former`, so a test never
144
- touches the machine's proxy. The next release drops the transition: stacks
145
- still in `ai-dev-proxy` alone lose their addresses — `up` again moves them.
134
+ touches the machine's proxy.
146
135
 
147
136
  ## Labels of a task stack's service
148
137
 
@@ -183,9 +172,12 @@ it by hand shares the proxy's network as `external`, and `check` names it.
183
172
  - The network is `external`: the proxy creates it, so `proxy up` goes before
184
173
  the task stack — `up` and `e2e` start it themselves.
185
174
  - `traefik.docker.network` — the network the proxy reaches the service
186
- through: the service is in its own networks too, and without the label
187
- Traefik's pick among them is random (section "The proxy before the
188
- rename").
175
+ through: the service is in its own networks too, and the label names the
176
+ proxy's one, so the pick doesn't depend on the provider's default network.
177
+ Without the label a container outside that default gets from Traefik "the
178
+ first" of its networks in Go's map order — a random one per configuration,
179
+ and a stack in its `default` and the proxy's network would answer or not by
180
+ chance.
189
181
  - Networks: the service's own as Compose normalized them (one without
190
182
  `networks` is in `default`) plus the proxy's — listed networks replace the
191
183
  implicit `default`, and a key with null keeps the project's aliases.
@@ -33,12 +33,10 @@
33
33
  * another. The defaults live only here: the script passes both to the compose file explicitly (`composeEnv`), the file
34
34
  * has none (`${…:?}`); `check -g` of the installer imports `proxyName`.
35
35
  *
36
- * The rename of the flow to Elves renamed the proxy: `FORMER_PROXY` → `PROXY`. For one release `proxy up` takes the
37
- * former proxy's place: removes its container — it holds the port — and joins its network, where stacks brought up
38
- * before the rename stay, so their addresses keep working; `check` names the former network in a project's compose
39
- * file. Which network the proxy reaches a container through is deterministic: a task
40
- * stack's service names it by a label, the provider's default is the former network while the proxy is in it
41
- * (`proxyLabels`, `up`).
36
+ * The rename of the flow to Elves renamed the proxy: `FORMER_PROXY` → `PROXY`. `proxy up` takes the former proxy's
37
+ * place — removes its container, which holds the port — but not its network: the proxy is in its own network only, and
38
+ * `check` holds a hand-written former network to the rule of any external one (#564). A task stack's service names the
39
+ * proxy's network by a label (`proxyLabels`).
42
40
  *
43
41
  * The proxy's API (Traefik `/api/http/routers`) answers on its port by the host `<name>.localhost`: `proxyHosts` reads
44
42
  * the hosts of its routes, `taskAddress` tells a task's address among them — the dashboard shows a task's services by
@@ -98,9 +96,8 @@ Examples:
98
96
 
99
97
  Holds the compose file in <dir> (default — the current directory) to the task isolation rules: no container_name, no
100
98
  fixed host port, volumes and networks without name and external, code by paths relative to the compose file; one ❌ line
101
- per violation; the network of the proxy's former name ai-dev-proxy — remove it, task-env up adds the proxy's network
102
- itself. Code 0 — the rules hold or there is no compose file, 1 — violations, 2 — not checked (no docker compose, the
103
- config doesn't parse).
99
+ per violation. Code 0 — the rules hold or there is no compose file, 1 — violations, 2 — not checked (no docker compose,
100
+ the config doesn't parse).
104
101
 
105
102
  Examples:
106
103
  task-env check
@@ -108,8 +105,8 @@ Examples:
108
105
  "proxy up": `task-env proxy up
109
106
 
110
107
  Starts the machine's proxy (Traefik) or brings it to the skill's compose file and waits until it is healthy; port 80 busy
111
- — another one in ELVES_PROXY_PORT. The proxy before the rename (ai-dev-proxy) — its container removed, its network
112
- joined: stacks brought up before the rename keep their addresses.
108
+ — another one in ELVES_PROXY_PORT. The proxy before the rename (ai-dev-proxy) holds the port — its container removed;
109
+ stacks brought up before the rename get their addresses back by task-env up.
113
110
 
114
111
  Examples:
115
112
  task-env proxy up`,
@@ -158,9 +155,9 @@ export const ENTRY_POINT = "web";
158
155
  export const PROXY = "elves-proxy";
159
156
 
160
157
  /**
161
- * The proxy's default name before the rename of the flow (ai-dev → Elves), read for the transition — one release
162
- * (#545): `proxy up` takes its place, `check` names its network in a project's compose file, `check -g` of the
163
- * installer names a proxy left under this name.
158
+ * The proxy's default name before the rename of the flow (ai-dev → Elves), read for the transition (#545): `proxy up`
159
+ * removes its container, which holds the port, `check -g` of the installer names a proxy left under this name. Its
160
+ * network the proxy no longer joins (#564).
164
161
  */
165
162
  export const FORMER_PROXY = "ai-dev-proxy";
166
163
 
@@ -183,15 +180,13 @@ export const formerProxyName = (env: NodeJS.ProcessEnv = process.env) => {
183
180
  export const proxyPort = (env: NodeJS.ProcessEnv = process.env) => env.ELVES_PROXY_PORT || env.AI_DEV_PROXY_PORT || "80";
184
181
 
185
182
  /**
186
- * The environment of the proxy's compose file: the name, the port and the provider's default network are passed
187
- * explicitly — the file has no defaults of its own (`${…:?}`), they live only here (#396, question 1). The default
188
- * network is the proxy's own unless `up` joins the former proxy's network (`up`); not a setting — the script sets it.
183
+ * The environment of the proxy's compose file: the name and the port are passed explicitly — the file has no defaults
184
+ * of its own (`${…:?}`), they live only here (#396, question 1).
189
185
  */
190
- export const composeEnv = (env: NodeJS.ProcessEnv = process.env, network = proxyName(env)): NodeJS.ProcessEnv => ({
186
+ export const composeEnv = (env: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv => ({
191
187
  ...env,
192
188
  ELVES_PROXY: proxyName(env),
193
189
  ELVES_PROXY_PORT: proxyPort(env),
194
- ELVES_PROXY_DEFAULT_NETWORK: network,
195
190
  });
196
191
 
197
192
  /** The host of the proxy's API: a router of the proxy's own labels on the `web` entry point (proxy/compose.yaml). */
@@ -307,8 +302,7 @@ export interface ProxyRoute {
307
302
  * one service (`--scale`) carry the same labels — Traefik balances between them. The network for reaching the
308
303
  * container — by a label (#545): without it Traefik takes the provider's default network, and when the container isn't
309
304
  * in that one — the first of its networks in Go's map order, a random one per configuration, and a stack in its
310
- * `default` and the proxy's network would answer or not by chance; the default is the former proxy's network while
311
- * the proxy is in it (`up`).
305
+ * `default` and the proxy's network would answer or not by chance.
312
306
  */
313
307
  export function proxyLabels(r: ProxyRoute): Record<string, string> {
314
308
  const id = `${r.stack}-${r.service}`;
@@ -344,17 +338,9 @@ function hint(reason: string, env: NodeJS.ProcessEnv) {
344
338
  return "";
345
339
  }
346
340
 
347
- /**
348
- * `docker compose` of the proxy. `former` — the former proxy's network to join: an override on Compose's stdin (the
349
- * network isn't always on the machine, so it isn't in the file), and it becomes the provider's default network.
350
- */
351
- function compose(args: string[], env: NodeJS.ProcessEnv, former?: string) {
352
- const override = former ? { services: { [SERVICE]: { networks: { proxy: null, former: null } } }, networks: { former: { name: former, external: true } } } : undefined;
353
- return spawnSync("docker", ["compose", "-f", PROXY_FILE, ...(override ? ["-f", "-"] : []), ...args], {
354
- encoding: "utf8",
355
- input: override ? JSON.stringify(override) : undefined,
356
- env: composeEnv(env, former),
357
- });
341
+ /** `docker compose` of the proxy. */
342
+ function compose(args: string[], env: NodeJS.ProcessEnv) {
343
+ return spawnSync("docker", ["compose", "-f", PROXY_FILE, ...args], { encoding: "utf8", env: composeEnv(env) });
358
344
  }
359
345
 
360
346
  /** The proxy containers of a Compose project in any state — a `format` line each; Docker unavailable — the reason. */
@@ -371,31 +357,18 @@ export function proxyRunning(env: NodeJS.ProcessEnv = process.env): boolean | st
371
357
  return typeof states === "string" ? states : states.includes("running");
372
358
  }
373
359
 
374
- /** Whether a network is on the machine; Docker unavailable — the reason. */
375
- function networkExists(name: string, env: NodeJS.ProcessEnv): boolean | string {
376
- const r = spawnSync("docker", ["network", "ls", "--filter", `name=^${name}$`, "--format", "{{.Name}}"], { encoding: "utf8", env, timeout: 10_000 });
377
- if (r.error || r.status !== 0) return why(r);
378
- return r.stdout.split("\n").includes(name);
379
- }
380
-
381
360
  function status(env: NodeJS.ProcessEnv): Result {
382
361
  const name = proxyName(env);
383
- const found = proxyContainers(name, "{{.State}}\t{{.Networks}}", env);
384
- if (typeof found === "string") return { code: 2, out: `proxy ${name}: Docker unavailable — ${found}${hint(found, env)}` };
385
- const running = found.map((l) => l.split("\t")).find(([state]) => state === "running");
362
+ const running = proxyRunning(env);
363
+ if (typeof running === "string") return { code: 2, out: `proxy ${name}: Docker unavailable — ${running}${hint(running, env)}` };
386
364
  if (!running) return { code: 1, out: `proxy ${name}: not running — start: ${START}` };
387
- const former = formerProxyName(env);
388
- const also = running[1]?.split(",").includes(former) ? `; and ${former} — of the proxy before the rename, with the stacks brought up before it` : "";
389
- return { code: 0, out: `proxy ${name}: running — ${addressUrl("<service>.<N>.<project>.localhost", proxyPort(env))} on 127.0.0.1, routes by container labels (network ${name}${also})` };
365
+ return { code: 0, out: `proxy ${name}: running — ${addressUrl("<service>.<N>.<project>.localhost", proxyPort(env))} on 127.0.0.1, routes by container labels (network ${name})` };
390
366
  }
391
367
 
392
368
  /**
393
- * `proxy up`: the proxy brought to the compose file and healthy. The transition from the proxy before the rename
394
- * (#545, one release): its container holds the port — removed once the new one is created (a failed pull leaves the
395
- * former proxy serving); its network stays — stacks brought up before the rename are in it — and the proxy joins it as
396
- * the provider's default network: their services carry no network label, and a container outside the default network
397
- * gets a random one of its networks (`proxyLabels`). No former network on the machine — the proxy is in its own only,
398
- * the former one isn't created.
369
+ * `proxy up`: the proxy brought to the compose file and healthy, in its own network only. The proxy before the rename
370
+ * (#545) holds the port: its container is removed once the new one is created (a failed pull leaves the former proxy
371
+ * serving). Its network the proxy doesn't join (#564): stacks left only in it get their addresses back by `up`.
399
372
  */
400
373
  function up(env: NodeJS.ProcessEnv): Result {
401
374
  const name = proxyName(env);
@@ -404,17 +377,14 @@ function up(env: NodeJS.ProcessEnv): Result {
404
377
  const fail = (reason: string) => ({ code: 2, out: [...out, `proxy ${name}: not started — ${reason}${hint(reason, env)}`].join("\n") });
405
378
  const old = proxyContainers(former, "{{.ID}}", env);
406
379
  if (typeof old === "string") return fail(old);
407
- const joined = networkExists(former, env);
408
- if (typeof joined === "string") return fail(joined);
409
- const network = joined ? former : undefined;
410
380
  if (old.length) {
411
- const created = compose(["up", "--no-start", "--quiet-pull"], env, network);
381
+ const created = compose(["up", "--no-start", "--quiet-pull"], env);
412
382
  if (created.error || created.status !== 0) return fail(why(created));
413
383
  const rm = spawnSync("docker", ["rm", "--force", ...old], { encoding: "utf8", env });
414
384
  if (rm.error || rm.status !== 0) return fail(`the proxy before the rename, ${former}, not removed: ${why(rm)}`);
415
- out.push(`proxy ${former}: removed — the proxy before the rename; ${name} takes its port${joined ? `, its network ${former} stays` : ""}`);
385
+ out.push(`proxy ${former}: removed — the proxy before the rename; ${name} takes its port`);
416
386
  }
417
- const r = compose(["up", "--detach", "--wait", "--quiet-pull"], env, network);
387
+ const r = compose(["up", "--detach", "--wait", "--quiet-pull"], env);
418
388
  if (r.error || r.status !== 0) return fail(why(r));
419
389
  const s = status(env);
420
390
  return { code: s.code, out: [...out, s.out].join("\n") };
@@ -515,12 +485,6 @@ function violations(config: Config, file: string): string[] {
515
485
  }
516
486
  for (const [key, r] of Object.entries(config.networks ?? {})) {
517
487
  const users = services.filter(([, s]) => Object.hasOwn(s.networks ?? {}, key)).map(([n]) => n);
518
- // the proxy's network under its name before the rename (#545): a compose file wrote it by hand, as the example of
519
- // reference.md did; the proxy stays in that network one release (`up`), and a machine without it fails the stack
520
- if (r.name === FORMER_PROXY) {
521
- line(`network ${key} (${list(users)})`, `${FORMER_PROXY} is the machine's proxy before the rename, now ${PROXY}: remove the network from the compose file and its services; task-env up adds the proxy's network to the HTTP services of env.http itself`);
522
- continue;
523
- }
524
488
  shared("network", key, r, users, "task stacks share it and reach each other's services");
525
489
  }
526
490
  return out;