halfcycle 0.12.0 → 0.13.0-rc.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.
package/dist/cli.js CHANGED
@@ -2447,7 +2447,7 @@ if (switchedOn) {
2447
2447
  }
2448
2448
 
2449
2449
  // dist/bin.js
2450
- import { existsSync as existsSync18, statSync as statSync3, writeSync as writeSync4 } from "node:fs";
2450
+ import { existsSync as existsSync19, statSync as statSync3, writeSync as writeSync4 } from "node:fs";
2451
2451
 
2452
2452
  // dist/install.js
2453
2453
  import { execFileSync as execFileSync2 } from "node:child_process";
@@ -2895,13 +2895,39 @@ var leftForOwnerSchema = z6.object({
2895
2895
  }).strict().refine((v) => v.tasks.length + v.waiting.length > 0, {
2896
2896
  message: "is sent only when some work was left for the owner"
2897
2897
  });
2898
+ var TASKS_FILE_PATH = "docs/tasks.md";
2899
+ function isTicket(ref, url) {
2900
+ const issue = /^#([1-9][0-9]{0,9})$/.exec(ref);
2901
+ if (issue === null)
2902
+ return ref === TASKS_FILE_PATH && (url === void 0 || url === null);
2903
+ if (url === void 0 || url === null)
2904
+ return true;
2905
+ const address = /^(.*)\/issues\/([1-9][0-9]{0,9})$/.exec(url);
2906
+ return address !== null && address[2] === issue[1] && GITHUB_REPOSITORY_ADDRESS.test(address[1]) && !looksLikeSecret(url);
2907
+ }
2908
+ var TICKET_MESSAGE = `must be #<number> or ${TASKS_FILE_PATH}, with a link only beside #<number>: https://github.com/<owner>/<repo>/issues/<number>`;
2909
+ var ticketSchema = z6.object({
2910
+ ref: z6.string(),
2911
+ url: z6.string().optional()
2912
+ }).strict().refine((t) => isTicket(t.ref, t.url), { message: TICKET_MESSAGE });
2913
+ var TICKET_TITLE_MAX = 240;
2914
+ var TICKETS_MAX = 500;
2915
+ var ticketEntrySchema = z6.object({
2916
+ title: plainText(TICKET_TITLE_MAX),
2917
+ ref: z6.string(),
2918
+ url: z6.string().nullable()
2919
+ }).strict().refine((t) => isTicket(t.ref, t.url), { message: TICKET_MESSAGE });
2920
+ var ticketsSchema = z6.object({
2921
+ entries: z6.array(ticketEntrySchema).min(1).max(TICKETS_MAX)
2922
+ }).strict();
2898
2923
  var observedCloseSchema = z6.object({
2899
2924
  sequence: z6.number().int().min(1),
2900
2925
  closedAt: utcIso8601,
2901
2926
  closedBy: closedBySchema,
2902
2927
  crossed: crossedSchema,
2903
2928
  seatCoverage: seatCoverageSchema.nullable(),
2904
- leftForOwner: leftForOwnerSchema.optional()
2929
+ leftForOwner: leftForOwnerSchema.optional(),
2930
+ tickets: ticketsSchema.optional()
2905
2931
  }).strict();
2906
2932
  var couldNotRunCheckSchema = z6.object({
2907
2933
  check: plainText(),
@@ -3471,7 +3497,8 @@ var buildTaskSchema = z8.object({
3471
3497
  ships: buildTaskShipsSchema.optional(),
3472
3498
  // The commit or version the task reported when it shipped, exactly as reported, as
3473
3499
  // one word with no spaces. Absent until it has shipped, or when it reported none.
3474
- shipped: z8.string().min(1).max(BUILD_TASK_SHIPPED_MAX).regex(/^[^\s\x00-\x1f\x7f-\x9f]*$/, { message: "must be one word, with no spaces or control characters" }).optional()
3500
+ shipped: z8.string().min(1).max(BUILD_TASK_SHIPPED_MAX).regex(/^[^\s\x00-\x1f\x7f-\x9f]*$/, { message: "must be one word, with no spaces or control characters" }).optional(),
3501
+ ticket: ticketSchema.optional()
3475
3502
  }).strict().superRefine((task, ctx) => {
3476
3503
  if (task.reviews !== void 0 && task.reviews === task.id) {
3477
3504
  ctx.addIssue({
@@ -7322,6 +7349,8 @@ var ownerWaitAddResultSchema = z37.discriminatedUnion("outcome", [
7322
7349
  z37.object({ outcome: z37.literal("refused"), reason: z37.literal("too-many") }).strict()
7323
7350
  ]);
7324
7351
  var ownerWaitUndoableSchema = z37.enum(["yes", "no"]);
7352
+ var OWNER_WAIT_STEP_MAX = 64;
7353
+ var ownerWaitStepSchema = z37.string().max(OWNER_WAIT_STEP_MAX).refine((v) => stepIdSchema.safeParse(v).success, { message: "must be the id of a step, as the method names it" });
7325
7354
  var ownerWaitDeclareRequestSchema = z37.object({
7326
7355
  engagementId: z37.string().min(1),
7327
7356
  item: ownerWaitItemRefSchema,
@@ -7330,7 +7359,8 @@ var ownerWaitDeclareRequestSchema = z37.object({
7330
7359
  task: buildTaskIdSchema.optional(),
7331
7360
  by: agentNameSchema.optional(),
7332
7361
  undoable: ownerWaitUndoableSchema.optional(),
7333
- how: statementText(STATEMENT_MAX).optional()
7362
+ how: statementText(STATEMENT_MAX).optional(),
7363
+ step: ownerWaitStepSchema.optional()
7334
7364
  }).strict().refine((r) => r.undoable === "yes" === (r.how !== void 0), {
7335
7365
  message: "says how to undo it exactly when it can be undone",
7336
7366
  path: ["how"]
@@ -10439,7 +10469,11 @@ HC_LEAD="$HC_LEAD|"$'\\xc2\\xa0|\\xe1\\x9a\\x80|\\xe2\\x80[\\x80-\\x8a\\xa8\\xa9
10439
10469
  # reading is the same set or narrower): a number of agents, the most, faster, a
10440
10470
  # vague amount it answers with an ask for a number, or a free share.
10441
10471
  HC_PACE='((run|use|start|go)( +with| +at)? +)?(up +to +|the +)?(faster|more +agents|(-?[0-9]+(\\.[0-9]+)?|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|twenty) +agents?|(max|maximum|full|top) +(pace|speed)|(max|maximum)( +possible)?( +number +of)? +agents|as +many +agents +as +(possible|you +can)|(lots +of|a +lot +of|loads +of|plenty +of|many|some|several|a +few|a +bunch +of) +agents)( +(at +once|at +a +time|in +parallel|concurrently|simultaneously|together))?|keep +[0-9]{1,3}% +free|use +up +to +[0-9]{1,3}%'
10442
- if ! printf '%s' "$HC_INPUT" | LC_ALL=C grep -Eiq "\\"prompt\\"[[:space:]]*:[[:space:]]*\\"($HC_LEAD)*((approve|send back|accept|question|carry|drop|reject|autonomy|decline) |allow( +[0-9]{1,4})?($HC_LEAD)*\\"|(keep going|stop|stop here|limit it to [0-9]{1,9}(\\.[0-9]{1,2})? (times|million|billion|million tokens|billion tokens)|limit it to [0-9]{1,3}% of the week)($HC_LEAD)*\\"|(please,? +)?($HC_PACE)(,? +please)?[.!]*($HC_LEAD)*\\")"; then
10472
+ # A pace intent anywhere in a short prompt (a short one-line prompt is read from what the
10473
+ # person means; the runner decides the count of words and the line, this only lets a
10474
+ # candidate through, so it is the same set or wider).
10475
+ HC_INTENT='(max|maximum|full|top|fastest)( +[a-z]+){0,2} +(pace|speed)|as +many +agents +as +possible|faster|speed +up|(increase|raise|up)( +the)? +(pace|speed)|more +agents|slower|slow +down|(decrease|lower|reduce)( +the)? +(pace|speed)|fewer +agents|(-?[0-9]+(\\.[0-9]+)?|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|twenty) +agents?'
10476
+ if ! printf '%s' "$HC_INPUT" | LC_ALL=C grep -Eiq "\\"prompt\\"[[:space:]]*:[[:space:]]*\\"($HC_LEAD)*((approve|send back|accept|question|carry|drop|reject|autonomy|decline) |allow( +[0-9]{1,4})?\\.?($HC_LEAD)*\\"|[^\\"]{0,200}($HC_INTENT)[^\\"]{0,200}\\"|(keep going|stop|stop here|limit it to [0-9]{1,9}(\\.[0-9]{1,2})? (times|million|billion|million tokens|billion tokens)|limit it to [0-9]{1,3}% of the week)($HC_LEAD)*\\"|(please,? +)?($HC_PACE)(,? +please)?[.!]*($HC_LEAD)*\\")"; then
10443
10477
  exit 0
10444
10478
  fi
10445
10479
 
@@ -11759,6 +11793,209 @@ function installSelfBuild(options) {
11759
11793
  return { writtenPaths: report.writtenPaths, credentialWritten };
11760
11794
  }
11761
11795
 
11796
+ // dist/worker-copies.js
11797
+ import { spawnSync } from "node:child_process";
11798
+ import { existsSync as existsSync7, lstatSync as lstatSync2, readdirSync, realpathSync, rmdirSync, rmSync as rmSync3 } from "node:fs";
11799
+ import { isAbsolute as isAbsolute3, join as join9, relative as relative2, resolve as resolve2, sep } from "node:path";
11800
+ var WORKER_COPY_CACHE_FOLDERS = ["__pycache__", ".pytest_cache", ".mypy_cache", "node_modules"];
11801
+ var COPIES_SHOWN = ".claude/worktrees/";
11802
+ var CACHE_NAMES = new Set(WORKER_COPY_CACHE_FOLDERS);
11803
+ var NOTHING = { removed: 0, kept: [], message: void 0 };
11804
+ var NOT_TIDIED = {
11805
+ removed: 0,
11806
+ kept: [],
11807
+ message: `[halfcycle] The worker copies under ${COPIES_SHOWN} were not tidied, because git did not answer.`
11808
+ };
11809
+ function tidyWorkerCopies(projectDir) {
11810
+ try {
11811
+ return tidy(projectDir);
11812
+ } catch {
11813
+ return NOT_TIDIED;
11814
+ }
11815
+ }
11816
+ function tidy(projectDir) {
11817
+ let root;
11818
+ try {
11819
+ root = realpathSync(resolve2(projectDir));
11820
+ } catch {
11821
+ return NOTHING;
11822
+ }
11823
+ if (isInsideCopiesFolder(root))
11824
+ return NOTHING;
11825
+ const top = git2(root, ["rev-parse", "--show-toplevel"]);
11826
+ if (!top.ran)
11827
+ return NOT_TIDIED;
11828
+ if (!top.ok)
11829
+ return NOTHING;
11830
+ const listed = git2(root, ["worktree", "list", "--porcelain"]);
11831
+ if (!listed.ok)
11832
+ return NOT_TIDIED;
11833
+ const worktrees = parseWorktrees(listed.out);
11834
+ const main2 = worktrees[0];
11835
+ if (main2 === void 0)
11836
+ return NOT_TIDIED;
11837
+ const mainPath = canonical(main2.path);
11838
+ if (!samePath(mainPath, canonical(top.out.trim())))
11839
+ return NOTHING;
11840
+ const copiesDir = join9(root, ".claude", "worktrees");
11841
+ let removed = 0;
11842
+ const kept = [];
11843
+ for (const tree of worktrees.slice(1)) {
11844
+ if (tree.bare)
11845
+ continue;
11846
+ const at = canonical(tree.path);
11847
+ if (samePath(at, mainPath) || !isInside(copiesDir, at))
11848
+ continue;
11849
+ if (!existsSync7(at))
11850
+ continue;
11851
+ const shown2 = relative2(root, at).split(sep).join("/");
11852
+ if (tree.locked || holdsWork(at)) {
11853
+ kept.push(shown2);
11854
+ continue;
11855
+ }
11856
+ clearCaches(at, at);
11857
+ if (!git2(root, ["worktree", "remove", at]).ok) {
11858
+ kept.push(shown2);
11859
+ continue;
11860
+ }
11861
+ removed += 1;
11862
+ if (tree.branch !== void 0)
11863
+ git2(root, ["branch", "-d", tree.branch]);
11864
+ }
11865
+ return { removed, kept, message: lineFor(removed, kept) };
11866
+ }
11867
+ function lineFor(removed, kept) {
11868
+ if (removed === 0 && kept.length === 0)
11869
+ return void 0;
11870
+ const head = `[halfcycle] Removed ${removed} worker ${removed === 1 ? "copy" : "copies"} under ${COPIES_SHOWN}.`;
11871
+ return kept.length === 0 ? head : `${head} Kept ${kept.length} that may hold work: ${kept.join(", ")}.`;
11872
+ }
11873
+ function git2(cwd, args2) {
11874
+ const r = spawnSync("git", [...args2], {
11875
+ cwd,
11876
+ encoding: "utf-8",
11877
+ stdio: ["ignore", "pipe", "pipe"],
11878
+ windowsHide: true,
11879
+ timeout: 12e4,
11880
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
11881
+ });
11882
+ if (r.error !== void 0)
11883
+ return { ran: false, ok: false, out: "" };
11884
+ return { ran: true, ok: r.status === 0, out: r.stdout ?? "" };
11885
+ }
11886
+ function parseWorktrees(porcelain) {
11887
+ const trees = [];
11888
+ let current;
11889
+ for (const line of porcelain.split(/\r?\n/)) {
11890
+ if (line.startsWith("worktree ")) {
11891
+ current = { path: line.slice("worktree ".length), branch: void 0, locked: false, bare: false };
11892
+ trees.push(current);
11893
+ } else if (current === void 0) {
11894
+ continue;
11895
+ } else if (line.startsWith("branch refs/heads/")) {
11896
+ current.branch = line.slice("branch refs/heads/".length);
11897
+ } else if (line === "locked" || line.startsWith("locked ")) {
11898
+ current.locked = true;
11899
+ } else if (line === "bare") {
11900
+ current.bare = true;
11901
+ }
11902
+ }
11903
+ return trees;
11904
+ }
11905
+ function holdsWork(copyDir) {
11906
+ const status = git2(copyDir, ["status", "--porcelain", "-z", "--untracked-files=all"]);
11907
+ if (!status.ok)
11908
+ return true;
11909
+ const fields = status.out.split("\0");
11910
+ for (let i = 0; i < fields.length; i += 1) {
11911
+ const field = fields[i];
11912
+ if (field === "")
11913
+ continue;
11914
+ const code = field.slice(0, 2);
11915
+ if (code !== "??")
11916
+ return true;
11917
+ const path = field.slice(3);
11918
+ if (!path.split("/").some((segment) => CACHE_NAMES.has(segment)))
11919
+ return true;
11920
+ }
11921
+ return false;
11922
+ }
11923
+ function clearCaches(copyDir, dir) {
11924
+ let names;
11925
+ try {
11926
+ names = readdirSync(dir);
11927
+ } catch {
11928
+ return;
11929
+ }
11930
+ for (const name of names) {
11931
+ if (name === ".git")
11932
+ continue;
11933
+ const at = join9(dir, name);
11934
+ let entry;
11935
+ try {
11936
+ entry = lstatSync2(at);
11937
+ } catch {
11938
+ continue;
11939
+ }
11940
+ if (entry.isSymbolicLink()) {
11941
+ if (CACHE_NAMES.has(name) && !holdsTracked(copyDir, at))
11942
+ removeLink(at);
11943
+ continue;
11944
+ }
11945
+ if (!entry.isDirectory())
11946
+ continue;
11947
+ if (CACHE_NAMES.has(name)) {
11948
+ if (holdsTracked(copyDir, at))
11949
+ continue;
11950
+ try {
11951
+ rmSync3(at, { recursive: true, force: true });
11952
+ } catch {
11953
+ }
11954
+ continue;
11955
+ }
11956
+ clearCaches(copyDir, at);
11957
+ }
11958
+ }
11959
+ function holdsTracked(copyDir, path) {
11960
+ const listed = git2(copyDir, ["ls-files", "-z", "--", relative2(copyDir, path).split(sep).join("/")]);
11961
+ return !listed.ok || listed.out !== "";
11962
+ }
11963
+ function removeLink(at) {
11964
+ try {
11965
+ rmSync3(at, { force: true });
11966
+ } catch {
11967
+ try {
11968
+ rmdirSync(at);
11969
+ } catch {
11970
+ }
11971
+ }
11972
+ }
11973
+ function canonical(path) {
11974
+ try {
11975
+ return realpathSync(path);
11976
+ } catch {
11977
+ return resolve2(path);
11978
+ }
11979
+ }
11980
+ function fold(path) {
11981
+ return process.platform === "win32" ? path.toLowerCase() : path;
11982
+ }
11983
+ function samePath(a, b) {
11984
+ return fold(resolve2(a)) === fold(resolve2(b));
11985
+ }
11986
+ function isInside(parent, child) {
11987
+ const rel = relative2(fold(parent), fold(child));
11988
+ return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute3(rel);
11989
+ }
11990
+ function isInsideCopiesFolder(path) {
11991
+ const segments = fold(path).split(sep);
11992
+ for (let i = 0; i + 2 < segments.length; i += 1) {
11993
+ if (segments[i] === ".claude" && segments[i + 1] === "worktrees")
11994
+ return true;
11995
+ }
11996
+ return false;
11997
+ }
11998
+
11762
11999
  // dist/control-origin.js
11763
12000
  var DEFAULT_CONTROL_ORIGIN = "https://control.halfcycle.ai";
11764
12001
  function resolveControlOrigin(env = process.env) {
@@ -11870,14 +12107,14 @@ async function bindLoopback(env = process.env, engagementId, serviceUrl) {
11870
12107
  else
11871
12108
  pending = callback;
11872
12109
  }, engagementId, serviceUrl));
11873
- const bindProblem = await new Promise((resolve9) => {
12110
+ const bindProblem = await new Promise((resolve10) => {
11874
12111
  const onError = (err) => {
11875
- resolve9(`${err.code ?? "bind failed"} on ${LOOPBACK_REDIRECT_HOST}:${wanted.port}`);
12112
+ resolve10(`${err.code ?? "bind failed"} on ${LOOPBACK_REDIRECT_HOST}:${wanted.port}`);
11876
12113
  };
11877
12114
  server.once("error", onError);
11878
12115
  server.listen({ host: LOOPBACK_REDIRECT_HOST, port: wanted.port }, () => {
11879
12116
  server.removeListener("error", onError);
11880
- resolve9(null);
12117
+ resolve10(null);
11881
12118
  });
11882
12119
  });
11883
12120
  if (bindProblem !== null) {
@@ -11893,9 +12130,9 @@ async function bindLoopback(env = process.env, engagementId, serviceUrl) {
11893
12130
  bound: true,
11894
12131
  target: { port: address.port, state },
11895
12132
  boundAddress: address.address,
11896
- waitForCallback: ({ timeoutMs, onStillWaiting, stillWaitingMs = DEFAULT_STILL_WAITING_MS }) => new Promise((resolve9) => {
12133
+ waitForCallback: ({ timeoutMs, onStillWaiting, stillWaitingMs = DEFAULT_STILL_WAITING_MS }) => new Promise((resolve10) => {
11897
12134
  if (pending !== null) {
11898
- resolve9(pending);
12135
+ resolve10(pending);
11899
12136
  return;
11900
12137
  }
11901
12138
  const startedAt = Date.now();
@@ -11906,7 +12143,7 @@ async function bindLoopback(env = process.env, engagementId, serviceUrl) {
11906
12143
  clearTimeout(timer);
11907
12144
  deliver = null;
11908
12145
  abandon = null;
11909
- resolve9(value);
12146
+ resolve10(value);
11910
12147
  };
11911
12148
  const timer = setTimeout(() => finish(null), Math.max(0, timeoutMs));
11912
12149
  deliver = finish;
@@ -11920,23 +12157,23 @@ async function bindLoopback(env = process.env, engagementId, serviceUrl) {
11920
12157
  };
11921
12158
  }
11922
12159
  function closeServer(server) {
11923
- return new Promise((resolve9) => {
12160
+ return new Promise((resolve10) => {
11924
12161
  server.closeAllConnections();
11925
- server.close(() => resolve9());
12162
+ server.close(() => resolve10());
11926
12163
  });
11927
12164
  }
11928
12165
 
11929
12166
  // dist/sign-in-attempt.js
11930
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync8, realpathSync, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "node:fs";
12167
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync8, realpathSync as realpathSync2, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "node:fs";
11931
12168
  import { homedir as homedir4 } from "node:os";
11932
- import { join as join9 } from "node:path";
12169
+ import { join as join10 } from "node:path";
11933
12170
  var ATTEMPT_FILENAME = "sign-in-attempt.json";
11934
12171
  function attemptPath(home) {
11935
- return join9(home ?? homedir4(), HALFCYCLE_DIR_NAME, ATTEMPT_FILENAME);
12172
+ return join10(home ?? homedir4(), HALFCYCLE_DIR_NAME, ATTEMPT_FILENAME);
11936
12173
  }
11937
12174
  function realFolder(folder2) {
11938
12175
  try {
11939
- return realpathSync(folder2);
12176
+ return realpathSync2(folder2);
11940
12177
  } catch {
11941
12178
  return folder2;
11942
12179
  }
@@ -11944,7 +12181,7 @@ function realFolder(folder2) {
11944
12181
  function recordSignInAttempt(folder2, expiresAt, home) {
11945
12182
  try {
11946
12183
  const path = attemptPath(home);
11947
- mkdirSync6(join9(home ?? homedir4(), HALFCYCLE_DIR_NAME), { recursive: true, mode: 448 });
12184
+ mkdirSync6(join10(home ?? homedir4(), HALFCYCLE_DIR_NAME), { recursive: true, mode: 448 });
11948
12185
  const record2 = { folder: realFolder(folder2), expiresAt };
11949
12186
  writeFileSync6(path, `${JSON.stringify(record2)}
11950
12187
  `, { mode: 384 });
@@ -11954,7 +12191,7 @@ function recordSignInAttempt(folder2, expiresAt, home) {
11954
12191
  }
11955
12192
  function clearSignInAttempt(home) {
11956
12193
  try {
11957
- rmSync3(attemptPath(home), { force: true });
12194
+ rmSync4(attemptPath(home), { force: true });
11958
12195
  } catch {
11959
12196
  return;
11960
12197
  }
@@ -11997,7 +12234,7 @@ function defaultWrite(text) {
11997
12234
  process.stdout.write(text);
11998
12235
  }
11999
12236
  function defaultSleep(ms) {
12000
- return new Promise((resolve9) => setTimeout(resolve9, ms));
12237
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
12001
12238
  }
12002
12239
  function browserOpenCommand(os, url) {
12003
12240
  if (os === "darwin")
@@ -12044,12 +12281,12 @@ async function openBrowser(url, env = process.env, os = platform3(), spawnImpl =
12044
12281
  };
12045
12282
  }
12046
12283
  const { command, args: args2, windowsVerbatimArguments } = browserOpenCommand(os, url);
12047
- return new Promise((resolve9) => {
12284
+ return new Promise((resolve10) => {
12048
12285
  let child;
12049
12286
  try {
12050
12287
  child = spawnImpl(command, args2, { stdio: "ignore", env, windowsVerbatimArguments });
12051
12288
  } catch {
12052
- resolve9({ opened: false, reason: `${command} could not be run` });
12289
+ resolve10({ opened: false, reason: `${command} could not be run` });
12053
12290
  return;
12054
12291
  }
12055
12292
  let settled = false;
@@ -12058,7 +12295,7 @@ async function openBrowser(url, env = process.env, os = platform3(), spawnImpl =
12058
12295
  return;
12059
12296
  settled = true;
12060
12297
  clearTimeout(timer);
12061
- resolve9(attempt2);
12298
+ resolve10(attempt2);
12062
12299
  };
12063
12300
  const timer = setTimeout(() => {
12064
12301
  child.unref();
@@ -12203,17 +12440,17 @@ async function pollBesideLoopback(serviceUrl, started, wake, deps) {
12203
12440
  continue;
12204
12441
  }
12205
12442
  if (sleep === void 0) {
12206
- await new Promise((resolve9) => {
12207
- const timer = setTimeout(resolve9, started.pollIntervalMs);
12443
+ await new Promise((resolve10) => {
12444
+ const timer = setTimeout(resolve10, started.pollIntervalMs);
12208
12445
  interrupt = () => {
12209
12446
  clearTimeout(timer);
12210
- resolve9();
12447
+ resolve10();
12211
12448
  };
12212
12449
  });
12213
12450
  } else {
12214
- await new Promise((resolve9) => {
12215
- interrupt = resolve9;
12216
- void sleep(started.pollIntervalMs).then(resolve9);
12451
+ await new Promise((resolve10) => {
12452
+ interrupt = resolve10;
12453
+ void sleep(started.pollIntervalMs).then(resolve10);
12217
12454
  });
12218
12455
  }
12219
12456
  interrupt = null;
@@ -12819,7 +13056,7 @@ function worthAnotherTry(value) {
12819
13056
  return { tryAgain: true, value };
12820
13057
  }
12821
13058
  function realSleep(ms) {
12822
- return new Promise((resolve9) => setTimeout(resolve9, ms));
13059
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
12823
13060
  }
12824
13061
  async function attemptInTurn(attempt2, options = {}) {
12825
13062
  const timeouts = options.timeouts ?? ATTEMPT_TIMEOUTS_MS;
@@ -13017,9 +13254,9 @@ function wrongAccountVisibility(act) {
13017
13254
  async function askLine(question2) {
13018
13255
  const rl = createInterface({ input: process.stdin, output: process.stdout });
13019
13256
  try {
13020
- return await new Promise((resolve9) => {
13021
- rl.once("close", () => resolve9(""));
13022
- rl.question(question2).then(resolve9, () => resolve9(""));
13257
+ return await new Promise((resolve10) => {
13258
+ rl.once("close", () => resolve10(""));
13259
+ rl.question(question2).then(resolve10, () => resolve10(""));
13023
13260
  });
13024
13261
  } finally {
13025
13262
  rl.close();
@@ -13152,7 +13389,7 @@ async function confirmActingIdentity(serviceUrl, credential, act, deps = {}) {
13152
13389
  }
13153
13390
 
13154
13391
  // dist/project-name.js
13155
- import { basename, resolve as resolve2 } from "node:path";
13392
+ import { basename, resolve as resolve3 } from "node:path";
13156
13393
  var MAX_DERIVED_NAME_LEN = 100;
13157
13394
  var EMPTY_SEGMENTS = /* @__PURE__ */ new Set(["", ".", "..", "/", "\\"]);
13158
13395
  function repositoryNameFromRemote(remote) {
@@ -13172,7 +13409,7 @@ function isLocalRemote(remote) {
13172
13409
  function deriveProjectName(targetRepoRoot) {
13173
13410
  const remote = readRemote(targetRepoRoot);
13174
13411
  const fromRemote = remote && !isLocalRemote(remote) ? repositoryNameFromRemote(remote) : void 0;
13175
- const candidate = fromRemote ?? basename(resolve2(targetRepoRoot));
13412
+ const candidate = fromRemote ?? basename(resolve3(targetRepoRoot));
13176
13413
  const name = candidate.trim();
13177
13414
  if (EMPTY_SEGMENTS.has(name))
13178
13415
  return void 0;
@@ -13261,9 +13498,9 @@ async function joinPinnedEngagement(serviceUrl, engagementId, targetRepo, deps =
13261
13498
  }
13262
13499
 
13263
13500
  // dist/pre-create-checks.js
13264
- import { spawnSync } from "node:child_process";
13265
- import { existsSync as existsSync7, readFileSync as readFileSync9, realpathSync as realpathSync2 } from "node:fs";
13266
- import { delimiter, dirname as dirname5, join as join10 } from "node:path";
13501
+ import { spawnSync as spawnSync2 } from "node:child_process";
13502
+ import { existsSync as existsSync8, readFileSync as readFileSync9, realpathSync as realpathSync3 } from "node:fs";
13503
+ import { delimiter, dirname as dirname5, join as join11 } from "node:path";
13267
13504
  var KEPT_FOLDER_ID_LINE = `Nothing on the Halfcycle service needs undoing, and this folder keeps its project id, so running "${ownBareCommand()}" again picks up where it left off.`;
13268
13505
  var PreCreateRefused = class extends Error {
13269
13506
  constructor(message) {
@@ -13306,14 +13543,14 @@ function refuseSystemFolder(targetRepo, platform6 = process.platform) {
13306
13543
  function refuseInsideProject(targetRepo) {
13307
13544
  let folder2 = realOrLiteral(targetRepo);
13308
13545
  for (let parent = dirname5(folder2); parent !== folder2; folder2 = parent, parent = dirname5(parent)) {
13309
- if (existsSync7(join10(parent, BUNDLE_PIN_REL))) {
13546
+ if (existsSync8(join11(parent, BUNDLE_PIN_REL))) {
13310
13547
  throw new PreCreateRefused(`${targetRepo} is inside the Halfcycle project at ${parent} \u2014 open that folder in your coding agent instead, or run "${ownBareCommand()}" in a folder outside it.`);
13311
13548
  }
13312
13549
  }
13313
13550
  }
13314
13551
  function realOrLiteral(folder2) {
13315
13552
  try {
13316
- return realpathSync2(folder2);
13553
+ return realpathSync3(folder2);
13317
13554
  } catch {
13318
13555
  return folder2;
13319
13556
  }
@@ -13330,22 +13567,22 @@ async function offerGitForWindows() {
13330
13567
  throw new PreCreateRefused(`Git for Windows is needed and is not installed \u2014 install it with "${GIT_INSTALL_COMMAND}" or from ${GIT_DOWNLOAD_PAGE}, then open a new terminal and run "${ownBareCommand()}" again.`);
13331
13568
  }
13332
13569
  function gitBashPath(env) {
13333
- const onPath = (env["PATH"] ?? "").split(delimiter).filter((dir) => dir !== "" && existsSync7(join10(dir, "git.exe"))).map((dir) => join10(dir, "..", "bin", "bash.exe"));
13570
+ const onPath = (env["PATH"] ?? "").split(delimiter).filter((dir) => dir !== "" && existsSync8(join11(dir, "git.exe"))).map((dir) => join11(dir, "..", "bin", "bash.exe"));
13334
13571
  const candidates = [
13335
13572
  env["CLAUDE_CODE_GIT_BASH_PATH"],
13336
13573
  ...onPath,
13337
- env["ProgramFiles"] === void 0 ? void 0 : join10(env["ProgramFiles"], "Git", "bin", "bash.exe")
13574
+ env["ProgramFiles"] === void 0 ? void 0 : join11(env["ProgramFiles"], "Git", "bin", "bash.exe")
13338
13575
  ];
13339
- return candidates.find((file) => file !== void 0 && file !== "" && existsSync7(file));
13576
+ return candidates.find((file) => file !== void 0 && file !== "" && existsSync8(file));
13340
13577
  }
13341
13578
  function hasGitBash(env) {
13342
13579
  return gitBashPath(env) !== void 0;
13343
13580
  }
13344
13581
  function runWingetInstall() {
13345
- return spawnSync("winget", [...GIT_INSTALL_ARGS], { stdio: "inherit" }).status === 0;
13582
+ return spawnSync2("winget", [...GIT_INSTALL_ARGS], { stdio: "inherit" }).status === 0;
13346
13583
  }
13347
13584
  function unreadableProjectRefusal(targetRepo) {
13348
- return `${join10(targetRepo, PROJECT_IDENTITY_REL)} could not be read as a Halfcycle project file \u2014 delete the .halfcycle folder in ${targetRepo} and run "${ownBareCommand()}" again to start a new project here.`;
13585
+ return `${join11(targetRepo, PROJECT_IDENTITY_REL)} could not be read as a Halfcycle project file \u2014 delete the .halfcycle folder in ${targetRepo} and run "${ownBareCommand()}" again to start a new project here.`;
13349
13586
  }
13350
13587
  function cannotWriteRefusal(targetRepo) {
13351
13588
  return `cannot write to ${targetRepo} \u2014 run "${ownBareCommand()}" in a folder you can write to.`;
@@ -13365,7 +13602,7 @@ function claimFolderId(targetRepo) {
13365
13602
  function readFolderId(targetRepo) {
13366
13603
  let folderId;
13367
13604
  try {
13368
- const text = readFileSync9(join10(targetRepo, PROJECT_IDENTITY_REL), "utf-8");
13605
+ const text = readFileSync9(join11(targetRepo, PROJECT_IDENTITY_REL), "utf-8");
13369
13606
  folderId = JSON.parse(text)?.projectId;
13370
13607
  } catch {
13371
13608
  return void 0;
@@ -13382,7 +13619,7 @@ function checkSettingsParse(targetRepo) {
13382
13619
  }
13383
13620
 
13384
13621
  // dist/folder-refusals.js
13385
- import { realpathSync as realpathSync3 } from "node:fs";
13622
+ import { realpathSync as realpathSync4 } from "node:fs";
13386
13623
  import { homedir as homedir5 } from "node:os";
13387
13624
  function folderMissingRefusal(targetRepo) {
13388
13625
  return `halfcycle: no such directory: ${targetRepo}`;
@@ -13402,8 +13639,8 @@ function homeFolderRefusal(targetRepo) {
13402
13639
  }
13403
13640
  function isHomeFolder(dir) {
13404
13641
  try {
13405
- const target = realpathSync3(dir);
13406
- const home = realpathSync3(homedir5());
13642
+ const target = realpathSync4(dir);
13643
+ const home = realpathSync4(homedir5());
13407
13644
  return process.platform === "win32" ? target.toLowerCase() === home.toLowerCase() : target === home;
13408
13645
  } catch {
13409
13646
  return false;
@@ -13411,16 +13648,16 @@ function isHomeFolder(dir) {
13411
13648
  }
13412
13649
 
13413
13650
  // dist/preview.js
13414
- import { accessSync, constants, existsSync as existsSync9, readFileSync as readFileSync12, statSync } from "node:fs";
13415
- import { join as join13 } from "node:path";
13651
+ import { accessSync, constants, existsSync as existsSync10, readFileSync as readFileSync12, statSync } from "node:fs";
13652
+ import { join as join14 } from "node:path";
13416
13653
 
13417
13654
  // dist/halfcycle-monorepo.js
13418
13655
  import { readFileSync as readFileSync10 } from "node:fs";
13419
- import { join as join11 } from "node:path";
13656
+ import { join as join12 } from "node:path";
13420
13657
  var MONOREPO_PACKAGE_NAME = "halfcycle-monorepo";
13421
13658
  function isHalfcycleMonorepo(dir) {
13422
13659
  try {
13423
- const pkg = JSON.parse(readFileSync10(join11(dir, "package.json"), "utf-8"));
13660
+ const pkg = JSON.parse(readFileSync10(join12(dir, "package.json"), "utf-8"));
13424
13661
  return pkg.name === MONOREPO_PACKAGE_NAME;
13425
13662
  } catch {
13426
13663
  return false;
@@ -13432,8 +13669,8 @@ function isSelfBuildInstallForm(engagementIdArg, engagementTypeArg) {
13432
13669
 
13433
13670
  // dist/local-allow.js
13434
13671
  import { execFileSync as execFileSync3 } from "node:child_process";
13435
- import { existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync11, rmSync as rmSync4, writeFileSync as writeFileSync7 } from "node:fs";
13436
- import { dirname as dirname6, join as join12 } from "node:path";
13672
+ import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync11, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "node:fs";
13673
+ import { dirname as dirname6, join as join13 } from "node:path";
13437
13674
  var LOCAL_SETTINGS_REL2 = ".claude/settings.local.json";
13438
13675
  var LOCAL_ALLOW_RECORD_REL = ".halfcycle/local-allow.json";
13439
13676
  var LOCAL_ALLOW_EDIT_MODE = "acceptEdits";
@@ -13441,7 +13678,6 @@ var SCRIPTS = ["test", "build", "lint", "typecheck"];
13441
13678
  var SCRIPT_RUNNERS = ["npm run", "pnpm", "pnpm run", "yarn", "bun run"];
13442
13679
  var HALFCYCLE_TOOLS_RULE = `mcp__${MCP_SERVER_KEY}`;
13443
13680
  var bash = (command) => `Bash(${command}:*)`;
13444
- var CONTEXT_BUDGET_IGNORE_CHECK = "git check-ignore -q .halfcycle/context-budget.json";
13445
13681
  var COMMIT_FILE_LISTING = "git ls-tree -r --name-only";
13446
13682
  var INSTALLED_HOOK_SCRIPTS = Object.keys(OWNED_GENERATED_HEADERS).filter((path) => path.startsWith(".claude/hooks/"));
13447
13683
  var LOCAL_ALLOW_RULES = [
@@ -13477,7 +13713,6 @@ var LOCAL_ALLOW_RULES = [
13477
13713
  bash("git merge"),
13478
13714
  bash("git worktree add"),
13479
13715
  bash("git worktree list"),
13480
- `Bash(${CONTEXT_BUDGET_IGNORE_CHECK})`,
13481
13716
  bash("git pull --ff-only"),
13482
13717
  bash("git pull --rebase"),
13483
13718
  bash("npm install"),
@@ -13489,6 +13724,8 @@ var LOCAL_ALLOW_RULES = [
13489
13724
  bash("yarn add"),
13490
13725
  bash("bun install"),
13491
13726
  bash("bun add"),
13727
+ bash("gh repo view"),
13728
+ bash("gh issue create"),
13492
13729
  bash("go mod tidy"),
13493
13730
  bash("go get"),
13494
13731
  bash("cargo add"),
@@ -13528,7 +13765,7 @@ var LOCAL_ALLOW_RULES = [
13528
13765
  bash("tr"),
13529
13766
  HALFCYCLE_TOOLS_RULE
13530
13767
  ];
13531
- var LOCAL_ALLOW_QUESTION = "Let your coding agent work without asking on this computer? It would run your tests and builds, install packages, run local tools, make and move files, use git locally, run Halfcycle, and edit files in this project without a permission prompt. Pushing, publishing, deploying to production, deleting and changing anything on GitHub still ask. This is saved only on this computer, in .claude/settings.local.json, which is not committed.\nType yes to allow it, or press Enter to keep asking: ";
13768
+ var LOCAL_ALLOW_QUESTION = "Let your coding agent work without asking on this computer? It would run your tests and builds, install packages, run local tools, make and move files, use git locally, run Halfcycle, and edit files in this project without a permission prompt. Opening a ticket on GitHub does not ask either. Pushing, publishing, deploying to production and deleting still ask. This is saved only on this computer, in .claude/settings.local.json, which is not committed.\nType yes to allow it, or press Enter to keep asking: ";
13532
13769
  var LOCAL_ALLOW_ON_LINE = `Your coding agent can now work without asking on this computer. To undo it, run: ${ownCommand()} allow --remove`;
13533
13770
  var LOCAL_ALLOW_OFF_LINE = `Your coding agent will ask before it runs commands. To stop most of those questions on this computer, run: ${ownCommand()} allow`;
13534
13771
  var LOCAL_ALLOW_TRACKED_LINE = "Your .claude/settings.local.json is committed to git, so Halfcycle will not add rules to it. Remove it from git first if you want them.";
@@ -13550,8 +13787,8 @@ function trackedByGit(repo) {
13550
13787
  }
13551
13788
  }
13552
13789
  function readRecord2(repo) {
13553
- const path = join12(repo, LOCAL_ALLOW_RECORD_REL);
13554
- if (!existsSync8(path))
13790
+ const path = join13(repo, LOCAL_ALLOW_RECORD_REL);
13791
+ if (!existsSync9(path))
13555
13792
  return null;
13556
13793
  try {
13557
13794
  const raw = JSON.parse(readFileSync11(path, "utf8"));
@@ -13567,12 +13804,12 @@ function readRecord2(repo) {
13567
13804
  }
13568
13805
  }
13569
13806
  function writeRecord(repo, record2) {
13570
- const path = join12(repo, LOCAL_ALLOW_RECORD_REL);
13807
+ const path = join13(repo, LOCAL_ALLOW_RECORD_REL);
13571
13808
  mkdirSync7(dirname6(path), { recursive: true });
13572
13809
  writeFileSync7(path, JSON.stringify({ rules: record2.rules, defaultMode: record2.defaultMode, before: record2.before }, null, 2) + "\n");
13573
13810
  }
13574
13811
  function holdsLocalAllowRecord(repo) {
13575
- return existsSync8(join12(repo, LOCAL_ALLOW_RECORD_REL));
13812
+ return existsSync9(join13(repo, LOCAL_ALLOW_RECORD_REL));
13576
13813
  }
13577
13814
  function layoutOf(text) {
13578
13815
  const eol = text.includes("\r\n") ? "\r\n" : "\n";
@@ -13587,8 +13824,8 @@ function render(value, like) {
13587
13824
  function writeLocalAllow(repo) {
13588
13825
  if (trackedByGit(repo))
13589
13826
  return { kind: "tracked", line: LOCAL_ALLOW_TRACKED_LINE };
13590
- const path = join12(repo, LOCAL_SETTINGS_REL2);
13591
- const existed = existsSync8(path);
13827
+ const path = join13(repo, LOCAL_SETTINGS_REL2);
13828
+ const existed = existsSync9(path);
13592
13829
  const text = existed ? readFileSync11(path, "utf8") : null;
13593
13830
  let settings = {};
13594
13831
  if (text !== null) {
@@ -13631,14 +13868,14 @@ function writeLocalAllow(repo) {
13631
13868
  }
13632
13869
  function removeLocalAllow(repo) {
13633
13870
  const record2 = readRecord2(repo);
13634
- const recordPath = join12(repo, LOCAL_ALLOW_RECORD_REL);
13871
+ const recordPath = join13(repo, LOCAL_ALLOW_RECORD_REL);
13635
13872
  if (record2 === null) {
13636
- if (existsSync8(recordPath))
13637
- rmSync4(recordPath, { force: true });
13873
+ if (existsSync9(recordPath))
13874
+ rmSync5(recordPath, { force: true });
13638
13875
  return { kind: "nothing", line: LOCAL_ALLOW_NOTHING_TO_REMOVE_LINE };
13639
13876
  }
13640
- const path = join12(repo, LOCAL_SETTINGS_REL2);
13641
- if (existsSync8(path)) {
13877
+ const path = join13(repo, LOCAL_SETTINGS_REL2);
13878
+ if (existsSync9(path)) {
13642
13879
  const text = readFileSync11(path, "utf8");
13643
13880
  let parsed = null;
13644
13881
  try {
@@ -13673,13 +13910,13 @@ function removeLocalAllow(repo) {
13673
13910
  if (record2.before !== null && original !== null && sameJson(next, original)) {
13674
13911
  writeFileSync7(path, record2.before);
13675
13912
  } else if (record2.before === null && Object.keys(next).length === 0) {
13676
- rmSync4(path, { force: true });
13913
+ rmSync5(path, { force: true });
13677
13914
  } else {
13678
13915
  writeFileSync7(path, render(next, text));
13679
13916
  }
13680
13917
  }
13681
13918
  }
13682
- rmSync4(recordPath, { force: true });
13919
+ rmSync5(recordPath, { force: true });
13683
13920
  return { kind: "removed", line: LOCAL_ALLOW_REMOVED_LINE };
13684
13921
  }
13685
13922
  function parseObject(text) {
@@ -13693,21 +13930,21 @@ function parseObject(text) {
13693
13930
  }
13694
13931
  }
13695
13932
  function sameJson(a, b) {
13696
- return JSON.stringify(canonical(a)) === JSON.stringify(canonical(b));
13933
+ return JSON.stringify(canonical2(a)) === JSON.stringify(canonical2(b));
13697
13934
  }
13698
- function canonical(value) {
13935
+ function canonical2(value) {
13699
13936
  if (Array.isArray(value))
13700
- return value.map(canonical);
13937
+ return value.map(canonical2);
13701
13938
  if (isObject2(value)) {
13702
- return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonical(value[key])]));
13939
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonical2(value[key])]));
13703
13940
  }
13704
13941
  return value;
13705
13942
  }
13706
13943
  function shouldAskLocalAllow(repo, interactive) {
13707
13944
  if (!interactive || holdsLocalAllowRecord(repo))
13708
13945
  return false;
13709
- const path = join12(repo, LOCAL_SETTINGS_REL2);
13710
- if (!existsSync8(path))
13946
+ const path = join13(repo, LOCAL_SETTINGS_REL2);
13947
+ if (!existsSync9(path))
13711
13948
  return true;
13712
13949
  const parsed = parseObject(readFileSync11(path, "utf8"));
13713
13950
  const allow = isObject2(parsed?.["permissions"]) ? parsed["permissions"]["allow"] : void 0;
@@ -13728,8 +13965,8 @@ function localAllowClosingLine(repo, said) {
13728
13965
  var ENABLED_SERVERS_KEY = "enabledMcpjsonServers";
13729
13966
  var DISABLED_SERVERS_KEY = "disabledMcpjsonServers";
13730
13967
  function registersOwnServer(repo) {
13731
- const path = join12(repo, MCP_REGISTRATION_REL);
13732
- const registration = parseObject(existsSync8(path) ? readFileSync11(path, "utf8") : null);
13968
+ const path = join13(repo, MCP_REGISTRATION_REL);
13969
+ const registration = parseObject(existsSync9(path) ? readFileSync11(path, "utf8") : null);
13733
13970
  const servers = registration?.["mcpServers"];
13734
13971
  return isObject2(servers) && isObject2(servers[MCP_SERVER_KEY]);
13735
13972
  }
@@ -13739,15 +13976,15 @@ function stringsIn(value) {
13739
13976
  function approveOwnServer(repo) {
13740
13977
  const plan = planOwnServerApproval(repo, registersOwnServer(repo));
13741
13978
  if (plan.after !== null) {
13742
- const path = join12(repo, LOCAL_SETTINGS_REL2);
13979
+ const path = join13(repo, LOCAL_SETTINGS_REL2);
13743
13980
  mkdirSync7(dirname6(path), { recursive: true });
13744
13981
  writeFileSync7(path, plan.after);
13745
13982
  }
13746
13983
  return plan.outcome;
13747
13984
  }
13748
13985
  function planOwnServerApproval(repo, registered) {
13749
- const path = join12(repo, LOCAL_SETTINGS_REL2);
13750
- const text = existsSync8(path) ? readFileSync11(path, "utf8") : null;
13986
+ const path = join13(repo, LOCAL_SETTINGS_REL2);
13987
+ const text = existsSync9(path) ? readFileSync11(path, "utf8") : null;
13751
13988
  const unchanged = (outcome) => ({ outcome, before: text, after: null });
13752
13989
  if (!registered)
13753
13990
  return unchanged("no-server");
@@ -13783,8 +14020,8 @@ function planOwnServerApproval(repo, registered) {
13783
14020
  return { outcome: "approved", before: text, after: after === text ? null : after };
13784
14021
  }
13785
14022
  function removeOwnServerApproval(repo) {
13786
- const path = join12(repo, LOCAL_SETTINGS_REL2);
13787
- if (!existsSync8(path) || trackedByGit(repo))
14023
+ const path = join13(repo, LOCAL_SETTINGS_REL2);
14024
+ if (!existsSync9(path) || trackedByGit(repo))
13788
14025
  return false;
13789
14026
  const text = readFileSync11(path, "utf8");
13790
14027
  const settings = parseObject(text);
@@ -13818,7 +14055,7 @@ function removeOwnServerApproval(repo) {
13818
14055
  if (!changed)
13819
14056
  return false;
13820
14057
  if (Object.keys(next).length === 0)
13821
- rmSync4(path, { force: true });
14058
+ rmSync5(path, { force: true });
13822
14059
  else
13823
14060
  writeFileSync7(path, render(next, text));
13824
14061
  return true;
@@ -13879,8 +14116,8 @@ function ownServerRow(targetRepo, plan) {
13879
14116
  }
13880
14117
  }
13881
14118
  function claudeMdLine(targetRepo) {
13882
- const path = join13(targetRepo, CLAUDE_MD_REL);
13883
- if (!existsSync9(path))
14119
+ const path = join14(targetRepo, CLAUDE_MD_REL);
14120
+ if (!existsSync10(path))
13884
14121
  return void 0;
13885
14122
  let text;
13886
14123
  try {
@@ -13891,7 +14128,7 @@ function claudeMdLine(targetRepo) {
13891
14128
  return text.includes(AGENTS_IMPORT_LINE) ? WORDS.claudeMdAlreadyPoints : WORDS.claudeMdWillAdd;
13892
14129
  }
13893
14130
  function previewRefusal(targetRepo, options) {
13894
- if (!existsSync9(targetRepo))
14131
+ if (!existsSync10(targetRepo))
13895
14132
  return folderMissingRefusal(targetRepo);
13896
14133
  if (!statSync(targetRepo).isDirectory())
13897
14134
  return folderIsFileRefusal(targetRepo);
@@ -13912,7 +14149,7 @@ function previewRefusal(targetRepo, options) {
13912
14149
  } catch {
13913
14150
  return cannotWriteRefusal(targetRepo);
13914
14151
  }
13915
- if (existsSync9(join13(targetRepo, PROJECT_IDENTITY_REL)) && readFolderId(targetRepo) === void 0) {
14152
+ if (existsSync10(join14(targetRepo, PROJECT_IDENTITY_REL)) && readFolderId(targetRepo) === void 0) {
13916
14153
  return unreadableProjectRefusal(targetRepo);
13917
14154
  }
13918
14155
  try {
@@ -13972,10 +14209,10 @@ function renderPreview(plan, missingGitForWindows, claudeMdWords, ownServer) {
13972
14209
  }
13973
14210
 
13974
14211
  // dist/doctor.js
13975
- import { spawnSync as spawnSync2 } from "node:child_process";
13976
- import { existsSync as existsSync10, lstatSync as lstatSync2, readFileSync as readFileSync13, realpathSync as realpathSync4, statSync as statSync2 } from "node:fs";
14212
+ import { spawnSync as spawnSync3 } from "node:child_process";
14213
+ import { existsSync as existsSync11, lstatSync as lstatSync3, readFileSync as readFileSync13, realpathSync as realpathSync5, statSync as statSync2 } from "node:fs";
13977
14214
  import { homedir as homedir6 } from "node:os";
13978
- import { join as join14 } from "node:path";
14215
+ import { join as join15 } from "node:path";
13979
14216
  var GUARD_RUNNER_REL2 = ".claude/hooks/guard-runner.sh";
13980
14217
  var CONNECTION_VERB = "check-connection";
13981
14218
  var WRAPPER_TIMEOUT_MS = 3e4;
@@ -14014,7 +14251,7 @@ function runDoctor(root, options) {
14014
14251
  };
14015
14252
  }
14016
14253
  function setUpLine(root, upToDate) {
14017
- if (!existsSync10(join14(root, BUNDLE_PIN_REL))) {
14254
+ if (!existsSync11(join15(root, BUNDLE_PIN_REL))) {
14018
14255
  return {
14019
14256
  state: "no",
14020
14257
  hooksMissing: false,
@@ -14028,9 +14265,9 @@ function setUpLine(root, upToDate) {
14028
14265
  const missing = [];
14029
14266
  if (!pinNamesProject(root))
14030
14267
  missing.push("Halfcycle's project record");
14031
- if (!existsSync10(join14(root, INSTALL_MANIFEST_REL)))
14268
+ if (!existsSync11(join15(root, INSTALL_MANIFEST_REL)))
14032
14269
  missing.push("the install record");
14033
- if (!existsSync10(join14(root, VENDORED_BIN_REL)))
14270
+ if (!existsSync11(join15(root, VENDORED_BIN_REL)))
14034
14271
  missing.push("Halfcycle's check program");
14035
14272
  if (!mcpNamesServer(root))
14036
14273
  missing.push("Halfcycle's server entry");
@@ -14056,14 +14293,14 @@ function pinNamesProject(root) {
14056
14293
  }
14057
14294
  }
14058
14295
  function mcpNamesServer(root) {
14059
- const parsed = readJson(join14(root, MCP_REGISTRATION_REL));
14296
+ const parsed = readJson(join15(root, MCP_REGISTRATION_REL));
14060
14297
  if (!isObject3(parsed) || !isObject3(parsed["mcpServers"]))
14061
14298
  return false;
14062
14299
  const entry = parsed["mcpServers"][MCP_SERVER_KEY];
14063
14300
  return isObject3(entry) && typeof entry["url"] === "string";
14064
14301
  }
14065
14302
  function settingsRegisterEveryCommand(root) {
14066
- const parsed = readJson(join14(root, SETTINGS_REL));
14303
+ const parsed = readJson(join15(root, SETTINGS_REL));
14067
14304
  if (!isObject3(parsed) || !isObject3(parsed["hooks"]))
14068
14305
  return false;
14069
14306
  const registered = /* @__PURE__ */ new Set();
@@ -14094,10 +14331,10 @@ function startWrapper(root, options) {
14094
14331
  }
14095
14332
  shell = gitBash;
14096
14333
  }
14097
- const wrapper = join14(root, GUARD_RUNNER_REL2);
14098
- if (!existsSync10(wrapper))
14334
+ const wrapper = join15(root, GUARD_RUNNER_REL2);
14335
+ if (!existsSync11(wrapper))
14099
14336
  return failed(repairSentence());
14100
- const run = spawnSync2(shell, [wrapper, CONNECTION_VERB], {
14337
+ const run = spawnSync3(shell, [wrapper, CONNECTION_VERB], {
14101
14338
  cwd: root,
14102
14339
  env: { ...options.env, CLAUDE_PROJECT_DIR: root },
14103
14340
  encoding: "utf-8",
@@ -14122,7 +14359,7 @@ function startWrapper(root, options) {
14122
14359
  }
14123
14360
  function settingsDisablingHooks(root) {
14124
14361
  for (const rel of [SETTINGS_REL, LOCAL_SETTINGS_REL]) {
14125
- const parsed = readJson(join14(root, rel));
14362
+ const parsed = readJson(join15(root, rel));
14126
14363
  if (isObject3(parsed) && parsed["disableAllHooks"] === true)
14127
14364
  return rel;
14128
14365
  }
@@ -14212,8 +14449,8 @@ function folderTrustLine(root, options) {
14212
14449
  if (!mcpNamesServer(root))
14213
14450
  return void 0;
14214
14451
  const configFolder = options.env["CLAUDE_CONFIG_DIR"];
14215
- const stateFile = join14(configFolder !== void 0 && configFolder !== "" ? configFolder : options.home ?? homedir6(), CLAUDE_STATE_FILENAME);
14216
- const trust = trustOfWorkingFolder(root, stateFile, options.platform, { lstatSync: lstatSync2, readFileSync: readFileSync13, realpathSync: realpathSync4 });
14452
+ const stateFile = join15(configFolder !== void 0 && configFolder !== "" ? configFolder : options.home ?? homedir6(), CLAUDE_STATE_FILENAME);
14453
+ const trust = trustOfWorkingFolder(root, stateFile, options.platform, { lstatSync: lstatSync3, readFileSync: readFileSync13, realpathSync: realpathSync5 });
14217
14454
  return trust === "not-trusted" ? { ok: true, text: `Folder trust: ${FOLDER_NOT_TRUSTED_SENTENCE}` } : void 0;
14218
14455
  }
14219
14456
  function signInCodeLine(root, running, now, options) {
@@ -14287,7 +14524,7 @@ var CODEX_EVENT_SNAKE = {
14287
14524
  };
14288
14525
  function codexHomeDir(options) {
14289
14526
  const codexHome = options.env["CODEX_HOME"];
14290
- return codexHome !== void 0 && codexHome !== "" ? codexHome : join14(homedir6(), ".codex");
14527
+ return codexHome !== void 0 && codexHome !== "" ? codexHome : join15(homedir6(), ".codex");
14291
14528
  }
14292
14529
  function isDirectory(path) {
14293
14530
  try {
@@ -14297,8 +14534,8 @@ function isDirectory(path) {
14297
14534
  }
14298
14535
  }
14299
14536
  function codexLine(root, options) {
14300
- const hooksPath = join14(root, CODEX_HOOKS_REL2);
14301
- if (!existsSync10(hooksPath))
14537
+ const hooksPath = join15(root, CODEX_HOOKS_REL2);
14538
+ if (!existsSync11(hooksPath))
14302
14539
  return void 0;
14303
14540
  if (!isDirectory(codexHomeDir(options)))
14304
14541
  return void 0;
@@ -14340,7 +14577,7 @@ function codexEntryPositions(hooksPath) {
14340
14577
  return positions;
14341
14578
  }
14342
14579
  function codexApprovalStatus(root, positions, options) {
14343
- const configPath = join14(codexHomeDir(options), "config.toml");
14580
+ const configPath = join15(codexHomeDir(options), "config.toml");
14344
14581
  let text;
14345
14582
  try {
14346
14583
  text = readFileSync13(configPath, "utf-8");
@@ -14349,7 +14586,7 @@ function codexApprovalStatus(root, positions, options) {
14349
14586
  }
14350
14587
  let hooksPath;
14351
14588
  try {
14352
- hooksPath = join14(realpathSync4(root), CODEX_HOOKS_REL2);
14589
+ hooksPath = join15(realpathSync5(root), CODEX_HOOKS_REL2);
14353
14590
  } catch {
14354
14591
  return "not-approved";
14355
14592
  }
@@ -14379,7 +14616,7 @@ function naturalList(items) {
14379
14616
  return `${items.slice(0, -1).join(", ")} and ${items[items.length - 1]}`;
14380
14617
  }
14381
14618
  function readJson(path) {
14382
- if (!existsSync10(path))
14619
+ if (!existsSync11(path))
14383
14620
  return void 0;
14384
14621
  try {
14385
14622
  return JSON.parse(readFileSync13(path, "utf-8"));
@@ -14393,8 +14630,8 @@ function isObject3(value) {
14393
14630
 
14394
14631
  // dist/claude-version-probe.js
14395
14632
  import { execFileSync as execFileSync4 } from "node:child_process";
14396
- import { existsSync as existsSync11, realpathSync as realpathSync5 } from "node:fs";
14397
- import { delimiter as delimiter2, join as join15 } from "node:path";
14633
+ import { existsSync as existsSync12, realpathSync as realpathSync6 } from "node:fs";
14634
+ import { delimiter as delimiter2, join as join16 } from "node:path";
14398
14635
  function execVersionCommand(command, timeoutMs, deps) {
14399
14636
  const platform6 = deps.platform ?? process.platform;
14400
14637
  const exec = deps.exec ?? execFileSync4;
@@ -14437,10 +14674,10 @@ var INSTALL_METHOD_RULES = [
14437
14674
  function probeClaudeCodeInstallMethod(deps = {}) {
14438
14675
  const env = deps.env ?? process.env;
14439
14676
  const platform6 = deps.platform ?? process.platform;
14440
- const exists = deps.exists ?? existsSync11;
14677
+ const exists = deps.exists ?? existsSync12;
14441
14678
  const realpath = deps.realpath ?? ((file) => {
14442
14679
  try {
14443
- return realpathSync5(file);
14680
+ return realpathSync6(file);
14444
14681
  } catch {
14445
14682
  return file;
14446
14683
  }
@@ -14449,7 +14686,7 @@ function probeClaudeCodeInstallMethod(deps = {}) {
14449
14686
  const dirs = (env["PATH"] ?? "").split(delimiter2).filter((dir) => dir !== "");
14450
14687
  for (const dir of dirs) {
14451
14688
  for (const name of names) {
14452
- const file = join15(dir, name);
14689
+ const file = join16(dir, name);
14453
14690
  if (!exists(file))
14454
14691
  continue;
14455
14692
  const real = realpath(file);
@@ -14484,8 +14721,8 @@ function codexInstallLine(codex) {
14484
14721
  }
14485
14722
 
14486
14723
  // dist/build-record/write.js
14487
- import { existsSync as existsSync12, mkdirSync as mkdirSync8, rmSync as rmSync6 } from "node:fs";
14488
- import { basename as basename2, join as join16, resolve as resolve3, relative as relative2, isAbsolute as isAbsolute3 } from "node:path";
14724
+ import { existsSync as existsSync13, mkdirSync as mkdirSync8, rmSync as rmSync7 } from "node:fs";
14725
+ import { basename as basename2, join as join17, resolve as resolve4, relative as relative3, isAbsolute as isAbsolute4 } from "node:path";
14489
14726
 
14490
14727
  // dist/build-record/render-v2.js
14491
14728
  var HIDDEN = /[\p{Cc}\p{Zl}\p{Zp}\p{Default_Ignorable_Code_Point}]/u;
@@ -14831,6 +15068,20 @@ function leftForYouLines(left, p) {
14831
15068
  }
14832
15069
  return lines;
14833
15070
  }
15071
+ var TASKS_FILE_LINK = "../tasks.md";
15072
+ function ticketLines(tickets, p) {
15073
+ if (tickets === void 0)
15074
+ return [];
15075
+ const lines = ["- Tickets:"];
15076
+ for (const entry of tickets.entries) {
15077
+ lines.push(` - ${entry.ref} ${p.text(entry.title)}`);
15078
+ if (entry.url !== null)
15079
+ lines.push(` - Link: [${entry.ref}](${entry.url})`);
15080
+ else if (entry.ref === TASKS_FILE_PATH)
15081
+ lines.push(` - Link: [${entry.ref}](${TASKS_FILE_LINK})`);
15082
+ }
15083
+ return lines;
15084
+ }
14834
15085
  function shownBy(steps) {
14835
15086
  const named2 = steps.filter((s) => s.name !== void 0).length;
14836
15087
  if (named2 === 0)
@@ -14904,6 +15155,7 @@ function renderClose(heading, observed, attested, checks, p, closingRound = fals
14904
15155
  `- Signed in as: ${signedInAs(observed.closedBy, p)}`,
14905
15156
  ...outstanding(observed.crossed, p),
14906
15157
  ...leftForYouLines(observed.leftForOwner, p),
15158
+ ...ticketLines(observed.tickets, p),
14907
15159
  machines(observed.seatCoverage, checks)
14908
15160
  ].join("\n"),
14909
15161
  STATED_LABEL,
@@ -15076,7 +15328,7 @@ function renderBuildRecordV2Markdown(record2) {
15076
15328
  }
15077
15329
 
15078
15330
  // dist/build-record/pair-io.js
15079
- import { closeSync, openSync, readFileSync as readFileSync14, renameSync as renameSync2, rmSync as rmSync5, writeSync as writeSync2 } from "node:fs";
15331
+ import { closeSync, openSync, readFileSync as readFileSync14, renameSync as renameSync2, rmSync as rmSync6, writeSync as writeSync2 } from "node:fs";
15080
15332
  function createExclusiveOnDisk(path, data) {
15081
15333
  const bytes = typeof data === "string" ? Buffer.from(data, "utf-8") : data;
15082
15334
  const fd = openSync(path, "wx");
@@ -15087,7 +15339,7 @@ function createExclusiveOnDisk(path, data) {
15087
15339
  }
15088
15340
  } catch (err) {
15089
15341
  closeSync(fd);
15090
- rmSync5(path, { force: true });
15342
+ rmSync6(path, { force: true });
15091
15343
  throw err;
15092
15344
  }
15093
15345
  closeSync(fd);
@@ -15121,28 +15373,28 @@ function createPairExclusive(io, jsonPath, jsonText, mdPath, mdText) {
15121
15373
  return "created";
15122
15374
  } catch (err) {
15123
15375
  if (createdJson)
15124
- rmSync5(jsonPath, { force: true });
15376
+ rmSync6(jsonPath, { force: true });
15125
15377
  if (!isEexist(err))
15126
15378
  throw err;
15127
15379
  return "exists";
15128
15380
  }
15129
15381
  }
15130
15382
  function replaceOne(io, tmp, finalPath, text) {
15131
- rmSync5(tmp, { force: true });
15383
+ rmSync6(tmp, { force: true });
15132
15384
  try {
15133
15385
  io.createExclusive(tmp, text);
15134
15386
  io.rename(tmp, finalPath);
15135
15387
  } catch (err) {
15136
- rmSync5(tmp, { force: true });
15388
+ rmSync6(tmp, { force: true });
15137
15389
  throw err;
15138
15390
  }
15139
15391
  }
15140
15392
 
15141
15393
  // dist/build-record/write.js
15142
- var BUILD_RECORD_DIR = join16("docs", "build-records");
15394
+ var BUILD_RECORD_DIR = join17("docs", "build-records");
15143
15395
  function assertInsideOutDir(outDir, candidate, identity) {
15144
- const rel = relative2(outDir, resolve3(candidate));
15145
- const isPlainFilename = rel !== "" && rel !== ".." && !isAbsolute3(rel) && rel === basename2(rel);
15396
+ const rel = relative3(outDir, resolve4(candidate));
15397
+ const isPlainFilename = rel !== "" && rel !== ".." && !isAbsolute4(rel) && rel === basename2(rel);
15146
15398
  if (!isPlainFilename) {
15147
15399
  throw new InvalidPhaseIdentityError(identity, BUILD_RECORD_DIR);
15148
15400
  }
@@ -15175,10 +15427,10 @@ function writeRecordPair(repoRoot, phase, rawRecord, record2, io = NODE_RECORD_P
15175
15427
  if (!isValidPhaseIdentity(phase)) {
15176
15428
  throw new InvalidPhaseIdentityError(phase, BUILD_RECORD_DIR);
15177
15429
  }
15178
- const outDir = resolve3(repoRoot, BUILD_RECORD_DIR);
15430
+ const outDir = resolve4(repoRoot, BUILD_RECORD_DIR);
15179
15431
  const segment = renderPhaseSegment(phase);
15180
- const jsonPath = join16(outDir, `${segment}.json`);
15181
- const mdPath = join16(outDir, `${segment}.md`);
15432
+ const jsonPath = join17(outDir, `${segment}.json`);
15433
+ const mdPath = join17(outDir, `${segment}.md`);
15182
15434
  const jsonTmp = `${jsonPath}.tmp`;
15183
15435
  const mdTmp = `${mdPath}.tmp`;
15184
15436
  for (const candidate of [jsonPath, mdPath, jsonTmp, mdTmp]) {
@@ -15192,7 +15444,7 @@ function writeRecordPair(repoRoot, phase, rawRecord, record2, io = NODE_RECORD_P
15192
15444
  }
15193
15445
  const existingJson = readIfPresent(jsonPath);
15194
15446
  if (existingJson === null) {
15195
- return { kind: "in-the-way", path: existsSync12(jsonPath) ? jsonPath : mdPath };
15447
+ return { kind: "in-the-way", path: existsSync13(jsonPath) ? jsonPath : mdPath };
15196
15448
  }
15197
15449
  if (existingJson === jsonText) {
15198
15450
  replaceOne(io, mdTmp, mdPath, mdText);
@@ -15208,7 +15460,7 @@ function writeRecordPair(repoRoot, phase, rawRecord, record2, io = NODE_RECORD_P
15208
15460
  try {
15209
15461
  replaceOne(io, jsonTmp, jsonPath, existingJson);
15210
15462
  } catch {
15211
- rmSync6(jsonPath, { force: true });
15463
+ rmSync7(jsonPath, { force: true });
15212
15464
  }
15213
15465
  throw err;
15214
15466
  }
@@ -15216,24 +15468,24 @@ function writeRecordPair(repoRoot, phase, rawRecord, record2, io = NODE_RECORD_P
15216
15468
  }
15217
15469
 
15218
15470
  // dist/build-record/obtain.js
15219
- import { relative as relative5 } from "node:path";
15471
+ import { relative as relative6 } from "node:path";
15220
15472
 
15221
15473
  // dist/open-phase.js
15222
15474
  import { readFileSync as readFileSync20 } from "node:fs";
15223
- import { join as join24 } from "node:path";
15475
+ import { join as join25 } from "node:path";
15224
15476
 
15225
15477
  // dist/close-phase.js
15226
15478
  import { execFile, execFileSync as execFileSync6 } from "node:child_process";
15227
15479
  import { readFileSync as readFileSync19 } from "node:fs";
15228
- import { join as join23 } from "node:path";
15480
+ import { join as join24 } from "node:path";
15229
15481
 
15230
15482
  // dist/plan.js
15231
- import { existsSync as existsSync14, readFileSync as readFileSync18 } from "node:fs";
15232
- import { join as join22 } from "node:path";
15483
+ import { existsSync as existsSync15, readFileSync as readFileSync18 } from "node:fs";
15484
+ import { join as join23 } from "node:path";
15233
15485
 
15234
15486
  // dist/draw/write.js
15235
- import { closeSync as closeSync2, lstatSync as lstatSync3, mkdirSync as mkdirSync9, openSync as openSync2, readFileSync as readFileSync15, readSync, realpathSync as realpathSync6, rmSync as rmSync7 } from "node:fs";
15236
- import { dirname as dirname7, isAbsolute as isAbsolute4, join as join17, relative as relative3, resolve as resolve4, sep } from "node:path";
15487
+ import { closeSync as closeSync2, lstatSync as lstatSync4, mkdirSync as mkdirSync9, openSync as openSync2, readFileSync as readFileSync15, readSync, realpathSync as realpathSync7, rmSync as rmSync8 } from "node:fs";
15488
+ import { dirname as dirname7, isAbsolute as isAbsolute5, join as join18, relative as relative4, resolve as resolve5, sep as sep2 } from "node:path";
15237
15489
  var DOCS_DIR = "docs";
15238
15490
  var DIAGRAMS_DIR = "diagrams";
15239
15491
  var DIAGRAM_DIR_REL = `${DOCS_DIR}/${DIAGRAMS_DIR}`;
@@ -15288,10 +15540,10 @@ function embedImageLink(base, doc) {
15288
15540
  return `${up}${DIAGRAMS_DIR}/${markdownLinkName(embedImageName(base))}`;
15289
15541
  }
15290
15542
  function diagramPaths(repoRoot, base) {
15291
- const root = resolve4(repoRoot);
15292
- const dir = join17(root, DOCS_DIR, DIAGRAMS_DIR);
15543
+ const root = resolve5(repoRoot);
15544
+ const dir = join18(root, DOCS_DIR, DIAGRAMS_DIR);
15293
15545
  const names = fileNames(base);
15294
- const file = Object.fromEntries(DIAGRAM_FILES.map((which) => [which, join17(dir, names[which])]));
15546
+ const file = Object.fromEntries(DIAGRAM_FILES.map((which) => [which, join18(dir, names[which])]));
15295
15547
  return { repoRoot: root, dir, file };
15296
15548
  }
15297
15549
  function diagramRel(base, which) {
@@ -15308,19 +15560,19 @@ function spokenList(items) {
15308
15560
  }
15309
15561
  function lstatOrNull2(path) {
15310
15562
  try {
15311
- return lstatSync3(path);
15563
+ return lstatSync4(path);
15312
15564
  } catch {
15313
15565
  return null;
15314
15566
  }
15315
15567
  }
15316
- function isInside(outer, inner) {
15317
- const rel = relative3(outer, inner);
15318
- return rel === "" || !isAbsolute4(rel) && rel !== ".." && !rel.startsWith(`..${sep}`);
15568
+ function isInside2(outer, inner) {
15569
+ const rel = relative4(outer, inner);
15570
+ return rel === "" || !isAbsolute5(rel) && rel !== ".." && !rel.startsWith(`..${sep2}`);
15319
15571
  }
15320
15572
  function checkDiagramPlace(paths) {
15321
15573
  let root;
15322
15574
  try {
15323
- root = realpathSync6(paths.repoRoot);
15575
+ root = realpathSync7(paths.repoRoot);
15324
15576
  } catch {
15325
15577
  return { ok: false, why: `the folder ${paths.repoRoot} could not be opened. Check the path, then run this again.` };
15326
15578
  }
@@ -15333,14 +15585,14 @@ function checkDiagramPlace(paths) {
15333
15585
  }
15334
15586
  let real;
15335
15587
  try {
15336
- real = realpathSync6(ancestor);
15588
+ real = realpathSync7(ancestor);
15337
15589
  } catch {
15338
15590
  return {
15339
15591
  ok: false,
15340
15592
  why: `${DIAGRAM_DIR_REL} leads to a place that does not exist. Remove the link in the way, then run this again.`
15341
15593
  };
15342
15594
  }
15343
- if (!isInside(root, real)) {
15595
+ if (!isInside2(root, real)) {
15344
15596
  return {
15345
15597
  ok: false,
15346
15598
  why: `${DIAGRAM_DIR_REL} leads outside this repository, so nothing was written. Remove the link in the way, then run this again.`
@@ -15348,12 +15600,12 @@ function checkDiagramPlace(paths) {
15348
15600
  }
15349
15601
  let isFolder = false;
15350
15602
  try {
15351
- isFolder = lstatSync3(real).isDirectory();
15603
+ isFolder = lstatSync4(real).isDirectory();
15352
15604
  } catch {
15353
15605
  isFolder = false;
15354
15606
  }
15355
15607
  if (!isFolder) {
15356
- const name = relative3(root, real).split("\\").join("/");
15608
+ const name = relative4(root, real).split("\\").join("/");
15357
15609
  return {
15358
15610
  ok: false,
15359
15611
  why: `${name === "" ? "this repository" : name} is not a folder, so ${DIAGRAM_DIR_REL} cannot be made. Move it, then run this again.`
@@ -15468,7 +15720,7 @@ function checkPictures(drawn, base) {
15468
15720
  }
15469
15721
  function removeQuietly(path) {
15470
15722
  try {
15471
- rmSync7(path, { force: true });
15723
+ rmSync8(path, { force: true });
15472
15724
  } catch {
15473
15725
  return;
15474
15726
  }
@@ -15534,10 +15786,10 @@ function restore(io, paths, renamed, existing) {
15534
15786
  const tmp = tempPath(paths, which);
15535
15787
  try {
15536
15788
  if (previous === null) {
15537
- rmSync7(finalPath, { force: true });
15789
+ rmSync8(finalPath, { force: true });
15538
15790
  continue;
15539
15791
  }
15540
- rmSync7(tmp, { force: true });
15792
+ rmSync8(tmp, { force: true });
15541
15793
  io.createExclusive(tmp, previous);
15542
15794
  io.rename(tmp, finalPath);
15543
15795
  } catch {
@@ -15564,7 +15816,7 @@ function isMissingRoute(method, status, body) {
15564
15816
  }
15565
15817
 
15566
15818
  // dist/change.js
15567
- import { join as join20 } from "node:path";
15819
+ import { join as join21 } from "node:path";
15568
15820
 
15569
15821
  // dist/cli-contract.js
15570
15822
  var CLI_VERBS = ["install", "check-drift", "build-record", "open-phase", "close-phase", "system", "draw", "plan", "review", "check-tasks", "check-criteria", "check-report", "ready", "uninstall", "change", "pace", "route", "prove", "preview", "doctor", "restore", "request-shapes", "allow", "size", "regression", "crew"];
@@ -15809,11 +16061,11 @@ function placeholderRefusal(verb, tokens) {
15809
16061
  // dist/system-report.js
15810
16062
  import { execFileSync as execFileSync5 } from "node:child_process";
15811
16063
  import { readFileSync as readFileSync16 } from "node:fs";
15812
- import { join as join18 } from "node:path";
16064
+ import { join as join19 } from "node:path";
15813
16065
  function readDescriptionFile(repoRoot) {
15814
16066
  let text;
15815
16067
  try {
15816
- text = readFileSync16(join18(repoRoot, SYSTEM_DESCRIPTION_REL), "utf-8");
16068
+ text = readFileSync16(join19(repoRoot, SYSTEM_DESCRIPTION_REL), "utf-8");
15817
16069
  } catch (err) {
15818
16070
  const code = err.code;
15819
16071
  return code === "ENOENT" || code === "ENOTDIR" ? { kind: "missing" } : { kind: "unreadable" };
@@ -16037,8 +16289,8 @@ async function runSystem(repoRoot, deps) {
16037
16289
  }
16038
16290
 
16039
16291
  // dist/build-record/write-change.js
16040
- import { existsSync as existsSync13, mkdirSync as mkdirSync10 } from "node:fs";
16041
- import { basename as basename3, isAbsolute as isAbsolute5, join as join19, relative as relative4, resolve as resolve5 } from "node:path";
16292
+ import { existsSync as existsSync14, mkdirSync as mkdirSync10 } from "node:fs";
16293
+ import { basename as basename3, isAbsolute as isAbsolute6, join as join20, relative as relative5, resolve as resolve6 } from "node:path";
16042
16294
 
16043
16295
  // dist/build-record/render-change.js
16044
16296
  function counted2(n, one, many) {
@@ -16183,7 +16435,7 @@ function renderChangeRecordMarkdown(record2) {
16183
16435
  }
16184
16436
 
16185
16437
  // dist/build-record/write-change.js
16186
- var CHANGE_RECORD_DIR = join19("docs", "build-records", "changes");
16438
+ var CHANGE_RECORD_DIR = join20("docs", "build-records", "changes");
16187
16439
  var InvalidChangeIdError = class extends Error {
16188
16440
  id;
16189
16441
  constructor(id) {
@@ -16193,8 +16445,8 @@ var InvalidChangeIdError = class extends Error {
16193
16445
  }
16194
16446
  };
16195
16447
  function assertInsideChangeDir(outDir, candidate, id) {
16196
- const rel = relative4(outDir, resolve5(candidate));
16197
- const isPlainFilename = rel !== "" && rel !== ".." && !isAbsolute5(rel) && rel === basename3(rel);
16448
+ const rel = relative5(outDir, resolve6(candidate));
16449
+ const isPlainFilename = rel !== "" && rel !== ".." && !isAbsolute6(rel) && rel === basename3(rel);
16198
16450
  if (!isPlainFilename)
16199
16451
  throw new InvalidChangeIdError(id);
16200
16452
  }
@@ -16202,9 +16454,9 @@ function writeChangeRecordPair(repoRoot, sortingId, rawRecord, record2, io = NOD
16202
16454
  const checked = sortingIdSchema.safeParse(sortingId);
16203
16455
  if (!checked.success)
16204
16456
  throw new InvalidChangeIdError(sortingId);
16205
- const outDir = resolve5(repoRoot, CHANGE_RECORD_DIR);
16206
- const jsonPath = join19(outDir, `${checked.data}.json`);
16207
- const mdPath = join19(outDir, `${checked.data}.md`);
16457
+ const outDir = resolve6(repoRoot, CHANGE_RECORD_DIR);
16458
+ const jsonPath = join20(outDir, `${checked.data}.json`);
16459
+ const mdPath = join20(outDir, `${checked.data}.md`);
16208
16460
  const mdTmp = `${mdPath}.tmp`;
16209
16461
  for (const candidate of [jsonPath, mdPath, mdTmp]) {
16210
16462
  assertInsideChangeDir(outDir, candidate, sortingId);
@@ -16217,7 +16469,7 @@ function writeChangeRecordPair(repoRoot, sortingId, rawRecord, record2, io = NOD
16217
16469
  }
16218
16470
  const existingJson = readIfPresent(jsonPath);
16219
16471
  if (existingJson === null) {
16220
- return { kind: "in-the-way", path: existsSync13(jsonPath) ? jsonPath : mdPath };
16472
+ return { kind: "in-the-way", path: existsSync14(jsonPath) ? jsonPath : mdPath };
16221
16473
  }
16222
16474
  if (existingJson !== jsonText)
16223
16475
  return { kind: "in-the-way", path: jsonPath };
@@ -16480,7 +16732,7 @@ function writePairLines(deps, repoRoot, sortingId, found) {
16480
16732
  };
16481
16733
  }
16482
16734
  return {
16483
- stdout: `[halfcycle] Its record is in ${join20(CHANGE_RECORD_DIR, `${sortingId}.md`)}.
16735
+ stdout: `[halfcycle] Its record is in ${join21(CHANGE_RECORD_DIR, `${sortingId}.md`)}.
16484
16736
  `,
16485
16737
  stderr: "",
16486
16738
  ok: true
@@ -16721,12 +16973,12 @@ function runChange(repoRoot, args2, deps) {
16721
16973
  // dist/shown-record.js
16722
16974
  import { chmodSync as chmodSync3, mkdirSync as mkdirSync11, readFileSync as readFileSync17, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync8 } from "node:fs";
16723
16975
  import { platform as platform4 } from "node:os";
16724
- import { join as join21 } from "node:path";
16976
+ import { join as join22 } from "node:path";
16725
16977
  function updateShownRecord(engagementId, update, home) {
16726
16978
  let temp = null;
16727
16979
  try {
16728
16980
  const dir = engagementStateDir(engagementId, home);
16729
- const path = join21(dir, SHOWN_PLANS_FILENAME);
16981
+ const path = join22(dir, SHOWN_PLANS_FILENAME);
16730
16982
  let current;
16731
16983
  try {
16732
16984
  current = parseShownRecord(readFileSync17(path, "utf-8"));
@@ -16736,7 +16988,7 @@ function updateShownRecord(engagementId, update, home) {
16736
16988
  const next = update(current);
16737
16989
  const body = serialiseShownRecord(next);
16738
16990
  mkdirSync11(dir, { recursive: true, mode: 448 });
16739
- temp = join21(dir, `.${SHOWN_PLANS_FILENAME}.${process.pid}.tmp`);
16991
+ temp = join22(dir, `.${SHOWN_PLANS_FILENAME}.${process.pid}.tmp`);
16740
16992
  writeFileSync8(temp, body, { mode: 384 });
16741
16993
  if (platform4() !== "win32")
16742
16994
  chmodSync3(temp, 384);
@@ -16813,7 +17065,7 @@ function readAndCheckPlan(repoRoot, phase, description, verb) {
16813
17065
  const fileRel = planFileRel(phase);
16814
17066
  let text;
16815
17067
  try {
16816
- text = readFileSync18(join22(repoRoot, fileRel), "utf-8");
17068
+ text = readFileSync18(join23(repoRoot, fileRel), "utf-8");
16817
17069
  } catch (err) {
16818
17070
  const code = err.code;
16819
17071
  if (code === "ENOENT" || code === "ENOTDIR") {
@@ -16969,7 +17221,7 @@ function removalOwedLines(verb, parts) {
16969
17221
  function redrawLine(repoRoot, phase) {
16970
17222
  let exists;
16971
17223
  try {
16972
- exists = existsSync14(join22(repoRoot, DIAGRAM_DIR_REL, embedImageName(phaseDiagramBase(phase))));
17224
+ exists = existsSync15(join23(repoRoot, DIAGRAM_DIR_REL, embedImageName(phaseDiagramBase(phase))));
16973
17225
  } catch {
16974
17226
  return null;
16975
17227
  }
@@ -17553,7 +17805,7 @@ function readObserveLine(stdout) {
17553
17805
  function runRunnerChild(binary, args2, credential, repoRoot, readLine, fallback) {
17554
17806
  if (binary instanceof Error)
17555
17807
  return Promise.resolve(fallback);
17556
- return new Promise((resolve9) => {
17808
+ return new Promise((resolve10) => {
17557
17809
  try {
17558
17810
  execFile(process.execPath, [binary, ...args2], {
17559
17811
  cwd: repoRoot,
@@ -17563,10 +17815,10 @@ function runRunnerChild(binary, args2, credential, repoRoot, readLine, fallback)
17563
17815
  windowsHide: true
17564
17816
  }, (err, stdout) => {
17565
17817
  const stopped = err !== null && err.killed === true;
17566
- resolve9(stopped || typeof stdout !== "string" ? fallback : readLine(stdout));
17818
+ resolve10(stopped || typeof stdout !== "string" ? fallback : readLine(stdout));
17567
17819
  });
17568
17820
  } catch {
17569
- resolve9(fallback);
17821
+ resolve10(fallback);
17570
17822
  }
17571
17823
  });
17572
17824
  }
@@ -17929,7 +18181,7 @@ var PHASE_MODELS_FILENAME = "phase-models.json";
17929
18181
  var PACING_FOLDER = "pacing";
17930
18182
  function pacingFile(engagementId, name, home) {
17931
18183
  try {
17932
- return join23(engagementStateDir(engagementId, home), PACING_FOLDER, name);
18184
+ return join24(engagementStateDir(engagementId, home), PACING_FOLDER, name);
17933
18185
  } catch {
17934
18186
  return null;
17935
18187
  }
@@ -18101,7 +18353,7 @@ function readRepoCredential(repoRoot, env = process.env, home) {
18101
18353
  if (pinned !== null)
18102
18354
  assertEngagementId(pinned);
18103
18355
  const fromStore = pinned === null ? null : readEngagementEnv(pinned, home);
18104
- const looked = pinned === null ? `${join24(repoRoot, ".halfcycle", "bundle.json")} (which names no engagement)` : engagementEnvPath(pinned, home);
18356
+ const looked = pinned === null ? `${join25(repoRoot, ".halfcycle", "bundle.json")} (which names no engagement)` : engagementEnvPath(pinned, home);
18105
18357
  const values = {};
18106
18358
  for (const key of OPEN_PHASE_ENV_KEYS) {
18107
18359
  const value = (env[key] ?? fromStore?.[key] ?? "").trim();
@@ -18118,7 +18370,7 @@ function readRepoCredential(repoRoot, env = process.env, home) {
18118
18370
  }
18119
18371
  function readPinnedEngagementId(repoRoot) {
18120
18372
  try {
18121
- const pin = JSON.parse(readFileSync20(join24(repoRoot, ".halfcycle", "bundle.json"), "utf-8"));
18373
+ const pin = JSON.parse(readFileSync20(join25(repoRoot, ".halfcycle", "bundle.json"), "utf-8"));
18122
18374
  const id = pin[PIN_ENGAGEMENT_ID_FIELD];
18123
18375
  return typeof id === "string" && id !== "" ? id : null;
18124
18376
  } catch {
@@ -18268,12 +18520,12 @@ async function obtainBuildRecord(credential, repoRoot, phase, io = NODE_RECORD_P
18268
18520
  };
18269
18521
  }
18270
18522
  if (result.kind === "in-the-way") {
18271
- return { kind: "exists", path: relative5(repoRoot, result.path) };
18523
+ return { kind: "exists", path: relative6(repoRoot, result.path) };
18272
18524
  }
18273
18525
  return {
18274
18526
  kind: "written",
18275
- jsonPath: relative5(repoRoot, result.jsonPath),
18276
- mdPath: relative5(repoRoot, result.mdPath),
18527
+ jsonPath: relative6(repoRoot, result.jsonPath),
18528
+ mdPath: relative6(repoRoot, result.mdPath),
18277
18529
  headline: latestCloseHeadline(answer.record),
18278
18530
  stampFailure: clearStampFor(credential.engagementId, phase, home)
18279
18531
  };
@@ -18606,7 +18858,7 @@ async function writeDrawOutcome(repoRoot, deps, outcome, target, warnings, canno
18606
18858
 
18607
18859
  // dist/check-tasks.js
18608
18860
  import { readFileSync as readFileSync21 } from "node:fs";
18609
- import { join as join25, resolve as resolve6 } from "node:path";
18861
+ import { join as join26, resolve as resolve7 } from "node:path";
18610
18862
  var CHECK_TASKS_USAGE = `${ownCommand()} check-tasks <phase> [--with-task-list] [--repo <root>]`;
18611
18863
  function parseCheckTasksArgs(argv) {
18612
18864
  const positionals = [];
@@ -18647,7 +18899,7 @@ function issuePlace(rel, path) {
18647
18899
  function readJsonFile(repoRoot, rel, parse) {
18648
18900
  let text;
18649
18901
  try {
18650
- text = readFileSync21(resolve6(repoRoot, rel), "utf-8");
18902
+ text = readFileSync21(resolve7(repoRoot, rel), "utf-8");
18651
18903
  } catch (err) {
18652
18904
  const code = err.code;
18653
18905
  if (code === "ENOENT" || code === "ENOTDIR")
@@ -18705,7 +18957,7 @@ function readPhasePlanParts(repoRoot, phase, description) {
18705
18957
  const file = planFileRel(phase);
18706
18958
  let text;
18707
18959
  try {
18708
- text = readFileSync21(join25(repoRoot, file), "utf-8");
18960
+ text = readFileSync21(join26(repoRoot, file), "utf-8");
18709
18961
  } catch (err) {
18710
18962
  const code = err.code;
18711
18963
  if (code === "ENOENT" || code === "ENOTDIR")
@@ -18738,7 +18990,7 @@ function readPlanSpecFiles(repoRoot, plan) {
18738
18990
  const rel = `${SPEC_FOLDER}/${slug}.md`;
18739
18991
  let text;
18740
18992
  try {
18741
- text = readFileSync21(join25(repoRoot, rel), "utf-8");
18993
+ text = readFileSync21(join26(repoRoot, rel), "utf-8");
18742
18994
  } catch (err) {
18743
18995
  const code = err.code;
18744
18996
  if (code === "ENOENT" || code === "ENOTDIR") {
@@ -18994,8 +19246,8 @@ function runCheckReport(source, head, deps = {}) {
18994
19246
 
18995
19247
  // dist/setup-committed.js
18996
19248
  import { execFileSync as execFileSync7 } from "node:child_process";
18997
- import { existsSync as existsSync15 } from "node:fs";
18998
- import { join as join26 } from "node:path";
19249
+ import { existsSync as existsSync16 } from "node:fs";
19250
+ import { join as join27 } from "node:path";
18999
19251
  var SETUP_COMMIT_MESSAGE = "Add Halfcycle setup files";
19000
19252
  var SETUP_UNCOMMITTED_LINES = {
19001
19253
  intro: () => "Halfcycle's setup files are not committed, so the copies your agents build in would not have Halfcycle. Nothing was started. Commit them, then run this again:",
@@ -19006,7 +19258,7 @@ function shellWord(path) {
19006
19258
  return /^[A-Za-z0-9._/@+=-]+$/.test(path) ? path : `'${path.replace(/'/g, `'\\''`)}'`;
19007
19259
  }
19008
19260
  function uncommittedSetupFiles(repoRoot) {
19009
- if (!existsSync15(join26(repoRoot, INSTALL_MANIFEST_REL)))
19261
+ if (!existsSync16(join27(repoRoot, INSTALL_MANIFEST_REL)))
19010
19262
  return [];
19011
19263
  const manifest = readPreviousManifest(repoRoot);
19012
19264
  if (manifest === null)
@@ -19250,13 +19502,13 @@ function proveCouldNotStart(err) {
19250
19502
  function runProveChild(binary, credential, repoRoot, words, deps = {}) {
19251
19503
  const out = deps.stdout ?? process.stdout;
19252
19504
  const err = deps.stderr ?? process.stderr;
19253
- return new Promise((resolve9) => {
19505
+ return new Promise((resolve10) => {
19254
19506
  let settled = false;
19255
19507
  const finish = (code) => {
19256
19508
  if (settled)
19257
19509
  return;
19258
19510
  settled = true;
19259
- resolve9(code);
19511
+ resolve10(code);
19260
19512
  };
19261
19513
  let child;
19262
19514
  try {
@@ -19282,9 +19534,9 @@ function runProveChild(binary, credential, repoRoot, words, deps = {}) {
19282
19534
  }
19283
19535
 
19284
19536
  // dist/uninstall.js
19285
- import { existsSync as existsSync16, lstatSync as lstatSync4, readFileSync as readFileSync23, readdirSync, rmSync as rmSync8, rmdirSync, writeFileSync as writeFileSync9 } from "node:fs";
19537
+ import { existsSync as existsSync17, lstatSync as lstatSync5, readFileSync as readFileSync23, readdirSync as readdirSync2, rmSync as rmSync9, rmdirSync as rmdirSync2, writeFileSync as writeFileSync9 } from "node:fs";
19286
19538
  import { homedir as homedir7 } from "node:os";
19287
- import { dirname as dirname8, join as join27, relative as relative6, sep as sep2 } from "node:path";
19539
+ import { dirname as dirname8, join as join28, relative as relative7, sep as sep3 } from "node:path";
19288
19540
  var REMOVE_CREDENTIAL_FLAG = "--remove-credential";
19289
19541
  function parseUninstallArgs(args2) {
19290
19542
  let removeCredential = false;
@@ -19301,7 +19553,7 @@ function parseUninstallArgs(args2) {
19301
19553
  }
19302
19554
  function lexists(path) {
19303
19555
  try {
19304
- lstatSync4(path);
19556
+ lstatSync5(path);
19305
19557
  return true;
19306
19558
  } catch {
19307
19559
  return false;
@@ -19319,7 +19571,7 @@ function readMergedJson(targetRepo, rule) {
19319
19571
  const { rel } = rule;
19320
19572
  if (symlinkOnPath(targetRepo, rel))
19321
19573
  return { state: "symlink" };
19322
- const path = join27(targetRepo, rel);
19574
+ const path = join28(targetRepo, rel);
19323
19575
  if (!lexists(path))
19324
19576
  return { state: "absent" };
19325
19577
  let text;
@@ -19342,8 +19594,8 @@ function readMergedJson(targetRepo, rule) {
19342
19594
  }
19343
19595
  function validate(options, rules) {
19344
19596
  const { targetRepo, removeCredential, home } = options;
19345
- const manifestPath = join27(targetRepo, INSTALL_MANIFEST_REL);
19346
- const pinPath = join27(targetRepo, BUNDLE_PIN_REL);
19597
+ const manifestPath = join28(targetRepo, INSTALL_MANIFEST_REL);
19598
+ const pinPath = join28(targetRepo, BUNDLE_PIN_REL);
19347
19599
  if (symlinkOnPath(targetRepo, INSTALL_MANIFEST_REL) || symlinkOnPath(targetRepo, BUNDLE_PIN_REL)) {
19348
19600
  throw new Refusal(`.halfcycle, or a file Halfcycle keeps in it, is a symbolic link here, so nothing was followed and nothing was removed.`);
19349
19601
  }
@@ -19384,7 +19636,7 @@ function validate(options, rules) {
19384
19636
  throw new Refusal(`the project id in ${BUNDLE_PIN_REL} is not a valid Halfcycle project id, so it cannot name a folder on this machine. Nothing was removed, here or under ~/.halfcycle.`);
19385
19637
  }
19386
19638
  if (engagementId !== void 0) {
19387
- const expectedParent = join27(home ?? homedir7(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME);
19639
+ const expectedParent = join28(home ?? homedir7(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME);
19388
19640
  if (dirname8(engagementStateDir(engagementId, home)) !== expectedParent) {
19389
19641
  throw new Refusal(`the project id in ${BUNDLE_PIN_REL} does not name a folder under ~/.halfcycle/engagements. Nothing was removed.`);
19390
19642
  }
@@ -19410,10 +19662,10 @@ function mergedFileRules(writeSet) {
19410
19662
  function writeBack(targetRepo, rel, current, next, lists, name) {
19411
19663
  if (next === current)
19412
19664
  return;
19413
- const path = join27(targetRepo, rel);
19665
+ const path = join28(targetRepo, rel);
19414
19666
  try {
19415
19667
  if (next === null) {
19416
- rmSync8(path);
19668
+ rmSync9(path);
19417
19669
  name(lists.removed, rel, rel);
19418
19670
  } else {
19419
19671
  writeFileSync9(path, next, "utf-8");
@@ -19427,7 +19679,7 @@ function statusLineClaimedFromTree(targetRepo, writeSet) {
19427
19679
  try {
19428
19680
  if (symlinkOnPath(targetRepo, SETTINGS_REL))
19429
19681
  return {};
19430
- const value = JSON.parse(readFileSync23(join27(targetRepo, SETTINGS_REL), "utf-8"));
19682
+ const value = JSON.parse(readFileSync23(join28(targetRepo, SETTINGS_REL), "utf-8"));
19431
19683
  if (!isPlainObject4(value) || !("statusLine" in value))
19432
19684
  return {};
19433
19685
  const hash = canonicalSha256(value["statusLine"]);
@@ -19440,7 +19692,7 @@ function attributionClaimedFromTree(targetRepo, writeSet) {
19440
19692
  try {
19441
19693
  if (symlinkOnPath(targetRepo, SETTINGS_REL))
19442
19694
  return {};
19443
- const value = JSON.parse(readFileSync23(join27(targetRepo, SETTINGS_REL), "utf-8"));
19695
+ const value = JSON.parse(readFileSync23(join28(targetRepo, SETTINGS_REL), "utf-8"));
19444
19696
  if (!isPlainObject4(value) || !("attribution" in value))
19445
19697
  return {};
19446
19698
  const hash = canonicalSha256(value["attribution"]);
@@ -19454,7 +19706,7 @@ function noRecord(targetRepo, writeSet) {
19454
19706
  const claim = (rel, header, lines) => {
19455
19707
  let text;
19456
19708
  try {
19457
- text = symlinkOnPath(targetRepo, rel) ? "" : readFileSync23(join27(targetRepo, rel), "utf-8");
19709
+ text = symlinkOnPath(targetRepo, rel) ? "" : readFileSync23(join28(targetRepo, rel), "utf-8");
19458
19710
  } catch {
19459
19711
  text = "";
19460
19712
  }
@@ -19700,7 +19952,7 @@ function lastOccurrence(text, block) {
19700
19952
  }
19701
19953
  function coveredPathExists(targetRepo, line) {
19702
19954
  const path = line.trim().replace(/^\//, "").replace(/\/$/, "");
19703
- return path !== "" && lexists(join27(targetRepo, path));
19955
+ return path !== "" && lexists(join28(targetRepo, path));
19704
19956
  }
19705
19957
  function selfBuildMergedFileRules() {
19706
19958
  return {
@@ -19714,10 +19966,10 @@ function removeSelfBuildOwnedFile(targetRepo, rel, lists, name) {
19714
19966
  name(lists.leftAlone, `${rel} (a symbolic link, or inside one \u2014 not followed)`, rel);
19715
19967
  return;
19716
19968
  }
19717
- const abs = join27(targetRepo, rel);
19969
+ const abs = join28(targetRepo, rel);
19718
19970
  let isFile;
19719
19971
  try {
19720
- isFile = lstatSync4(abs).isFile();
19972
+ isFile = lstatSync5(abs).isFile();
19721
19973
  } catch {
19722
19974
  return;
19723
19975
  }
@@ -19726,7 +19978,7 @@ function removeSelfBuildOwnedFile(targetRepo, rel, lists, name) {
19726
19978
  return;
19727
19979
  }
19728
19980
  try {
19729
- rmSync8(abs);
19981
+ rmSync9(abs);
19730
19982
  name(lists.removed, rel, rel);
19731
19983
  } catch {
19732
19984
  name(lists.failed, rel, rel);
@@ -19738,14 +19990,14 @@ function removeSelfBuildCrewRoster(targetRepo, lists, name) {
19738
19990
  name(lists.leftAlone, `${rel} (a symbolic link, or inside one \u2014 not followed)`, rel);
19739
19991
  return;
19740
19992
  }
19741
- const abs = join27(targetRepo, rel);
19993
+ const abs = join28(targetRepo, rel);
19742
19994
  let bytes;
19743
19995
  try {
19744
19996
  bytes = readFileSync23(abs);
19745
19997
  } catch {
19746
19998
  return;
19747
19999
  }
19748
- if (!lstatSync4(abs).isFile()) {
20000
+ if (!lstatSync5(abs).isFile()) {
19749
20001
  name(lists.leftAlone, `${rel} (not a file)`, rel);
19750
20002
  return;
19751
20003
  }
@@ -19754,7 +20006,7 @@ function removeSelfBuildCrewRoster(targetRepo, lists, name) {
19754
20006
  return;
19755
20007
  }
19756
20008
  try {
19757
- rmSync8(abs);
20009
+ rmSync9(abs);
19758
20010
  name(lists.removed, rel, rel);
19759
20011
  } catch {
19760
20012
  name(lists.failed, rel, rel);
@@ -19765,14 +20017,14 @@ function removeSelfBuildCrewProfile(targetRepo, rel, text, lists, name) {
19765
20017
  name(lists.leftAlone, `${rel} (a symbolic link, or inside one \u2014 not followed)`, rel);
19766
20018
  return;
19767
20019
  }
19768
- const abs = join27(targetRepo, rel);
20020
+ const abs = join28(targetRepo, rel);
19769
20021
  let bytes;
19770
20022
  try {
19771
20023
  bytes = readFileSync23(abs);
19772
20024
  } catch {
19773
20025
  return;
19774
20026
  }
19775
- if (!lstatSync4(abs).isFile()) {
20027
+ if (!lstatSync5(abs).isFile()) {
19776
20028
  name(lists.leftAlone, `${rel} (not a file)`, rel);
19777
20029
  return;
19778
20030
  }
@@ -19781,7 +20033,7 @@ function removeSelfBuildCrewProfile(targetRepo, rel, text, lists, name) {
19781
20033
  return;
19782
20034
  }
19783
20035
  try {
19784
- rmSync8(abs);
20036
+ rmSync9(abs);
19785
20037
  name(lists.removed, rel, rel);
19786
20038
  } catch {
19787
20039
  name(lists.failed, rel, rel);
@@ -19790,15 +20042,15 @@ function removeSelfBuildCrewProfile(targetRepo, rel, text, lists, name) {
19790
20042
  function removeSelfBuildEmptyDir(targetRepo, dir, lists, name) {
19791
20043
  if (symlinkOnPath(targetRepo, dir))
19792
20044
  return;
19793
- const abs = join27(targetRepo, dir);
20045
+ const abs = join28(targetRepo, dir);
19794
20046
  try {
19795
- if (!lstatSync4(abs).isDirectory() || readdirSync(abs).length > 0)
20047
+ if (!lstatSync5(abs).isDirectory() || readdirSync2(abs).length > 0)
19796
20048
  return;
19797
20049
  } catch {
19798
20050
  return;
19799
20051
  }
19800
20052
  try {
19801
- rmdirSync(abs);
20053
+ rmdirSync2(abs);
19802
20054
  } catch {
19803
20055
  name(lists.failed, `${dir}/`, dir);
19804
20056
  }
@@ -19824,7 +20076,7 @@ function uninstallSelfBuild(options) {
19824
20076
  throw err;
19825
20077
  }
19826
20078
  const { targetRepo } = options;
19827
- const pinExisted = lexists(join27(targetRepo, BUNDLE_PIN_REL));
20079
+ const pinExisted = lexists(join28(targetRepo, BUNDLE_PIN_REL));
19828
20080
  const name = (list2, text, _path) => {
19829
20081
  list2.push(text);
19830
20082
  };
@@ -19911,7 +20163,7 @@ function uninstall(options) {
19911
20163
  }
19912
20164
  const serverRemoved = removeOwnServerApproval(targetRepo);
19913
20165
  if (listRemoved || serverRemoved) {
19914
- if (existsSync16(join27(targetRepo, LOCAL_SETTINGS_REL2))) {
20166
+ if (existsSync17(join28(targetRepo, LOCAL_SETTINGS_REL2))) {
19915
20167
  name(result.restored, LOCAL_SETTINGS_REL2, LOCAL_SETTINGS_REL2);
19916
20168
  } else {
19917
20169
  name(result.removed, LOCAL_SETTINGS_REL2, LOCAL_SETTINGS_REL2);
@@ -19953,7 +20205,7 @@ function uninstall(options) {
19953
20205
  const gitattributes = manifest.gitattributes ?? { created: false, appended: [] };
19954
20206
  trimAppendedFile(targetRepo, gitattributes, deleteWhenEmpty(gitattributes.created), gitattributesRules(writeSet), result, name);
19955
20207
  const foundNothing = result.removed.length + result.restored.length + result.kept.length + result.failed.length === 0 && result.keptIgnoreLines.length === 0;
19956
- if (checked.manifest === null && !lexists(join27(targetRepo, BUNDLE_PIN_REL)) && foundNothing) {
20208
+ if (checked.manifest === null && !lexists(join28(targetRepo, BUNDLE_PIN_REL)) && foundNothing) {
19957
20209
  return { outcome: "not-installed", exitCode: 0, ...result, credential: nothingToSay };
19958
20210
  }
19959
20211
  const credential = handleCredential(options, checked);
@@ -19961,7 +20213,7 @@ function uninstall(options) {
19961
20213
  if (result.failed.length === 0 && !credentialFailed) {
19962
20214
  removeInstalledFile(targetRepo, BUNDLE_PIN_REL, recorded.get(BUNDLE_PIN_REL), writeSet, result, name);
19963
20215
  try {
19964
- rmSync8(join27(targetRepo, INSTALL_MANIFEST_REL), { force: true });
20216
+ rmSync9(join28(targetRepo, INSTALL_MANIFEST_REL), { force: true });
19965
20217
  named2.add(INSTALL_MANIFEST_REL);
19966
20218
  } catch {
19967
20219
  name(result.failed, INSTALL_MANIFEST_REL, INSTALL_MANIFEST_REL);
@@ -19996,13 +20248,13 @@ function applyMergedJson(targetRepo, rule, file, deleteWhenEmpty, lists, name, e
19996
20248
  return;
19997
20249
  const empty = Object.keys(value).length === 0 && deleteWhenEmpty;
19998
20250
  writeBack(targetRepo, rel, file.text, empty ? null : inLineEndingOf(file.text, serializeJson(value)), lists, name);
19999
- if (adopted && lexists(join27(targetRepo, rel))) {
20251
+ if (adopted && lexists(join28(targetRepo, rel))) {
20000
20252
  name(lists.leftAlone, `${rel} (Halfcycle's hooks were taken out. There is no record of this file from before the install, so a permission rule or "$schema" Halfcycle added may still be there \u2014 it cannot be told apart from your own)`, rel);
20001
20253
  }
20002
20254
  }
20003
20255
  function removeCodexConfigBlock(targetRepo, record2, deleteWhenEmpty, lists, name) {
20004
20256
  const rel = CODEX_CONFIG_REL2;
20005
- const path = join27(targetRepo, rel);
20257
+ const path = join28(targetRepo, rel);
20006
20258
  if (symlinkOnPath(targetRepo, rel)) {
20007
20259
  if (lexists(path))
20008
20260
  name(lists.leftAlone, `${rel} (a symbolic link, or inside one \u2014 not followed)`, rel);
@@ -20010,7 +20262,7 @@ function removeCodexConfigBlock(targetRepo, record2, deleteWhenEmpty, lists, nam
20010
20262
  }
20011
20263
  let text;
20012
20264
  try {
20013
- if (!lstatSync4(path).isFile())
20265
+ if (!lstatSync5(path).isFile())
20014
20266
  return;
20015
20267
  text = readFileSync23(path, "utf-8");
20016
20268
  } catch {
@@ -20047,10 +20299,10 @@ function removeInstalledFile(targetRepo, path, entry, writeSet, lists, name) {
20047
20299
  name(lists.leftAlone, `${path} (a symbolic link, or inside one \u2014 not followed)`, path);
20048
20300
  return;
20049
20301
  }
20050
- const abs = join27(targetRepo, path);
20302
+ const abs = join28(targetRepo, path);
20051
20303
  let isFile;
20052
20304
  try {
20053
- isFile = lstatSync4(abs).isFile();
20305
+ isFile = lstatSync5(abs).isFile();
20054
20306
  } catch {
20055
20307
  return;
20056
20308
  }
@@ -20072,7 +20324,7 @@ function removeInstalledFile(targetRepo, path, entry, writeSet, lists, name) {
20072
20324
  }
20073
20325
  }
20074
20326
  try {
20075
- rmSync8(abs);
20327
+ rmSync9(abs);
20076
20328
  name(lists.removed, path, path);
20077
20329
  } catch {
20078
20330
  name(lists.failed, path, path);
@@ -20081,15 +20333,15 @@ function removeInstalledFile(targetRepo, path, entry, writeSet, lists, name) {
20081
20333
  function removeEmptyDir(targetRepo, dir, writeSet, lists, name) {
20082
20334
  if (!writeSet.dirs.has(dir) || symlinkOnPath(targetRepo, dir))
20083
20335
  return;
20084
- const abs = join27(targetRepo, dir);
20336
+ const abs = join28(targetRepo, dir);
20085
20337
  try {
20086
- if (!lstatSync4(abs).isDirectory() || readdirSync(abs).length > 0)
20338
+ if (!lstatSync5(abs).isDirectory() || readdirSync2(abs).length > 0)
20087
20339
  return;
20088
20340
  } catch {
20089
20341
  return;
20090
20342
  }
20091
20343
  try {
20092
- rmdirSync(abs);
20344
+ rmdirSync2(abs);
20093
20345
  } catch {
20094
20346
  name(lists.failed, `${dir}/`, dir);
20095
20347
  }
@@ -20100,7 +20352,7 @@ function trimAppendedFile(targetRepo, rec, deleteWhenEmpty, rules, lists, name)
20100
20352
  name(lists.leftAlone, `${rel} (a symbolic link \u2014 not followed)`, rel);
20101
20353
  return [];
20102
20354
  }
20103
- const path = join27(targetRepo, rel);
20355
+ const path = join28(targetRepo, rel);
20104
20356
  let before;
20105
20357
  try {
20106
20358
  before = readFileSync23(path, "utf-8");
@@ -20131,12 +20383,12 @@ function handleCredential(options, checked) {
20131
20383
  if (!lexists(dir))
20132
20384
  return { kind: "not-held", dir };
20133
20385
  try {
20134
- if (lstatSync4(dir).isSymbolicLink())
20386
+ if (lstatSync5(dir).isSymbolicLink())
20135
20387
  return { kind: "failed", dir, reason: "it is a symbolic link" };
20136
- rmSync8(engagementEnvPath(engagementId, home), { force: true });
20137
- rmSync8(notThisAccountMarkerPath(engagementId, home), { force: true });
20138
- if (readdirSync(dir).length === 0)
20139
- rmdirSync(dir);
20388
+ rmSync9(engagementEnvPath(engagementId, home), { force: true });
20389
+ rmSync9(notThisAccountMarkerPath(engagementId, home), { force: true });
20390
+ if (readdirSync2(dir).length === 0)
20391
+ rmdirSync2(dir);
20140
20392
  } catch (err) {
20141
20393
  return { kind: "failed", dir, reason: err instanceof Error ? err.message : String(err) };
20142
20394
  }
@@ -20147,16 +20399,16 @@ function filesUnder(root, rel) {
20147
20399
  const walk = (dirRel) => {
20148
20400
  let names;
20149
20401
  try {
20150
- if (!lstatSync4(join27(root, dirRel)).isDirectory())
20402
+ if (!lstatSync5(join28(root, dirRel)).isDirectory())
20151
20403
  return;
20152
- names = readdirSync(join27(root, dirRel));
20404
+ names = readdirSync2(join28(root, dirRel));
20153
20405
  } catch {
20154
20406
  return;
20155
20407
  }
20156
20408
  for (const entry of names.sort()) {
20157
20409
  const childRel = `${dirRel}/${entry}`;
20158
20410
  try {
20159
- if (lstatSync4(join27(root, childRel)).isDirectory())
20411
+ if (lstatSync5(join28(root, childRel)).isDirectory())
20160
20412
  walk(childRel);
20161
20413
  else
20162
20414
  out.push(childRel);
@@ -20170,9 +20422,9 @@ function filesUnder(root, rel) {
20170
20422
  }
20171
20423
  function shownPath(path, home) {
20172
20424
  const base = home ?? homedir7();
20173
- const rel = relative6(base, path);
20174
- const under = rel !== "" && !rel.startsWith("..") && !rel.startsWith(sep2);
20175
- return `${under ? `~/${rel.split(sep2).join("/")}` : path}/`;
20425
+ const rel = relative7(base, path);
20426
+ const under = rel !== "" && !rel.startsWith("..") && !rel.startsWith(sep3);
20427
+ return `${under ? `~/${rel.split(sep3).join("/")}` : path}/`;
20176
20428
  }
20177
20429
  function renderUninstallReport(result, home) {
20178
20430
  if (result.outcome === "refused") {
@@ -20228,7 +20480,7 @@ function renderUninstallReport(result, home) {
20228
20480
  }
20229
20481
 
20230
20482
  // dist/banner-facts.js
20231
- import { basename as basename4, resolve as resolve7 } from "node:path";
20483
+ import { basename as basename4, resolve as resolve8 } from "node:path";
20232
20484
  var BRAND_URL = "halfcycle.ai";
20233
20485
  var TAGLINE = [
20234
20486
  "Fewer cycles.",
@@ -20270,15 +20522,44 @@ var REAL_PROBES = {
20270
20522
  return void 0;
20271
20523
  }
20272
20524
  },
20273
- websiteHost: (env) => {
20525
+ websiteHost: (env, dir) => {
20274
20526
  try {
20275
- const resolved = resolveControlOrigin(env);
20276
- return resolved.source === "default" ? BRAND_URL : new URL(resolved.origin).host;
20527
+ return serviceHost4(env, dir);
20277
20528
  } catch {
20278
20529
  return void 0;
20279
20530
  }
20280
20531
  }
20281
20532
  };
20533
+ function serviceHost4(env, projectDir, home) {
20534
+ const fromEnv = resolveControlOrigin(env);
20535
+ const origin = fromEnv.source === "environment" ? fromEnv.origin : storedServiceOrigin(projectDir, home) ?? fromEnv.origin;
20536
+ if (origin === DEFAULT_CONTROL_ORIGIN)
20537
+ return BRAND_URL;
20538
+ try {
20539
+ return new URL(origin).host || BRAND_URL;
20540
+ } catch {
20541
+ return BRAND_URL;
20542
+ }
20543
+ }
20544
+ function storedServiceOrigin(projectDir, home) {
20545
+ let engagementId;
20546
+ try {
20547
+ engagementId = readBundlePin(projectDir)?.engagementId;
20548
+ } catch {
20549
+ return void 0;
20550
+ }
20551
+ if (typeof engagementId !== "string" || engagementId === "")
20552
+ return void 0;
20553
+ try {
20554
+ const stored = readEngagementEnv(engagementId, home)?.["HALFCYCLE_SERVICE_URL"];
20555
+ if (stored === void 0)
20556
+ return void 0;
20557
+ const resolved = resolveControlOrigin({ HALFCYCLE_SERVICE_URL: stored });
20558
+ return resolved.source === "environment" ? resolved.origin : void 0;
20559
+ } catch {
20560
+ return void 0;
20561
+ }
20562
+ }
20282
20563
  function fact(label, value, narrow) {
20283
20564
  if (value === void 0 || value === "")
20284
20565
  return void 0;
@@ -20304,14 +20585,14 @@ function openingBannerContent(projectDir, env = process.env, probes = REAL_PROBE
20304
20585
  // `Website`, NOT `Plane` — see this function's docblock. (No issue number here:
20305
20586
  // a comment beside a property survives into the published entry, unlike a
20306
20587
  // leading docblock, which esbuild drops.)
20307
- fact("Website", probes.websiteHost(env))
20588
+ fact("Website", probes.websiteHost(env, projectDir))
20308
20589
  ].filter((f) => f !== void 0);
20309
20590
  return {
20310
20591
  head: `halfcycle@${name}`,
20311
20592
  facts,
20312
20593
  tag: TAGLINE,
20313
20594
  tagNarrow: TAGLINE_NARROW,
20314
- url: probes.websiteHost(env) ?? BRAND_URL,
20595
+ url: probes.websiteHost(env, projectDir) ?? BRAND_URL,
20315
20596
  // `''` when even this package's own manifest could not be read. The one-line cut
20316
20597
  // drops the whole `vX.Y.Z` clause in that case rather than printing a bare `v`.
20317
20598
  version: version ?? ""
@@ -20319,7 +20600,7 @@ function openingBannerContent(projectDir, env = process.env, probes = REAL_PROBE
20319
20600
  }
20320
20601
  function safeBasename(dir) {
20321
20602
  try {
20322
- const name = basename4(resolve7(dir)).trim();
20603
+ const name = basename4(resolve8(dir)).trim();
20323
20604
  return name === "" ? "project" : name;
20324
20605
  } catch {
20325
20606
  return "project";
@@ -20661,7 +20942,7 @@ async function runCrew(repoRoot, deps) {
20661
20942
  // dist/pace.js
20662
20943
  import { spawn as spawn3 } from "node:child_process";
20663
20944
  import { readFileSync as readFileSync24 } from "node:fs";
20664
- import { join as join28 } from "node:path";
20945
+ import { join as join29 } from "node:path";
20665
20946
  var PACE_USAGE = `${ownCommand()} pace [--agent claude-code|codex] [--start <phase> [--agents <n>]] [--repo <root>]`;
20666
20947
  function parsePaceArgs(argv) {
20667
20948
  let repoRoot;
@@ -20726,13 +21007,13 @@ function runPaceChild(binary, credential, repoRoot, args2, deps = {}) {
20726
21007
  if (binary instanceof Error)
20727
21008
  return Promise.resolve({ code: 1, stdout: "" });
20728
21009
  const err = deps.stderr ?? process.stderr;
20729
- return new Promise((resolve9) => {
21010
+ return new Promise((resolve10) => {
20730
21011
  let settled = false;
20731
21012
  const done = (output) => {
20732
21013
  if (settled)
20733
21014
  return;
20734
21015
  settled = true;
20735
- resolve9(output);
21016
+ resolve10(output);
20736
21017
  };
20737
21018
  try {
20738
21019
  const child = spawn3(process.execPath, [binary, "pace", ...args2], {
@@ -20775,7 +21056,7 @@ function notPacedLine(reason) {
20775
21056
  }
20776
21057
  function readPromptScript(repoRoot) {
20777
21058
  try {
20778
- return readFileSync24(join28(repoRoot, ".claude", "hooks", "user-prompt-scope.sh"), "utf-8");
21059
+ return readFileSync24(join29(repoRoot, ".claude", "hooks", "user-prompt-scope.sh"), "utf-8");
20779
21060
  } catch {
20780
21061
  return null;
20781
21062
  }
@@ -20835,7 +21116,7 @@ async function runPace(repoRoot, start, deps, agent = null) {
20835
21116
 
20836
21117
  // dist/route.js
20837
21118
  import { readFileSync as readFileSync25 } from "node:fs";
20838
- import { join as join29 } from "node:path";
21119
+ import { join as join30 } from "node:path";
20839
21120
  var ROUTE_USAGE = `${ownCommand()} route <phase> <task> [--agent claude-code|codex] [--repo <root>]`;
20840
21121
  var ROUTE_WORK_READ_DEADLINE_MS = 5e3;
20841
21122
  function parseRouteArgs(argv) {
@@ -20878,7 +21159,7 @@ function parseRouteArgs(argv) {
20878
21159
  return { ok: true, phase: positionals[0], task: positionals[1], agent: agent ?? "claude-code", repoRoot };
20879
21160
  }
20880
21161
  function routingPath(engagementId, home) {
20881
- return join29(engagementStateDir(engagementId, home), ROUTING_FILENAME);
21162
+ return join30(engagementStateDir(engagementId, home), ROUTING_FILENAME);
20882
21163
  }
20883
21164
  function readCheapStarted(path, phase) {
20884
21165
  try {
@@ -20901,8 +21182,8 @@ function lastTierOf(attempts) {
20901
21182
  }
20902
21183
  async function readWorkInTime(deps, credential, phase) {
20903
21184
  let timer;
20904
- const late = new Promise((resolve9) => {
20905
- timer = setTimeout(() => resolve9({ kind: "unreachable" }), ROUTE_WORK_READ_DEADLINE_MS);
21185
+ const late = new Promise((resolve10) => {
21186
+ timer = setTimeout(() => resolve10({ kind: "unreachable" }), ROUTE_WORK_READ_DEADLINE_MS);
20906
21187
  timer.unref?.();
20907
21188
  });
20908
21189
  try {
@@ -20968,10 +21249,10 @@ async function runRoute(repoRoot, phase, taskId, agent, deps) {
20968
21249
  }
20969
21250
 
20970
21251
  // dist/restore.js
20971
- import { spawnSync as spawnSync3 } from "node:child_process";
20972
- import { closeSync as closeSync3, fchmodSync, lstatSync as lstatSync5, mkdirSync as mkdirSync12, openSync as openSync3, readdirSync as readdirSync2, readFileSync as readFileSync26, readlinkSync, realpathSync as realpathSync7, rmSync as rmSync9, symlinkSync, writeSync as writeSync3 } from "node:fs";
21252
+ import { spawnSync as spawnSync4 } from "node:child_process";
21253
+ import { closeSync as closeSync3, fchmodSync, lstatSync as lstatSync6, mkdirSync as mkdirSync12, openSync as openSync3, readdirSync as readdirSync3, readFileSync as readFileSync26, readlinkSync, realpathSync as realpathSync8, rmSync as rmSync10, symlinkSync, writeSync as writeSync3 } from "node:fs";
20973
21254
  import { homedir as homedir8 } from "node:os";
20974
- import { join as join30, resolve as resolve8 } from "node:path";
21255
+ import { join as join31, resolve as resolve9 } from "node:path";
20975
21256
  var RESTORE_USAGE = `${ownCommand()} restore [<id>]`;
20976
21257
  function parseRestoreArgs(rest) {
20977
21258
  if (rest.length > 1)
@@ -20984,7 +21265,7 @@ function parseRestoreArgs(rest) {
20984
21265
  var LIST_COMMAND = `${ownCommand()} restore`;
20985
21266
  function runRestore(cwd, id, options = {}) {
20986
21267
  const now = options.now ?? Date.now;
20987
- const top = git2(cwd, ["rev-parse", "--show-toplevel"]);
21268
+ const top = git3(cwd, ["rev-parse", "--show-toplevel"]);
20988
21269
  if (!top.ok || top.text.trim() === "") {
20989
21270
  return refused2("This folder is not in a project kept with git, so Halfcycle has saved nothing here to bring back.");
20990
21271
  }
@@ -20993,7 +21274,7 @@ function runRestore(cwd, id, options = {}) {
20993
21274
  if (engagementId === null) {
20994
21275
  return refused2(`Halfcycle is not set up in this project, so it has saved nothing here. To set it up, run: ${ownCommand()}`);
20995
21276
  }
20996
- const folder2 = join30(options.home ?? homedir8(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, SAVED_DIR_NAME);
21277
+ const folder2 = join31(options.home ?? homedir8(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, SAVED_DIR_NAME);
20997
21278
  const refs = savedRefs(root);
20998
21279
  if (refs === null)
20999
21280
  return refused2("Halfcycle could not read the saved copies in this project, so nothing was put back.");
@@ -21069,7 +21350,7 @@ function restoreOne(root, folder2, refs, rawId) {
21069
21350
  if (p.keptIn === "commit")
21070
21351
  fromCommit.push(p.path);
21071
21352
  else
21072
- restoreCopy(root, join30(folder2, id), p.path, out);
21353
+ restoreCopy(root, join31(folder2, id), p.path, out);
21073
21354
  }
21074
21355
  if (fromCommit.length > 0)
21075
21356
  restoreFromCommit(root, ref, fromCommit, out);
@@ -21097,7 +21378,7 @@ function makeFolders(root, rel) {
21097
21378
  const segments = rel.split("/");
21098
21379
  let at = root;
21099
21380
  for (let i = 0; i < segments.length - 1; i++) {
21100
- at = join30(at, segments[i]);
21381
+ at = join31(at, segments[i]);
21101
21382
  const shown2 = segments.slice(0, i + 1).join("/");
21102
21383
  let st = lstatOrNull3(at);
21103
21384
  if (st === null) {
@@ -21117,7 +21398,7 @@ function makeFolders(root, rel) {
21117
21398
  return null;
21118
21399
  }
21119
21400
  function place(root, rel, content, out) {
21120
- const abs = join30(root, rel);
21401
+ const abs = join31(root, rel);
21121
21402
  const there = lstatOrNull3(abs);
21122
21403
  if (there !== null) {
21123
21404
  if (sameAs(abs, there, content))
@@ -21165,14 +21446,14 @@ function restoreCopy(root, saveDir, rel, out) {
21165
21446
  let at = saveDir;
21166
21447
  const chain = [SAVED_COPIES_DIR_NAME, ...segments];
21167
21448
  for (let i = 0; i < chain.length - 1; i++) {
21168
- at = join30(at, chain[i]);
21449
+ at = join31(at, chain[i]);
21169
21450
  const st2 = lstatOrNull3(at);
21170
21451
  if (st2 === null || st2.isSymbolicLink() || !st2.isDirectory()) {
21171
21452
  out.left.push(`${rel}: the saved copy does not hold it`);
21172
21453
  return;
21173
21454
  }
21174
21455
  }
21175
- const src = join30(at, chain[chain.length - 1]);
21456
+ const src = join31(at, chain[chain.length - 1]);
21176
21457
  const st = lstatOrNull3(src);
21177
21458
  try {
21178
21459
  if (st !== null && st.isSymbolicLink())
@@ -21187,7 +21468,7 @@ function restoreFromCommit(root, ref, rels, out) {
21187
21468
  const entries = /* @__PURE__ */ new Map();
21188
21469
  for (let i = 0; i < rels.length; i += 200) {
21189
21470
  const chunk = rels.slice(i, i + 200);
21190
- const listed = git2(root, ["--literal-pathspecs", "ls-tree", "-z", "--full-tree", `${ref}^{commit}`, "--", ...chunk]);
21471
+ const listed = git3(root, ["--literal-pathspecs", "ls-tree", "-z", "--full-tree", `${ref}^{commit}`, "--", ...chunk]);
21191
21472
  if (!listed.ok) {
21192
21473
  for (const rel of rels)
21193
21474
  out.left.push(`${rel}: the saved copy could not be read`);
@@ -21218,7 +21499,7 @@ function restoreFromCommit(root, ref, rels, out) {
21218
21499
  }
21219
21500
  }
21220
21501
  function readBlobs(root, objects) {
21221
- const r = spawnSync3("git", ["cat-file", "--batch"], {
21502
+ const r = spawnSync4("git", ["cat-file", "--batch"], {
21222
21503
  cwd: root,
21223
21504
  env: gitEnvForNamedRepo(),
21224
21505
  input: `${objects.join("\n")}
@@ -21246,12 +21527,12 @@ function readBlobs(root, objects) {
21246
21527
  function restoreLines(root, ref, saved, out) {
21247
21528
  if (saved.branches.length === 0)
21248
21529
  return;
21249
- const parents = git2(root, ["rev-list", "--parents", "-n", "1", `${ref}^{commit}`]);
21530
+ const parents = git3(root, ["rev-list", "--parents", "-n", "1", `${ref}^{commit}`]);
21250
21531
  const held = new Set(parents.ok ? parents.text.trim().split(/\s+/).slice(1) : []);
21251
21532
  for (const b of saved.branches) {
21252
21533
  const shown2 = `the line of work "${b.name}"`;
21253
21534
  const full = `refs/heads/${b.name}`;
21254
- if (!git2(root, ["check-ref-format", full]).ok) {
21535
+ if (!git3(root, ["check-ref-format", full]).ok) {
21255
21536
  out.left.push(`${shown2}: its name cannot be used`);
21256
21537
  continue;
21257
21538
  }
@@ -21259,7 +21540,7 @@ function restoreLines(root, ref, saved, out) {
21259
21540
  out.left.push(`${shown2}: the saved copy does not hold its commit`);
21260
21541
  continue;
21261
21542
  }
21262
- const now = git2(root, ["rev-parse", "--verify", "--quiet", `${full}^{commit}`]);
21543
+ const now = git3(root, ["rev-parse", "--verify", "--quiet", `${full}^{commit}`]);
21263
21544
  if (now.ok && now.text.trim() !== "") {
21264
21545
  if (now.text.trim() === b.commit)
21265
21546
  out.already.push(`${shown2}, at its saved commit ${b.commit.slice(0, 12)}`);
@@ -21267,7 +21548,7 @@ function restoreLines(root, ref, saved, out) {
21267
21548
  out.left.push(`${shown2}: one with this name exists now; rename it and run this again to bring back the saved one`);
21268
21549
  continue;
21269
21550
  }
21270
- const made = git2(root, ["update-ref", "--no-deref", full, b.commit, ""]);
21551
+ const made = git3(root, ["update-ref", "--no-deref", full, b.commit, ""]);
21271
21552
  if (made.ok)
21272
21553
  out.back.push(`${shown2}, at its saved commit ${b.commit.slice(0, 12)}`);
21273
21554
  else
@@ -21276,14 +21557,14 @@ function restoreLines(root, ref, saved, out) {
21276
21557
  }
21277
21558
  function pinnedEngagement(root) {
21278
21559
  try {
21279
- const pin = JSON.parse(readFileSync26(join30(root, BUNDLE_PIN_REL), "utf-8"));
21560
+ const pin = JSON.parse(readFileSync26(join31(root, BUNDLE_PIN_REL), "utf-8"));
21280
21561
  return isEngagementId(pin.engagementId) ? pin.engagementId : null;
21281
21562
  } catch {
21282
21563
  return null;
21283
21564
  }
21284
21565
  }
21285
21566
  function savedRefs(root) {
21286
- const listed = git2(root, ["for-each-ref", "--format=%(refname)%00%(committerdate:unix)", SAVED_REF_PREFIX]);
21567
+ const listed = git3(root, ["for-each-ref", "--format=%(refname)%00%(committerdate:unix)", SAVED_REF_PREFIX]);
21287
21568
  if (!listed.ok)
21288
21569
  return null;
21289
21570
  const refs = /* @__PURE__ */ new Map();
@@ -21298,11 +21579,11 @@ function savedRefs(root) {
21298
21579
  return refs;
21299
21580
  }
21300
21581
  function readSaved(folder2, id) {
21301
- const dir = join30(folder2, id);
21582
+ const dir = join31(folder2, id);
21302
21583
  const st = lstatOrNull3(dir);
21303
21584
  if (st === null || !st.isDirectory())
21304
21585
  return null;
21305
- const file = join30(dir, SAVED_FILE_NAME);
21586
+ const file = join31(dir, SAVED_FILE_NAME);
21306
21587
  const fst = lstatOrNull3(file);
21307
21588
  if (fst === null || !fst.isFile())
21308
21589
  return null;
@@ -21315,11 +21596,11 @@ function readSaved(folder2, id) {
21315
21596
  }
21316
21597
  function pruneAtRestore(root, folder2, refs, now) {
21317
21598
  try {
21318
- const common = git2(root, ["rev-parse", "--git-common-dir"]);
21319
- const repository = common.ok && common.text.trim() !== "" ? realOrSelf(resolve8(root, common.text.trim())) : null;
21599
+ const common = git3(root, ["rev-parse", "--git-common-dir"]);
21600
+ const repository = common.ok && common.text.trim() !== "" ? realOrSelf(resolve9(root, common.text.trim())) : null;
21320
21601
  let names = [];
21321
21602
  try {
21322
- names = readdirSync2(folder2);
21603
+ names = readdirSync3(folder2);
21323
21604
  } catch {
21324
21605
  names = [];
21325
21606
  }
@@ -21328,7 +21609,7 @@ function pruneAtRestore(root, folder2, refs, now) {
21328
21609
  if (!savedIdSchema.safeParse(id).success)
21329
21610
  continue;
21330
21611
  here.add(id);
21331
- const dir = join30(folder2, id);
21612
+ const dir = join31(folder2, id);
21332
21613
  const st = lstatOrNull3(dir);
21333
21614
  const answer = savedPruneAnswer({
21334
21615
  refMadeAt: refs.get(id) ?? null,
@@ -21336,11 +21617,11 @@ function pruneAtRestore(root, folder2, refs, now) {
21336
21617
  folderRepository: markedRepository(dir)
21337
21618
  }, { repository, now });
21338
21619
  if (answer === "remove-folder")
21339
- rmSync9(dir, { recursive: true, force: true });
21340
- else if (answer === "remove-ref-then-folder" && git2(root, ["update-ref", "-d", savedRefOf(id)]).ok) {
21620
+ rmSync10(dir, { recursive: true, force: true });
21621
+ else if (answer === "remove-ref-then-folder" && git3(root, ["update-ref", "-d", savedRefOf(id)]).ok) {
21341
21622
  refs.delete(id);
21342
- rmSync9(dir, { recursive: true, force: true });
21343
- } else if (answer === "remove-ref" && git2(root, ["update-ref", "-d", savedRefOf(id)]).ok) {
21623
+ rmSync10(dir, { recursive: true, force: true });
21624
+ } else if (answer === "remove-ref" && git3(root, ["update-ref", "-d", savedRefOf(id)]).ok) {
21344
21625
  refs.delete(id);
21345
21626
  }
21346
21627
  }
@@ -21348,7 +21629,7 @@ function pruneAtRestore(root, folder2, refs, now) {
21348
21629
  if (here.has(id))
21349
21630
  continue;
21350
21631
  const answer = savedPruneAnswer({ refMadeAt: madeAt, folderChangedAt: null, folderRepository: null }, { repository, now });
21351
- if (answer === "remove-ref" && git2(root, ["update-ref", "-d", savedRefOf(id)]).ok)
21632
+ if (answer === "remove-ref" && git3(root, ["update-ref", "-d", savedRefOf(id)]).ok)
21352
21633
  refs.delete(id);
21353
21634
  }
21354
21635
  } catch {
@@ -21356,15 +21637,15 @@ function pruneAtRestore(root, folder2, refs, now) {
21356
21637
  }
21357
21638
  function markedRepository(dir) {
21358
21639
  try {
21359
- const text = readFileSync26(join30(dir, SAVED_REPOSITORY_FILE_NAME), "utf-8").trim();
21640
+ const text = readFileSync26(join31(dir, SAVED_REPOSITORY_FILE_NAME), "utf-8").trim();
21360
21641
  return text === "" ? null : text;
21361
21642
  } catch {
21362
21643
  return null;
21363
21644
  }
21364
21645
  }
21365
- function git2(cwd, args2) {
21646
+ function git3(cwd, args2) {
21366
21647
  try {
21367
- const r = spawnSync3("git", args2, { cwd, encoding: "utf-8", env: gitEnvForNamedRepo(), maxBuffer: 64 * 1024 * 1024 });
21648
+ const r = spawnSync4("git", args2, { cwd, encoding: "utf-8", env: gitEnvForNamedRepo(), maxBuffer: 64 * 1024 * 1024 });
21368
21649
  return r.status === 0 ? { ok: true, text: r.stdout ?? "" } : { ok: false };
21369
21650
  } catch {
21370
21651
  return { ok: false };
@@ -21372,23 +21653,23 @@ function git2(cwd, args2) {
21372
21653
  }
21373
21654
  function lstatOrNull3(p) {
21374
21655
  try {
21375
- return lstatSync5(p);
21656
+ return lstatSync6(p);
21376
21657
  } catch {
21377
21658
  return null;
21378
21659
  }
21379
21660
  }
21380
21661
  function realOrSelf(p) {
21381
21662
  try {
21382
- return realpathSync7(p);
21663
+ return realpathSync8(p);
21383
21664
  } catch {
21384
21665
  return p;
21385
21666
  }
21386
21667
  }
21387
21668
 
21388
21669
  // dist/request-shapes-verb.js
21389
- import { chmodSync as chmodSync4, existsSync as existsSync17, mkdirSync as mkdirSync13, readFileSync as readFileSync27, rmSync as rmSync10, writeFileSync as writeFileSync10 } from "node:fs";
21670
+ import { chmodSync as chmodSync4, existsSync as existsSync18, mkdirSync as mkdirSync13, readFileSync as readFileSync27, rmSync as rmSync11, writeFileSync as writeFileSync10 } from "node:fs";
21390
21671
  import { homedir as homedir9, platform as platform5 } from "node:os";
21391
- import { join as join31 } from "node:path";
21672
+ import { join as join32 } from "node:path";
21392
21673
  var REQUEST_SHAPES_USAGE = `${ownCommand()} request-shapes on|off|show|clear`;
21393
21674
  var REQUEST_SHAPES_FORMS = ["on", "off", "show", "clear"];
21394
21675
  var REQUEST_SHAPES_ON_LINE = "Halfcycle will write down the shape of every request this computer sends to it, never what is in them, until you run request-shapes off.";
@@ -21433,7 +21714,7 @@ function formatLine(line) {
21433
21714
  function showLog(logPath2) {
21434
21715
  let text = "";
21435
21716
  try {
21436
- text = existsSync17(logPath2) ? readFileSync27(logPath2, "utf-8") : "";
21717
+ text = existsSync18(logPath2) ? readFileSync27(logPath2, "utf-8") : "";
21437
21718
  } catch {
21438
21719
  text = "";
21439
21720
  }
@@ -21451,9 +21732,9 @@ function ownerOnly(path, mode) {
21451
21732
  chmodSync4(path, mode);
21452
21733
  }
21453
21734
  function runRequestShapes(form, home = homedir9()) {
21454
- const folder2 = join31(home, HALFCYCLE_DIR_NAME, REQUEST_SHAPES_DIR_NAME);
21455
- const switchPath = join31(folder2, REQUEST_SHAPES_SWITCH_FILENAME);
21456
- const logPath2 = join31(folder2, REQUEST_SHAPES_LOG_FILENAME);
21735
+ const folder2 = join32(home, HALFCYCLE_DIR_NAME, REQUEST_SHAPES_DIR_NAME);
21736
+ const switchPath = join32(folder2, REQUEST_SHAPES_SWITCH_FILENAME);
21737
+ const logPath2 = join32(folder2, REQUEST_SHAPES_LOG_FILENAME);
21457
21738
  try {
21458
21739
  switch (form) {
21459
21740
  case "on":
@@ -21464,13 +21745,13 @@ function runRequestShapes(form, home = homedir9()) {
21464
21745
  return { stdout: `${REQUEST_SHAPES_ON_LINE}
21465
21746
  `, stderr: "", exitCode: 0 };
21466
21747
  case "off":
21467
- rmSync10(switchPath, { force: true });
21748
+ rmSync11(switchPath, { force: true });
21468
21749
  return { stdout: `${REQUEST_SHAPES_OFF_LINE}
21469
21750
  `, stderr: "", exitCode: 0 };
21470
21751
  case "show":
21471
21752
  return { stdout: showLog(logPath2), stderr: "", exitCode: 0 };
21472
21753
  case "clear":
21473
- if (existsSync17(logPath2)) {
21754
+ if (existsSync18(logPath2)) {
21474
21755
  writeFileSync10(logPath2, "", { mode: 384 });
21475
21756
  ownerOnly(logPath2, 384);
21476
21757
  }
@@ -21779,7 +22060,7 @@ ${USAGE}`);
21779
22060
  if (installArm) {
21780
22061
  const engagementIdArg = positionals[1];
21781
22062
  const engagementTypeRaw = positionals[2] ?? "client";
21782
- if (!existsSync18(targetRepo)) {
22063
+ if (!existsSync19(targetRepo)) {
21783
22064
  process.stderr.write(`${folderMissingRefusal(targetRepo)}
21784
22065
  `);
21785
22066
  process.exit(1);
@@ -22187,6 +22468,13 @@ ${USAGE}`);
22187
22468
  const obtained = await obtainBuildRecord(credential, repoRoot, phaseArg);
22188
22469
  if (obtained.kind === "written") {
22189
22470
  process.stdout.write(writtenLines(obtained));
22471
+ const stamped = readEngagementEnv(credential.engagementId)?.[PHASE_STAMP_ENV_KEY]?.trim();
22472
+ if (stamped === void 0 || stamped === "" || stamped === phaseArg) {
22473
+ const tidied = tidyWorkerCopies(repoRoot);
22474
+ if (tidied.message !== void 0)
22475
+ process.stdout.write(`${tidied.message}
22476
+ `);
22477
+ }
22190
22478
  if (obtained.stampFailure) {
22191
22479
  process.stderr.write(`halfcycle build-record: the record was written, but this machine's record of the open phase could not be cleared: ${obtained.stampFailure.reason}
22192
22480
  halfcycle build-record: until ${obtained.stampFailure.path} can be written, the checks run on this computer are still filed under the phase that closed.