halfcycle 0.12.0 → 0.13.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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,241 @@ 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
+ const held = tree.locked ? "work" : holdsWork(at, mainPath);
11853
+ if (held === "work") {
11854
+ kept.push(shown2);
11855
+ continue;
11856
+ }
11857
+ clearCaches(at, at);
11858
+ const remove = held === "duplicates" ? ["worktree", "remove", "--force", at] : ["worktree", "remove", at];
11859
+ if (!git2(root, remove).ok) {
11860
+ kept.push(shown2);
11861
+ continue;
11862
+ }
11863
+ removed += 1;
11864
+ if (tree.branch !== void 0)
11865
+ git2(root, ["branch", "-d", tree.branch]);
11866
+ }
11867
+ return { removed, kept, message: lineFor(removed, kept) };
11868
+ }
11869
+ function lineFor(removed, kept) {
11870
+ if (removed === 0 && kept.length === 0)
11871
+ return void 0;
11872
+ const head = `[halfcycle] Removed ${removed} worker ${removed === 1 ? "copy" : "copies"} under ${COPIES_SHOWN}.`;
11873
+ return kept.length === 0 ? head : `${head} Kept ${kept.length} that may hold work: ${kept.join(", ")}.`;
11874
+ }
11875
+ function git2(cwd, args2) {
11876
+ const r = spawnSync("git", [...args2], {
11877
+ cwd,
11878
+ encoding: "utf-8",
11879
+ stdio: ["ignore", "pipe", "pipe"],
11880
+ windowsHide: true,
11881
+ timeout: 12e4,
11882
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
11883
+ });
11884
+ if (r.error !== void 0)
11885
+ return { ran: false, ok: false, out: "" };
11886
+ return { ran: true, ok: r.status === 0, out: r.stdout ?? "" };
11887
+ }
11888
+ function parseWorktrees(porcelain) {
11889
+ const trees = [];
11890
+ let current;
11891
+ for (const line of porcelain.split(/\r?\n/)) {
11892
+ if (line.startsWith("worktree ")) {
11893
+ current = { path: line.slice("worktree ".length), branch: void 0, locked: false, bare: false };
11894
+ trees.push(current);
11895
+ } else if (current === void 0) {
11896
+ continue;
11897
+ } else if (line.startsWith("branch refs/heads/")) {
11898
+ current.branch = line.slice("branch refs/heads/".length);
11899
+ } else if (line === "locked" || line.startsWith("locked ")) {
11900
+ current.locked = true;
11901
+ } else if (line === "bare") {
11902
+ current.bare = true;
11903
+ }
11904
+ }
11905
+ return trees;
11906
+ }
11907
+ function holdsWork(copyDir, mainDir) {
11908
+ const status = git2(copyDir, ["status", "--porcelain", "-z", "--untracked-files=all"]);
11909
+ if (!status.ok)
11910
+ return "work";
11911
+ let duplicates = false;
11912
+ const fields = status.out.split("\0");
11913
+ for (let i = 0; i < fields.length; i += 1) {
11914
+ const field = fields[i];
11915
+ if (field === "")
11916
+ continue;
11917
+ const code = field.slice(0, 2);
11918
+ const path = field.slice(3);
11919
+ if (code === "??" && path.split("/").some((segment) => CACHE_NAMES.has(segment)))
11920
+ continue;
11921
+ if (code !== "??" && code !== " M" && code !== "M " && code !== "A ")
11922
+ return "work";
11923
+ if (!duplicatesMain(copyDir, mainDir, path))
11924
+ return "work";
11925
+ duplicates = true;
11926
+ }
11927
+ return duplicates ? "duplicates" : "clean";
11928
+ }
11929
+ function duplicatesMain(copyDir, mainDir, path) {
11930
+ if (path === "" || path.endsWith("/"))
11931
+ return false;
11932
+ let executable;
11933
+ try {
11934
+ const entry = lstatSync2(join9(copyDir, ...path.split("/")));
11935
+ if (!entry.isFile())
11936
+ return false;
11937
+ executable = (entry.mode & 64) !== 0;
11938
+ } catch {
11939
+ return false;
11940
+ }
11941
+ const listed = git2(mainDir, ["--literal-pathspecs", "ls-tree", "-z", "--full-tree", "HEAD", "--", path]);
11942
+ if (!listed.ok)
11943
+ return false;
11944
+ const entries = listed.out.split("\0").filter((entry) => entry !== "");
11945
+ if (entries.length !== 1)
11946
+ return false;
11947
+ const match = /^(100644|100755) blob ([0-9a-f]+)\t([\s\S]*)$/.exec(entries[0]);
11948
+ if (match === null || match[3] !== path)
11949
+ return false;
11950
+ if (process.platform !== "win32" && executable !== (match[1] === "100755"))
11951
+ return false;
11952
+ const hashed = git2(copyDir, ["hash-object", "--no-filters", "--", path]);
11953
+ return hashed.ok && hashed.out.trim() === match[2];
11954
+ }
11955
+ function clearCaches(copyDir, dir) {
11956
+ let names;
11957
+ try {
11958
+ names = readdirSync(dir);
11959
+ } catch {
11960
+ return;
11961
+ }
11962
+ for (const name of names) {
11963
+ if (name === ".git")
11964
+ continue;
11965
+ const at = join9(dir, name);
11966
+ let entry;
11967
+ try {
11968
+ entry = lstatSync2(at);
11969
+ } catch {
11970
+ continue;
11971
+ }
11972
+ if (entry.isSymbolicLink()) {
11973
+ if (CACHE_NAMES.has(name) && !holdsTracked(copyDir, at))
11974
+ removeLink(at);
11975
+ continue;
11976
+ }
11977
+ if (!entry.isDirectory())
11978
+ continue;
11979
+ if (CACHE_NAMES.has(name)) {
11980
+ if (holdsTracked(copyDir, at))
11981
+ continue;
11982
+ try {
11983
+ rmSync3(at, { recursive: true, force: true });
11984
+ } catch {
11985
+ }
11986
+ continue;
11987
+ }
11988
+ clearCaches(copyDir, at);
11989
+ }
11990
+ }
11991
+ function holdsTracked(copyDir, path) {
11992
+ const listed = git2(copyDir, ["ls-files", "-z", "--", relative2(copyDir, path).split(sep).join("/")]);
11993
+ return !listed.ok || listed.out !== "";
11994
+ }
11995
+ function removeLink(at) {
11996
+ try {
11997
+ rmSync3(at, { force: true });
11998
+ } catch {
11999
+ try {
12000
+ rmdirSync(at);
12001
+ } catch {
12002
+ }
12003
+ }
12004
+ }
12005
+ function canonical(path) {
12006
+ try {
12007
+ return realpathSync(path);
12008
+ } catch {
12009
+ return resolve2(path);
12010
+ }
12011
+ }
12012
+ function fold(path) {
12013
+ return process.platform === "win32" ? path.toLowerCase() : path;
12014
+ }
12015
+ function samePath(a, b) {
12016
+ return fold(resolve2(a)) === fold(resolve2(b));
12017
+ }
12018
+ function isInside(parent, child) {
12019
+ const rel = relative2(fold(parent), fold(child));
12020
+ return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute3(rel);
12021
+ }
12022
+ function isInsideCopiesFolder(path) {
12023
+ const segments = fold(path).split(sep);
12024
+ for (let i = 0; i + 2 < segments.length; i += 1) {
12025
+ if (segments[i] === ".claude" && segments[i + 1] === "worktrees")
12026
+ return true;
12027
+ }
12028
+ return false;
12029
+ }
12030
+
11762
12031
  // dist/control-origin.js
11763
12032
  var DEFAULT_CONTROL_ORIGIN = "https://control.halfcycle.ai";
11764
12033
  function resolveControlOrigin(env = process.env) {
@@ -11870,14 +12139,14 @@ async function bindLoopback(env = process.env, engagementId, serviceUrl) {
11870
12139
  else
11871
12140
  pending = callback;
11872
12141
  }, engagementId, serviceUrl));
11873
- const bindProblem = await new Promise((resolve9) => {
12142
+ const bindProblem = await new Promise((resolve10) => {
11874
12143
  const onError = (err) => {
11875
- resolve9(`${err.code ?? "bind failed"} on ${LOOPBACK_REDIRECT_HOST}:${wanted.port}`);
12144
+ resolve10(`${err.code ?? "bind failed"} on ${LOOPBACK_REDIRECT_HOST}:${wanted.port}`);
11876
12145
  };
11877
12146
  server.once("error", onError);
11878
12147
  server.listen({ host: LOOPBACK_REDIRECT_HOST, port: wanted.port }, () => {
11879
12148
  server.removeListener("error", onError);
11880
- resolve9(null);
12149
+ resolve10(null);
11881
12150
  });
11882
12151
  });
11883
12152
  if (bindProblem !== null) {
@@ -11893,9 +12162,9 @@ async function bindLoopback(env = process.env, engagementId, serviceUrl) {
11893
12162
  bound: true,
11894
12163
  target: { port: address.port, state },
11895
12164
  boundAddress: address.address,
11896
- waitForCallback: ({ timeoutMs, onStillWaiting, stillWaitingMs = DEFAULT_STILL_WAITING_MS }) => new Promise((resolve9) => {
12165
+ waitForCallback: ({ timeoutMs, onStillWaiting, stillWaitingMs = DEFAULT_STILL_WAITING_MS }) => new Promise((resolve10) => {
11897
12166
  if (pending !== null) {
11898
- resolve9(pending);
12167
+ resolve10(pending);
11899
12168
  return;
11900
12169
  }
11901
12170
  const startedAt = Date.now();
@@ -11906,7 +12175,7 @@ async function bindLoopback(env = process.env, engagementId, serviceUrl) {
11906
12175
  clearTimeout(timer);
11907
12176
  deliver = null;
11908
12177
  abandon = null;
11909
- resolve9(value);
12178
+ resolve10(value);
11910
12179
  };
11911
12180
  const timer = setTimeout(() => finish(null), Math.max(0, timeoutMs));
11912
12181
  deliver = finish;
@@ -11920,23 +12189,23 @@ async function bindLoopback(env = process.env, engagementId, serviceUrl) {
11920
12189
  };
11921
12190
  }
11922
12191
  function closeServer(server) {
11923
- return new Promise((resolve9) => {
12192
+ return new Promise((resolve10) => {
11924
12193
  server.closeAllConnections();
11925
- server.close(() => resolve9());
12194
+ server.close(() => resolve10());
11926
12195
  });
11927
12196
  }
11928
12197
 
11929
12198
  // dist/sign-in-attempt.js
11930
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync8, realpathSync, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "node:fs";
12199
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync8, realpathSync as realpathSync2, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "node:fs";
11931
12200
  import { homedir as homedir4 } from "node:os";
11932
- import { join as join9 } from "node:path";
12201
+ import { join as join10 } from "node:path";
11933
12202
  var ATTEMPT_FILENAME = "sign-in-attempt.json";
11934
12203
  function attemptPath(home) {
11935
- return join9(home ?? homedir4(), HALFCYCLE_DIR_NAME, ATTEMPT_FILENAME);
12204
+ return join10(home ?? homedir4(), HALFCYCLE_DIR_NAME, ATTEMPT_FILENAME);
11936
12205
  }
11937
12206
  function realFolder(folder2) {
11938
12207
  try {
11939
- return realpathSync(folder2);
12208
+ return realpathSync2(folder2);
11940
12209
  } catch {
11941
12210
  return folder2;
11942
12211
  }
@@ -11944,7 +12213,7 @@ function realFolder(folder2) {
11944
12213
  function recordSignInAttempt(folder2, expiresAt, home) {
11945
12214
  try {
11946
12215
  const path = attemptPath(home);
11947
- mkdirSync6(join9(home ?? homedir4(), HALFCYCLE_DIR_NAME), { recursive: true, mode: 448 });
12216
+ mkdirSync6(join10(home ?? homedir4(), HALFCYCLE_DIR_NAME), { recursive: true, mode: 448 });
11948
12217
  const record2 = { folder: realFolder(folder2), expiresAt };
11949
12218
  writeFileSync6(path, `${JSON.stringify(record2)}
11950
12219
  `, { mode: 384 });
@@ -11954,7 +12223,7 @@ function recordSignInAttempt(folder2, expiresAt, home) {
11954
12223
  }
11955
12224
  function clearSignInAttempt(home) {
11956
12225
  try {
11957
- rmSync3(attemptPath(home), { force: true });
12226
+ rmSync4(attemptPath(home), { force: true });
11958
12227
  } catch {
11959
12228
  return;
11960
12229
  }
@@ -11997,7 +12266,7 @@ function defaultWrite(text) {
11997
12266
  process.stdout.write(text);
11998
12267
  }
11999
12268
  function defaultSleep(ms) {
12000
- return new Promise((resolve9) => setTimeout(resolve9, ms));
12269
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
12001
12270
  }
12002
12271
  function browserOpenCommand(os, url) {
12003
12272
  if (os === "darwin")
@@ -12044,12 +12313,12 @@ async function openBrowser(url, env = process.env, os = platform3(), spawnImpl =
12044
12313
  };
12045
12314
  }
12046
12315
  const { command, args: args2, windowsVerbatimArguments } = browserOpenCommand(os, url);
12047
- return new Promise((resolve9) => {
12316
+ return new Promise((resolve10) => {
12048
12317
  let child;
12049
12318
  try {
12050
12319
  child = spawnImpl(command, args2, { stdio: "ignore", env, windowsVerbatimArguments });
12051
12320
  } catch {
12052
- resolve9({ opened: false, reason: `${command} could not be run` });
12321
+ resolve10({ opened: false, reason: `${command} could not be run` });
12053
12322
  return;
12054
12323
  }
12055
12324
  let settled = false;
@@ -12058,7 +12327,7 @@ async function openBrowser(url, env = process.env, os = platform3(), spawnImpl =
12058
12327
  return;
12059
12328
  settled = true;
12060
12329
  clearTimeout(timer);
12061
- resolve9(attempt2);
12330
+ resolve10(attempt2);
12062
12331
  };
12063
12332
  const timer = setTimeout(() => {
12064
12333
  child.unref();
@@ -12203,17 +12472,17 @@ async function pollBesideLoopback(serviceUrl, started, wake, deps) {
12203
12472
  continue;
12204
12473
  }
12205
12474
  if (sleep === void 0) {
12206
- await new Promise((resolve9) => {
12207
- const timer = setTimeout(resolve9, started.pollIntervalMs);
12475
+ await new Promise((resolve10) => {
12476
+ const timer = setTimeout(resolve10, started.pollIntervalMs);
12208
12477
  interrupt = () => {
12209
12478
  clearTimeout(timer);
12210
- resolve9();
12479
+ resolve10();
12211
12480
  };
12212
12481
  });
12213
12482
  } else {
12214
- await new Promise((resolve9) => {
12215
- interrupt = resolve9;
12216
- void sleep(started.pollIntervalMs).then(resolve9);
12483
+ await new Promise((resolve10) => {
12484
+ interrupt = resolve10;
12485
+ void sleep(started.pollIntervalMs).then(resolve10);
12217
12486
  });
12218
12487
  }
12219
12488
  interrupt = null;
@@ -12819,7 +13088,7 @@ function worthAnotherTry(value) {
12819
13088
  return { tryAgain: true, value };
12820
13089
  }
12821
13090
  function realSleep(ms) {
12822
- return new Promise((resolve9) => setTimeout(resolve9, ms));
13091
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
12823
13092
  }
12824
13093
  async function attemptInTurn(attempt2, options = {}) {
12825
13094
  const timeouts = options.timeouts ?? ATTEMPT_TIMEOUTS_MS;
@@ -13017,9 +13286,9 @@ function wrongAccountVisibility(act) {
13017
13286
  async function askLine(question2) {
13018
13287
  const rl = createInterface({ input: process.stdin, output: process.stdout });
13019
13288
  try {
13020
- return await new Promise((resolve9) => {
13021
- rl.once("close", () => resolve9(""));
13022
- rl.question(question2).then(resolve9, () => resolve9(""));
13289
+ return await new Promise((resolve10) => {
13290
+ rl.once("close", () => resolve10(""));
13291
+ rl.question(question2).then(resolve10, () => resolve10(""));
13023
13292
  });
13024
13293
  } finally {
13025
13294
  rl.close();
@@ -13152,7 +13421,7 @@ async function confirmActingIdentity(serviceUrl, credential, act, deps = {}) {
13152
13421
  }
13153
13422
 
13154
13423
  // dist/project-name.js
13155
- import { basename, resolve as resolve2 } from "node:path";
13424
+ import { basename, resolve as resolve3 } from "node:path";
13156
13425
  var MAX_DERIVED_NAME_LEN = 100;
13157
13426
  var EMPTY_SEGMENTS = /* @__PURE__ */ new Set(["", ".", "..", "/", "\\"]);
13158
13427
  function repositoryNameFromRemote(remote) {
@@ -13172,7 +13441,7 @@ function isLocalRemote(remote) {
13172
13441
  function deriveProjectName(targetRepoRoot) {
13173
13442
  const remote = readRemote(targetRepoRoot);
13174
13443
  const fromRemote = remote && !isLocalRemote(remote) ? repositoryNameFromRemote(remote) : void 0;
13175
- const candidate = fromRemote ?? basename(resolve2(targetRepoRoot));
13444
+ const candidate = fromRemote ?? basename(resolve3(targetRepoRoot));
13176
13445
  const name = candidate.trim();
13177
13446
  if (EMPTY_SEGMENTS.has(name))
13178
13447
  return void 0;
@@ -13261,9 +13530,9 @@ async function joinPinnedEngagement(serviceUrl, engagementId, targetRepo, deps =
13261
13530
  }
13262
13531
 
13263
13532
  // 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";
13533
+ import { spawnSync as spawnSync2 } from "node:child_process";
13534
+ import { existsSync as existsSync8, readFileSync as readFileSync9, realpathSync as realpathSync3 } from "node:fs";
13535
+ import { delimiter, dirname as dirname5, join as join11 } from "node:path";
13267
13536
  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
13537
  var PreCreateRefused = class extends Error {
13269
13538
  constructor(message) {
@@ -13306,14 +13575,14 @@ function refuseSystemFolder(targetRepo, platform6 = process.platform) {
13306
13575
  function refuseInsideProject(targetRepo) {
13307
13576
  let folder2 = realOrLiteral(targetRepo);
13308
13577
  for (let parent = dirname5(folder2); parent !== folder2; folder2 = parent, parent = dirname5(parent)) {
13309
- if (existsSync7(join10(parent, BUNDLE_PIN_REL))) {
13578
+ if (existsSync8(join11(parent, BUNDLE_PIN_REL))) {
13310
13579
  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
13580
  }
13312
13581
  }
13313
13582
  }
13314
13583
  function realOrLiteral(folder2) {
13315
13584
  try {
13316
- return realpathSync2(folder2);
13585
+ return realpathSync3(folder2);
13317
13586
  } catch {
13318
13587
  return folder2;
13319
13588
  }
@@ -13330,22 +13599,22 @@ async function offerGitForWindows() {
13330
13599
  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
13600
  }
13332
13601
  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"));
13602
+ const onPath = (env["PATH"] ?? "").split(delimiter).filter((dir) => dir !== "" && existsSync8(join11(dir, "git.exe"))).map((dir) => join11(dir, "..", "bin", "bash.exe"));
13334
13603
  const candidates = [
13335
13604
  env["CLAUDE_CODE_GIT_BASH_PATH"],
13336
13605
  ...onPath,
13337
- env["ProgramFiles"] === void 0 ? void 0 : join10(env["ProgramFiles"], "Git", "bin", "bash.exe")
13606
+ env["ProgramFiles"] === void 0 ? void 0 : join11(env["ProgramFiles"], "Git", "bin", "bash.exe")
13338
13607
  ];
13339
- return candidates.find((file) => file !== void 0 && file !== "" && existsSync7(file));
13608
+ return candidates.find((file) => file !== void 0 && file !== "" && existsSync8(file));
13340
13609
  }
13341
13610
  function hasGitBash(env) {
13342
13611
  return gitBashPath(env) !== void 0;
13343
13612
  }
13344
13613
  function runWingetInstall() {
13345
- return spawnSync("winget", [...GIT_INSTALL_ARGS], { stdio: "inherit" }).status === 0;
13614
+ return spawnSync2("winget", [...GIT_INSTALL_ARGS], { stdio: "inherit" }).status === 0;
13346
13615
  }
13347
13616
  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.`;
13617
+ 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
13618
  }
13350
13619
  function cannotWriteRefusal(targetRepo) {
13351
13620
  return `cannot write to ${targetRepo} \u2014 run "${ownBareCommand()}" in a folder you can write to.`;
@@ -13365,7 +13634,7 @@ function claimFolderId(targetRepo) {
13365
13634
  function readFolderId(targetRepo) {
13366
13635
  let folderId;
13367
13636
  try {
13368
- const text = readFileSync9(join10(targetRepo, PROJECT_IDENTITY_REL), "utf-8");
13637
+ const text = readFileSync9(join11(targetRepo, PROJECT_IDENTITY_REL), "utf-8");
13369
13638
  folderId = JSON.parse(text)?.projectId;
13370
13639
  } catch {
13371
13640
  return void 0;
@@ -13382,7 +13651,7 @@ function checkSettingsParse(targetRepo) {
13382
13651
  }
13383
13652
 
13384
13653
  // dist/folder-refusals.js
13385
- import { realpathSync as realpathSync3 } from "node:fs";
13654
+ import { realpathSync as realpathSync4 } from "node:fs";
13386
13655
  import { homedir as homedir5 } from "node:os";
13387
13656
  function folderMissingRefusal(targetRepo) {
13388
13657
  return `halfcycle: no such directory: ${targetRepo}`;
@@ -13402,8 +13671,8 @@ function homeFolderRefusal(targetRepo) {
13402
13671
  }
13403
13672
  function isHomeFolder(dir) {
13404
13673
  try {
13405
- const target = realpathSync3(dir);
13406
- const home = realpathSync3(homedir5());
13674
+ const target = realpathSync4(dir);
13675
+ const home = realpathSync4(homedir5());
13407
13676
  return process.platform === "win32" ? target.toLowerCase() === home.toLowerCase() : target === home;
13408
13677
  } catch {
13409
13678
  return false;
@@ -13411,16 +13680,16 @@ function isHomeFolder(dir) {
13411
13680
  }
13412
13681
 
13413
13682
  // 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";
13683
+ import { accessSync, constants, existsSync as existsSync10, readFileSync as readFileSync12, statSync } from "node:fs";
13684
+ import { join as join14 } from "node:path";
13416
13685
 
13417
13686
  // dist/halfcycle-monorepo.js
13418
13687
  import { readFileSync as readFileSync10 } from "node:fs";
13419
- import { join as join11 } from "node:path";
13688
+ import { join as join12 } from "node:path";
13420
13689
  var MONOREPO_PACKAGE_NAME = "halfcycle-monorepo";
13421
13690
  function isHalfcycleMonorepo(dir) {
13422
13691
  try {
13423
- const pkg = JSON.parse(readFileSync10(join11(dir, "package.json"), "utf-8"));
13692
+ const pkg = JSON.parse(readFileSync10(join12(dir, "package.json"), "utf-8"));
13424
13693
  return pkg.name === MONOREPO_PACKAGE_NAME;
13425
13694
  } catch {
13426
13695
  return false;
@@ -13432,8 +13701,8 @@ function isSelfBuildInstallForm(engagementIdArg, engagementTypeArg) {
13432
13701
 
13433
13702
  // dist/local-allow.js
13434
13703
  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";
13704
+ import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync11, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "node:fs";
13705
+ import { dirname as dirname6, join as join13 } from "node:path";
13437
13706
  var LOCAL_SETTINGS_REL2 = ".claude/settings.local.json";
13438
13707
  var LOCAL_ALLOW_RECORD_REL = ".halfcycle/local-allow.json";
13439
13708
  var LOCAL_ALLOW_EDIT_MODE = "acceptEdits";
@@ -13441,7 +13710,6 @@ var SCRIPTS = ["test", "build", "lint", "typecheck"];
13441
13710
  var SCRIPT_RUNNERS = ["npm run", "pnpm", "pnpm run", "yarn", "bun run"];
13442
13711
  var HALFCYCLE_TOOLS_RULE = `mcp__${MCP_SERVER_KEY}`;
13443
13712
  var bash = (command) => `Bash(${command}:*)`;
13444
- var CONTEXT_BUDGET_IGNORE_CHECK = "git check-ignore -q .halfcycle/context-budget.json";
13445
13713
  var COMMIT_FILE_LISTING = "git ls-tree -r --name-only";
13446
13714
  var INSTALLED_HOOK_SCRIPTS = Object.keys(OWNED_GENERATED_HEADERS).filter((path) => path.startsWith(".claude/hooks/"));
13447
13715
  var LOCAL_ALLOW_RULES = [
@@ -13477,7 +13745,6 @@ var LOCAL_ALLOW_RULES = [
13477
13745
  bash("git merge"),
13478
13746
  bash("git worktree add"),
13479
13747
  bash("git worktree list"),
13480
- `Bash(${CONTEXT_BUDGET_IGNORE_CHECK})`,
13481
13748
  bash("git pull --ff-only"),
13482
13749
  bash("git pull --rebase"),
13483
13750
  bash("npm install"),
@@ -13489,6 +13756,8 @@ var LOCAL_ALLOW_RULES = [
13489
13756
  bash("yarn add"),
13490
13757
  bash("bun install"),
13491
13758
  bash("bun add"),
13759
+ bash("gh repo view"),
13760
+ bash("gh issue create"),
13492
13761
  bash("go mod tidy"),
13493
13762
  bash("go get"),
13494
13763
  bash("cargo add"),
@@ -13528,7 +13797,7 @@ var LOCAL_ALLOW_RULES = [
13528
13797
  bash("tr"),
13529
13798
  HALFCYCLE_TOOLS_RULE
13530
13799
  ];
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: ";
13800
+ 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
13801
  var LOCAL_ALLOW_ON_LINE = `Your coding agent can now work without asking on this computer. To undo it, run: ${ownCommand()} allow --remove`;
13533
13802
  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
13803
  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 +13819,8 @@ function trackedByGit(repo) {
13550
13819
  }
13551
13820
  }
13552
13821
  function readRecord2(repo) {
13553
- const path = join12(repo, LOCAL_ALLOW_RECORD_REL);
13554
- if (!existsSync8(path))
13822
+ const path = join13(repo, LOCAL_ALLOW_RECORD_REL);
13823
+ if (!existsSync9(path))
13555
13824
  return null;
13556
13825
  try {
13557
13826
  const raw = JSON.parse(readFileSync11(path, "utf8"));
@@ -13567,12 +13836,12 @@ function readRecord2(repo) {
13567
13836
  }
13568
13837
  }
13569
13838
  function writeRecord(repo, record2) {
13570
- const path = join12(repo, LOCAL_ALLOW_RECORD_REL);
13839
+ const path = join13(repo, LOCAL_ALLOW_RECORD_REL);
13571
13840
  mkdirSync7(dirname6(path), { recursive: true });
13572
13841
  writeFileSync7(path, JSON.stringify({ rules: record2.rules, defaultMode: record2.defaultMode, before: record2.before }, null, 2) + "\n");
13573
13842
  }
13574
13843
  function holdsLocalAllowRecord(repo) {
13575
- return existsSync8(join12(repo, LOCAL_ALLOW_RECORD_REL));
13844
+ return existsSync9(join13(repo, LOCAL_ALLOW_RECORD_REL));
13576
13845
  }
13577
13846
  function layoutOf(text) {
13578
13847
  const eol = text.includes("\r\n") ? "\r\n" : "\n";
@@ -13587,8 +13856,8 @@ function render(value, like) {
13587
13856
  function writeLocalAllow(repo) {
13588
13857
  if (trackedByGit(repo))
13589
13858
  return { kind: "tracked", line: LOCAL_ALLOW_TRACKED_LINE };
13590
- const path = join12(repo, LOCAL_SETTINGS_REL2);
13591
- const existed = existsSync8(path);
13859
+ const path = join13(repo, LOCAL_SETTINGS_REL2);
13860
+ const existed = existsSync9(path);
13592
13861
  const text = existed ? readFileSync11(path, "utf8") : null;
13593
13862
  let settings = {};
13594
13863
  if (text !== null) {
@@ -13631,14 +13900,14 @@ function writeLocalAllow(repo) {
13631
13900
  }
13632
13901
  function removeLocalAllow(repo) {
13633
13902
  const record2 = readRecord2(repo);
13634
- const recordPath = join12(repo, LOCAL_ALLOW_RECORD_REL);
13903
+ const recordPath = join13(repo, LOCAL_ALLOW_RECORD_REL);
13635
13904
  if (record2 === null) {
13636
- if (existsSync8(recordPath))
13637
- rmSync4(recordPath, { force: true });
13905
+ if (existsSync9(recordPath))
13906
+ rmSync5(recordPath, { force: true });
13638
13907
  return { kind: "nothing", line: LOCAL_ALLOW_NOTHING_TO_REMOVE_LINE };
13639
13908
  }
13640
- const path = join12(repo, LOCAL_SETTINGS_REL2);
13641
- if (existsSync8(path)) {
13909
+ const path = join13(repo, LOCAL_SETTINGS_REL2);
13910
+ if (existsSync9(path)) {
13642
13911
  const text = readFileSync11(path, "utf8");
13643
13912
  let parsed = null;
13644
13913
  try {
@@ -13673,13 +13942,13 @@ function removeLocalAllow(repo) {
13673
13942
  if (record2.before !== null && original !== null && sameJson(next, original)) {
13674
13943
  writeFileSync7(path, record2.before);
13675
13944
  } else if (record2.before === null && Object.keys(next).length === 0) {
13676
- rmSync4(path, { force: true });
13945
+ rmSync5(path, { force: true });
13677
13946
  } else {
13678
13947
  writeFileSync7(path, render(next, text));
13679
13948
  }
13680
13949
  }
13681
13950
  }
13682
- rmSync4(recordPath, { force: true });
13951
+ rmSync5(recordPath, { force: true });
13683
13952
  return { kind: "removed", line: LOCAL_ALLOW_REMOVED_LINE };
13684
13953
  }
13685
13954
  function parseObject(text) {
@@ -13693,21 +13962,21 @@ function parseObject(text) {
13693
13962
  }
13694
13963
  }
13695
13964
  function sameJson(a, b) {
13696
- return JSON.stringify(canonical(a)) === JSON.stringify(canonical(b));
13965
+ return JSON.stringify(canonical2(a)) === JSON.stringify(canonical2(b));
13697
13966
  }
13698
- function canonical(value) {
13967
+ function canonical2(value) {
13699
13968
  if (Array.isArray(value))
13700
- return value.map(canonical);
13969
+ return value.map(canonical2);
13701
13970
  if (isObject2(value)) {
13702
- return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonical(value[key])]));
13971
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonical2(value[key])]));
13703
13972
  }
13704
13973
  return value;
13705
13974
  }
13706
13975
  function shouldAskLocalAllow(repo, interactive) {
13707
13976
  if (!interactive || holdsLocalAllowRecord(repo))
13708
13977
  return false;
13709
- const path = join12(repo, LOCAL_SETTINGS_REL2);
13710
- if (!existsSync8(path))
13978
+ const path = join13(repo, LOCAL_SETTINGS_REL2);
13979
+ if (!existsSync9(path))
13711
13980
  return true;
13712
13981
  const parsed = parseObject(readFileSync11(path, "utf8"));
13713
13982
  const allow = isObject2(parsed?.["permissions"]) ? parsed["permissions"]["allow"] : void 0;
@@ -13728,8 +13997,8 @@ function localAllowClosingLine(repo, said) {
13728
13997
  var ENABLED_SERVERS_KEY = "enabledMcpjsonServers";
13729
13998
  var DISABLED_SERVERS_KEY = "disabledMcpjsonServers";
13730
13999
  function registersOwnServer(repo) {
13731
- const path = join12(repo, MCP_REGISTRATION_REL);
13732
- const registration = parseObject(existsSync8(path) ? readFileSync11(path, "utf8") : null);
14000
+ const path = join13(repo, MCP_REGISTRATION_REL);
14001
+ const registration = parseObject(existsSync9(path) ? readFileSync11(path, "utf8") : null);
13733
14002
  const servers = registration?.["mcpServers"];
13734
14003
  return isObject2(servers) && isObject2(servers[MCP_SERVER_KEY]);
13735
14004
  }
@@ -13739,15 +14008,15 @@ function stringsIn(value) {
13739
14008
  function approveOwnServer(repo) {
13740
14009
  const plan = planOwnServerApproval(repo, registersOwnServer(repo));
13741
14010
  if (plan.after !== null) {
13742
- const path = join12(repo, LOCAL_SETTINGS_REL2);
14011
+ const path = join13(repo, LOCAL_SETTINGS_REL2);
13743
14012
  mkdirSync7(dirname6(path), { recursive: true });
13744
14013
  writeFileSync7(path, plan.after);
13745
14014
  }
13746
14015
  return plan.outcome;
13747
14016
  }
13748
14017
  function planOwnServerApproval(repo, registered) {
13749
- const path = join12(repo, LOCAL_SETTINGS_REL2);
13750
- const text = existsSync8(path) ? readFileSync11(path, "utf8") : null;
14018
+ const path = join13(repo, LOCAL_SETTINGS_REL2);
14019
+ const text = existsSync9(path) ? readFileSync11(path, "utf8") : null;
13751
14020
  const unchanged = (outcome) => ({ outcome, before: text, after: null });
13752
14021
  if (!registered)
13753
14022
  return unchanged("no-server");
@@ -13783,8 +14052,8 @@ function planOwnServerApproval(repo, registered) {
13783
14052
  return { outcome: "approved", before: text, after: after === text ? null : after };
13784
14053
  }
13785
14054
  function removeOwnServerApproval(repo) {
13786
- const path = join12(repo, LOCAL_SETTINGS_REL2);
13787
- if (!existsSync8(path) || trackedByGit(repo))
14055
+ const path = join13(repo, LOCAL_SETTINGS_REL2);
14056
+ if (!existsSync9(path) || trackedByGit(repo))
13788
14057
  return false;
13789
14058
  const text = readFileSync11(path, "utf8");
13790
14059
  const settings = parseObject(text);
@@ -13818,7 +14087,7 @@ function removeOwnServerApproval(repo) {
13818
14087
  if (!changed)
13819
14088
  return false;
13820
14089
  if (Object.keys(next).length === 0)
13821
- rmSync4(path, { force: true });
14090
+ rmSync5(path, { force: true });
13822
14091
  else
13823
14092
  writeFileSync7(path, render(next, text));
13824
14093
  return true;
@@ -13879,8 +14148,8 @@ function ownServerRow(targetRepo, plan) {
13879
14148
  }
13880
14149
  }
13881
14150
  function claudeMdLine(targetRepo) {
13882
- const path = join13(targetRepo, CLAUDE_MD_REL);
13883
- if (!existsSync9(path))
14151
+ const path = join14(targetRepo, CLAUDE_MD_REL);
14152
+ if (!existsSync10(path))
13884
14153
  return void 0;
13885
14154
  let text;
13886
14155
  try {
@@ -13891,7 +14160,7 @@ function claudeMdLine(targetRepo) {
13891
14160
  return text.includes(AGENTS_IMPORT_LINE) ? WORDS.claudeMdAlreadyPoints : WORDS.claudeMdWillAdd;
13892
14161
  }
13893
14162
  function previewRefusal(targetRepo, options) {
13894
- if (!existsSync9(targetRepo))
14163
+ if (!existsSync10(targetRepo))
13895
14164
  return folderMissingRefusal(targetRepo);
13896
14165
  if (!statSync(targetRepo).isDirectory())
13897
14166
  return folderIsFileRefusal(targetRepo);
@@ -13912,7 +14181,7 @@ function previewRefusal(targetRepo, options) {
13912
14181
  } catch {
13913
14182
  return cannotWriteRefusal(targetRepo);
13914
14183
  }
13915
- if (existsSync9(join13(targetRepo, PROJECT_IDENTITY_REL)) && readFolderId(targetRepo) === void 0) {
14184
+ if (existsSync10(join14(targetRepo, PROJECT_IDENTITY_REL)) && readFolderId(targetRepo) === void 0) {
13916
14185
  return unreadableProjectRefusal(targetRepo);
13917
14186
  }
13918
14187
  try {
@@ -13972,10 +14241,10 @@ function renderPreview(plan, missingGitForWindows, claudeMdWords, ownServer) {
13972
14241
  }
13973
14242
 
13974
14243
  // 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";
14244
+ import { spawnSync as spawnSync3 } from "node:child_process";
14245
+ import { existsSync as existsSync11, lstatSync as lstatSync3, readFileSync as readFileSync13, realpathSync as realpathSync5, statSync as statSync2 } from "node:fs";
13977
14246
  import { homedir as homedir6 } from "node:os";
13978
- import { join as join14 } from "node:path";
14247
+ import { join as join15 } from "node:path";
13979
14248
  var GUARD_RUNNER_REL2 = ".claude/hooks/guard-runner.sh";
13980
14249
  var CONNECTION_VERB = "check-connection";
13981
14250
  var WRAPPER_TIMEOUT_MS = 3e4;
@@ -14014,7 +14283,7 @@ function runDoctor(root, options) {
14014
14283
  };
14015
14284
  }
14016
14285
  function setUpLine(root, upToDate) {
14017
- if (!existsSync10(join14(root, BUNDLE_PIN_REL))) {
14286
+ if (!existsSync11(join15(root, BUNDLE_PIN_REL))) {
14018
14287
  return {
14019
14288
  state: "no",
14020
14289
  hooksMissing: false,
@@ -14028,9 +14297,9 @@ function setUpLine(root, upToDate) {
14028
14297
  const missing = [];
14029
14298
  if (!pinNamesProject(root))
14030
14299
  missing.push("Halfcycle's project record");
14031
- if (!existsSync10(join14(root, INSTALL_MANIFEST_REL)))
14300
+ if (!existsSync11(join15(root, INSTALL_MANIFEST_REL)))
14032
14301
  missing.push("the install record");
14033
- if (!existsSync10(join14(root, VENDORED_BIN_REL)))
14302
+ if (!existsSync11(join15(root, VENDORED_BIN_REL)))
14034
14303
  missing.push("Halfcycle's check program");
14035
14304
  if (!mcpNamesServer(root))
14036
14305
  missing.push("Halfcycle's server entry");
@@ -14056,14 +14325,14 @@ function pinNamesProject(root) {
14056
14325
  }
14057
14326
  }
14058
14327
  function mcpNamesServer(root) {
14059
- const parsed = readJson(join14(root, MCP_REGISTRATION_REL));
14328
+ const parsed = readJson(join15(root, MCP_REGISTRATION_REL));
14060
14329
  if (!isObject3(parsed) || !isObject3(parsed["mcpServers"]))
14061
14330
  return false;
14062
14331
  const entry = parsed["mcpServers"][MCP_SERVER_KEY];
14063
14332
  return isObject3(entry) && typeof entry["url"] === "string";
14064
14333
  }
14065
14334
  function settingsRegisterEveryCommand(root) {
14066
- const parsed = readJson(join14(root, SETTINGS_REL));
14335
+ const parsed = readJson(join15(root, SETTINGS_REL));
14067
14336
  if (!isObject3(parsed) || !isObject3(parsed["hooks"]))
14068
14337
  return false;
14069
14338
  const registered = /* @__PURE__ */ new Set();
@@ -14094,10 +14363,10 @@ function startWrapper(root, options) {
14094
14363
  }
14095
14364
  shell = gitBash;
14096
14365
  }
14097
- const wrapper = join14(root, GUARD_RUNNER_REL2);
14098
- if (!existsSync10(wrapper))
14366
+ const wrapper = join15(root, GUARD_RUNNER_REL2);
14367
+ if (!existsSync11(wrapper))
14099
14368
  return failed(repairSentence());
14100
- const run = spawnSync2(shell, [wrapper, CONNECTION_VERB], {
14369
+ const run = spawnSync3(shell, [wrapper, CONNECTION_VERB], {
14101
14370
  cwd: root,
14102
14371
  env: { ...options.env, CLAUDE_PROJECT_DIR: root },
14103
14372
  encoding: "utf-8",
@@ -14122,7 +14391,7 @@ function startWrapper(root, options) {
14122
14391
  }
14123
14392
  function settingsDisablingHooks(root) {
14124
14393
  for (const rel of [SETTINGS_REL, LOCAL_SETTINGS_REL]) {
14125
- const parsed = readJson(join14(root, rel));
14394
+ const parsed = readJson(join15(root, rel));
14126
14395
  if (isObject3(parsed) && parsed["disableAllHooks"] === true)
14127
14396
  return rel;
14128
14397
  }
@@ -14212,8 +14481,8 @@ function folderTrustLine(root, options) {
14212
14481
  if (!mcpNamesServer(root))
14213
14482
  return void 0;
14214
14483
  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 });
14484
+ const stateFile = join15(configFolder !== void 0 && configFolder !== "" ? configFolder : options.home ?? homedir6(), CLAUDE_STATE_FILENAME);
14485
+ const trust = trustOfWorkingFolder(root, stateFile, options.platform, { lstatSync: lstatSync3, readFileSync: readFileSync13, realpathSync: realpathSync5 });
14217
14486
  return trust === "not-trusted" ? { ok: true, text: `Folder trust: ${FOLDER_NOT_TRUSTED_SENTENCE}` } : void 0;
14218
14487
  }
14219
14488
  function signInCodeLine(root, running, now, options) {
@@ -14287,7 +14556,7 @@ var CODEX_EVENT_SNAKE = {
14287
14556
  };
14288
14557
  function codexHomeDir(options) {
14289
14558
  const codexHome = options.env["CODEX_HOME"];
14290
- return codexHome !== void 0 && codexHome !== "" ? codexHome : join14(homedir6(), ".codex");
14559
+ return codexHome !== void 0 && codexHome !== "" ? codexHome : join15(homedir6(), ".codex");
14291
14560
  }
14292
14561
  function isDirectory(path) {
14293
14562
  try {
@@ -14297,8 +14566,8 @@ function isDirectory(path) {
14297
14566
  }
14298
14567
  }
14299
14568
  function codexLine(root, options) {
14300
- const hooksPath = join14(root, CODEX_HOOKS_REL2);
14301
- if (!existsSync10(hooksPath))
14569
+ const hooksPath = join15(root, CODEX_HOOKS_REL2);
14570
+ if (!existsSync11(hooksPath))
14302
14571
  return void 0;
14303
14572
  if (!isDirectory(codexHomeDir(options)))
14304
14573
  return void 0;
@@ -14340,7 +14609,7 @@ function codexEntryPositions(hooksPath) {
14340
14609
  return positions;
14341
14610
  }
14342
14611
  function codexApprovalStatus(root, positions, options) {
14343
- const configPath = join14(codexHomeDir(options), "config.toml");
14612
+ const configPath = join15(codexHomeDir(options), "config.toml");
14344
14613
  let text;
14345
14614
  try {
14346
14615
  text = readFileSync13(configPath, "utf-8");
@@ -14349,7 +14618,7 @@ function codexApprovalStatus(root, positions, options) {
14349
14618
  }
14350
14619
  let hooksPath;
14351
14620
  try {
14352
- hooksPath = join14(realpathSync4(root), CODEX_HOOKS_REL2);
14621
+ hooksPath = join15(realpathSync5(root), CODEX_HOOKS_REL2);
14353
14622
  } catch {
14354
14623
  return "not-approved";
14355
14624
  }
@@ -14379,7 +14648,7 @@ function naturalList(items) {
14379
14648
  return `${items.slice(0, -1).join(", ")} and ${items[items.length - 1]}`;
14380
14649
  }
14381
14650
  function readJson(path) {
14382
- if (!existsSync10(path))
14651
+ if (!existsSync11(path))
14383
14652
  return void 0;
14384
14653
  try {
14385
14654
  return JSON.parse(readFileSync13(path, "utf-8"));
@@ -14393,8 +14662,8 @@ function isObject3(value) {
14393
14662
 
14394
14663
  // dist/claude-version-probe.js
14395
14664
  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";
14665
+ import { existsSync as existsSync12, realpathSync as realpathSync6 } from "node:fs";
14666
+ import { delimiter as delimiter2, join as join16 } from "node:path";
14398
14667
  function execVersionCommand(command, timeoutMs, deps) {
14399
14668
  const platform6 = deps.platform ?? process.platform;
14400
14669
  const exec = deps.exec ?? execFileSync4;
@@ -14437,10 +14706,10 @@ var INSTALL_METHOD_RULES = [
14437
14706
  function probeClaudeCodeInstallMethod(deps = {}) {
14438
14707
  const env = deps.env ?? process.env;
14439
14708
  const platform6 = deps.platform ?? process.platform;
14440
- const exists = deps.exists ?? existsSync11;
14709
+ const exists = deps.exists ?? existsSync12;
14441
14710
  const realpath = deps.realpath ?? ((file) => {
14442
14711
  try {
14443
- return realpathSync5(file);
14712
+ return realpathSync6(file);
14444
14713
  } catch {
14445
14714
  return file;
14446
14715
  }
@@ -14449,7 +14718,7 @@ function probeClaudeCodeInstallMethod(deps = {}) {
14449
14718
  const dirs = (env["PATH"] ?? "").split(delimiter2).filter((dir) => dir !== "");
14450
14719
  for (const dir of dirs) {
14451
14720
  for (const name of names) {
14452
- const file = join15(dir, name);
14721
+ const file = join16(dir, name);
14453
14722
  if (!exists(file))
14454
14723
  continue;
14455
14724
  const real = realpath(file);
@@ -14484,8 +14753,8 @@ function codexInstallLine(codex) {
14484
14753
  }
14485
14754
 
14486
14755
  // 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";
14756
+ import { existsSync as existsSync13, mkdirSync as mkdirSync8, rmSync as rmSync7 } from "node:fs";
14757
+ import { basename as basename2, join as join17, resolve as resolve4, relative as relative3, isAbsolute as isAbsolute4 } from "node:path";
14489
14758
 
14490
14759
  // dist/build-record/render-v2.js
14491
14760
  var HIDDEN = /[\p{Cc}\p{Zl}\p{Zp}\p{Default_Ignorable_Code_Point}]/u;
@@ -14831,6 +15100,20 @@ function leftForYouLines(left, p) {
14831
15100
  }
14832
15101
  return lines;
14833
15102
  }
15103
+ var TASKS_FILE_LINK = "../tasks.md";
15104
+ function ticketLines(tickets, p) {
15105
+ if (tickets === void 0)
15106
+ return [];
15107
+ const lines = ["- Tickets:"];
15108
+ for (const entry of tickets.entries) {
15109
+ lines.push(` - ${entry.ref} ${p.text(entry.title)}`);
15110
+ if (entry.url !== null)
15111
+ lines.push(` - Link: [${entry.ref}](${entry.url})`);
15112
+ else if (entry.ref === TASKS_FILE_PATH)
15113
+ lines.push(` - Link: [${entry.ref}](${TASKS_FILE_LINK})`);
15114
+ }
15115
+ return lines;
15116
+ }
14834
15117
  function shownBy(steps) {
14835
15118
  const named2 = steps.filter((s) => s.name !== void 0).length;
14836
15119
  if (named2 === 0)
@@ -14904,6 +15187,7 @@ function renderClose(heading, observed, attested, checks, p, closingRound = fals
14904
15187
  `- Signed in as: ${signedInAs(observed.closedBy, p)}`,
14905
15188
  ...outstanding(observed.crossed, p),
14906
15189
  ...leftForYouLines(observed.leftForOwner, p),
15190
+ ...ticketLines(observed.tickets, p),
14907
15191
  machines(observed.seatCoverage, checks)
14908
15192
  ].join("\n"),
14909
15193
  STATED_LABEL,
@@ -15076,7 +15360,7 @@ function renderBuildRecordV2Markdown(record2) {
15076
15360
  }
15077
15361
 
15078
15362
  // 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";
15363
+ import { closeSync, openSync, readFileSync as readFileSync14, renameSync as renameSync2, rmSync as rmSync6, writeSync as writeSync2 } from "node:fs";
15080
15364
  function createExclusiveOnDisk(path, data) {
15081
15365
  const bytes = typeof data === "string" ? Buffer.from(data, "utf-8") : data;
15082
15366
  const fd = openSync(path, "wx");
@@ -15087,7 +15371,7 @@ function createExclusiveOnDisk(path, data) {
15087
15371
  }
15088
15372
  } catch (err) {
15089
15373
  closeSync(fd);
15090
- rmSync5(path, { force: true });
15374
+ rmSync6(path, { force: true });
15091
15375
  throw err;
15092
15376
  }
15093
15377
  closeSync(fd);
@@ -15121,28 +15405,28 @@ function createPairExclusive(io, jsonPath, jsonText, mdPath, mdText) {
15121
15405
  return "created";
15122
15406
  } catch (err) {
15123
15407
  if (createdJson)
15124
- rmSync5(jsonPath, { force: true });
15408
+ rmSync6(jsonPath, { force: true });
15125
15409
  if (!isEexist(err))
15126
15410
  throw err;
15127
15411
  return "exists";
15128
15412
  }
15129
15413
  }
15130
15414
  function replaceOne(io, tmp, finalPath, text) {
15131
- rmSync5(tmp, { force: true });
15415
+ rmSync6(tmp, { force: true });
15132
15416
  try {
15133
15417
  io.createExclusive(tmp, text);
15134
15418
  io.rename(tmp, finalPath);
15135
15419
  } catch (err) {
15136
- rmSync5(tmp, { force: true });
15420
+ rmSync6(tmp, { force: true });
15137
15421
  throw err;
15138
15422
  }
15139
15423
  }
15140
15424
 
15141
15425
  // dist/build-record/write.js
15142
- var BUILD_RECORD_DIR = join16("docs", "build-records");
15426
+ var BUILD_RECORD_DIR = join17("docs", "build-records");
15143
15427
  function assertInsideOutDir(outDir, candidate, identity) {
15144
- const rel = relative2(outDir, resolve3(candidate));
15145
- const isPlainFilename = rel !== "" && rel !== ".." && !isAbsolute3(rel) && rel === basename2(rel);
15428
+ const rel = relative3(outDir, resolve4(candidate));
15429
+ const isPlainFilename = rel !== "" && rel !== ".." && !isAbsolute4(rel) && rel === basename2(rel);
15146
15430
  if (!isPlainFilename) {
15147
15431
  throw new InvalidPhaseIdentityError(identity, BUILD_RECORD_DIR);
15148
15432
  }
@@ -15175,10 +15459,10 @@ function writeRecordPair(repoRoot, phase, rawRecord, record2, io = NODE_RECORD_P
15175
15459
  if (!isValidPhaseIdentity(phase)) {
15176
15460
  throw new InvalidPhaseIdentityError(phase, BUILD_RECORD_DIR);
15177
15461
  }
15178
- const outDir = resolve3(repoRoot, BUILD_RECORD_DIR);
15462
+ const outDir = resolve4(repoRoot, BUILD_RECORD_DIR);
15179
15463
  const segment = renderPhaseSegment(phase);
15180
- const jsonPath = join16(outDir, `${segment}.json`);
15181
- const mdPath = join16(outDir, `${segment}.md`);
15464
+ const jsonPath = join17(outDir, `${segment}.json`);
15465
+ const mdPath = join17(outDir, `${segment}.md`);
15182
15466
  const jsonTmp = `${jsonPath}.tmp`;
15183
15467
  const mdTmp = `${mdPath}.tmp`;
15184
15468
  for (const candidate of [jsonPath, mdPath, jsonTmp, mdTmp]) {
@@ -15192,7 +15476,7 @@ function writeRecordPair(repoRoot, phase, rawRecord, record2, io = NODE_RECORD_P
15192
15476
  }
15193
15477
  const existingJson = readIfPresent(jsonPath);
15194
15478
  if (existingJson === null) {
15195
- return { kind: "in-the-way", path: existsSync12(jsonPath) ? jsonPath : mdPath };
15479
+ return { kind: "in-the-way", path: existsSync13(jsonPath) ? jsonPath : mdPath };
15196
15480
  }
15197
15481
  if (existingJson === jsonText) {
15198
15482
  replaceOne(io, mdTmp, mdPath, mdText);
@@ -15208,7 +15492,7 @@ function writeRecordPair(repoRoot, phase, rawRecord, record2, io = NODE_RECORD_P
15208
15492
  try {
15209
15493
  replaceOne(io, jsonTmp, jsonPath, existingJson);
15210
15494
  } catch {
15211
- rmSync6(jsonPath, { force: true });
15495
+ rmSync7(jsonPath, { force: true });
15212
15496
  }
15213
15497
  throw err;
15214
15498
  }
@@ -15216,24 +15500,24 @@ function writeRecordPair(repoRoot, phase, rawRecord, record2, io = NODE_RECORD_P
15216
15500
  }
15217
15501
 
15218
15502
  // dist/build-record/obtain.js
15219
- import { relative as relative5 } from "node:path";
15503
+ import { relative as relative6 } from "node:path";
15220
15504
 
15221
15505
  // dist/open-phase.js
15222
15506
  import { readFileSync as readFileSync20 } from "node:fs";
15223
- import { join as join24 } from "node:path";
15507
+ import { join as join25 } from "node:path";
15224
15508
 
15225
15509
  // dist/close-phase.js
15226
15510
  import { execFile, execFileSync as execFileSync6 } from "node:child_process";
15227
15511
  import { readFileSync as readFileSync19 } from "node:fs";
15228
- import { join as join23 } from "node:path";
15512
+ import { join as join24 } from "node:path";
15229
15513
 
15230
15514
  // dist/plan.js
15231
- import { existsSync as existsSync14, readFileSync as readFileSync18 } from "node:fs";
15232
- import { join as join22 } from "node:path";
15515
+ import { existsSync as existsSync15, readFileSync as readFileSync18 } from "node:fs";
15516
+ import { join as join23 } from "node:path";
15233
15517
 
15234
15518
  // 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";
15519
+ 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";
15520
+ import { dirname as dirname7, isAbsolute as isAbsolute5, join as join18, relative as relative4, resolve as resolve5, sep as sep2 } from "node:path";
15237
15521
  var DOCS_DIR = "docs";
15238
15522
  var DIAGRAMS_DIR = "diagrams";
15239
15523
  var DIAGRAM_DIR_REL = `${DOCS_DIR}/${DIAGRAMS_DIR}`;
@@ -15288,10 +15572,10 @@ function embedImageLink(base, doc) {
15288
15572
  return `${up}${DIAGRAMS_DIR}/${markdownLinkName(embedImageName(base))}`;
15289
15573
  }
15290
15574
  function diagramPaths(repoRoot, base) {
15291
- const root = resolve4(repoRoot);
15292
- const dir = join17(root, DOCS_DIR, DIAGRAMS_DIR);
15575
+ const root = resolve5(repoRoot);
15576
+ const dir = join18(root, DOCS_DIR, DIAGRAMS_DIR);
15293
15577
  const names = fileNames(base);
15294
- const file = Object.fromEntries(DIAGRAM_FILES.map((which) => [which, join17(dir, names[which])]));
15578
+ const file = Object.fromEntries(DIAGRAM_FILES.map((which) => [which, join18(dir, names[which])]));
15295
15579
  return { repoRoot: root, dir, file };
15296
15580
  }
15297
15581
  function diagramRel(base, which) {
@@ -15308,19 +15592,19 @@ function spokenList(items) {
15308
15592
  }
15309
15593
  function lstatOrNull2(path) {
15310
15594
  try {
15311
- return lstatSync3(path);
15595
+ return lstatSync4(path);
15312
15596
  } catch {
15313
15597
  return null;
15314
15598
  }
15315
15599
  }
15316
- function isInside(outer, inner) {
15317
- const rel = relative3(outer, inner);
15318
- return rel === "" || !isAbsolute4(rel) && rel !== ".." && !rel.startsWith(`..${sep}`);
15600
+ function isInside2(outer, inner) {
15601
+ const rel = relative4(outer, inner);
15602
+ return rel === "" || !isAbsolute5(rel) && rel !== ".." && !rel.startsWith(`..${sep2}`);
15319
15603
  }
15320
15604
  function checkDiagramPlace(paths) {
15321
15605
  let root;
15322
15606
  try {
15323
- root = realpathSync6(paths.repoRoot);
15607
+ root = realpathSync7(paths.repoRoot);
15324
15608
  } catch {
15325
15609
  return { ok: false, why: `the folder ${paths.repoRoot} could not be opened. Check the path, then run this again.` };
15326
15610
  }
@@ -15333,14 +15617,14 @@ function checkDiagramPlace(paths) {
15333
15617
  }
15334
15618
  let real;
15335
15619
  try {
15336
- real = realpathSync6(ancestor);
15620
+ real = realpathSync7(ancestor);
15337
15621
  } catch {
15338
15622
  return {
15339
15623
  ok: false,
15340
15624
  why: `${DIAGRAM_DIR_REL} leads to a place that does not exist. Remove the link in the way, then run this again.`
15341
15625
  };
15342
15626
  }
15343
- if (!isInside(root, real)) {
15627
+ if (!isInside2(root, real)) {
15344
15628
  return {
15345
15629
  ok: false,
15346
15630
  why: `${DIAGRAM_DIR_REL} leads outside this repository, so nothing was written. Remove the link in the way, then run this again.`
@@ -15348,12 +15632,12 @@ function checkDiagramPlace(paths) {
15348
15632
  }
15349
15633
  let isFolder = false;
15350
15634
  try {
15351
- isFolder = lstatSync3(real).isDirectory();
15635
+ isFolder = lstatSync4(real).isDirectory();
15352
15636
  } catch {
15353
15637
  isFolder = false;
15354
15638
  }
15355
15639
  if (!isFolder) {
15356
- const name = relative3(root, real).split("\\").join("/");
15640
+ const name = relative4(root, real).split("\\").join("/");
15357
15641
  return {
15358
15642
  ok: false,
15359
15643
  why: `${name === "" ? "this repository" : name} is not a folder, so ${DIAGRAM_DIR_REL} cannot be made. Move it, then run this again.`
@@ -15468,7 +15752,7 @@ function checkPictures(drawn, base) {
15468
15752
  }
15469
15753
  function removeQuietly(path) {
15470
15754
  try {
15471
- rmSync7(path, { force: true });
15755
+ rmSync8(path, { force: true });
15472
15756
  } catch {
15473
15757
  return;
15474
15758
  }
@@ -15534,10 +15818,10 @@ function restore(io, paths, renamed, existing) {
15534
15818
  const tmp = tempPath(paths, which);
15535
15819
  try {
15536
15820
  if (previous === null) {
15537
- rmSync7(finalPath, { force: true });
15821
+ rmSync8(finalPath, { force: true });
15538
15822
  continue;
15539
15823
  }
15540
- rmSync7(tmp, { force: true });
15824
+ rmSync8(tmp, { force: true });
15541
15825
  io.createExclusive(tmp, previous);
15542
15826
  io.rename(tmp, finalPath);
15543
15827
  } catch {
@@ -15564,7 +15848,7 @@ function isMissingRoute(method, status, body) {
15564
15848
  }
15565
15849
 
15566
15850
  // dist/change.js
15567
- import { join as join20 } from "node:path";
15851
+ import { join as join21 } from "node:path";
15568
15852
 
15569
15853
  // dist/cli-contract.js
15570
15854
  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 +16093,11 @@ function placeholderRefusal(verb, tokens) {
15809
16093
  // dist/system-report.js
15810
16094
  import { execFileSync as execFileSync5 } from "node:child_process";
15811
16095
  import { readFileSync as readFileSync16 } from "node:fs";
15812
- import { join as join18 } from "node:path";
16096
+ import { join as join19 } from "node:path";
15813
16097
  function readDescriptionFile(repoRoot) {
15814
16098
  let text;
15815
16099
  try {
15816
- text = readFileSync16(join18(repoRoot, SYSTEM_DESCRIPTION_REL), "utf-8");
16100
+ text = readFileSync16(join19(repoRoot, SYSTEM_DESCRIPTION_REL), "utf-8");
15817
16101
  } catch (err) {
15818
16102
  const code = err.code;
15819
16103
  return code === "ENOENT" || code === "ENOTDIR" ? { kind: "missing" } : { kind: "unreadable" };
@@ -16037,8 +16321,8 @@ async function runSystem(repoRoot, deps) {
16037
16321
  }
16038
16322
 
16039
16323
  // 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";
16324
+ import { existsSync as existsSync14, mkdirSync as mkdirSync10 } from "node:fs";
16325
+ import { basename as basename3, isAbsolute as isAbsolute6, join as join20, relative as relative5, resolve as resolve6 } from "node:path";
16042
16326
 
16043
16327
  // dist/build-record/render-change.js
16044
16328
  function counted2(n, one, many) {
@@ -16183,7 +16467,7 @@ function renderChangeRecordMarkdown(record2) {
16183
16467
  }
16184
16468
 
16185
16469
  // dist/build-record/write-change.js
16186
- var CHANGE_RECORD_DIR = join19("docs", "build-records", "changes");
16470
+ var CHANGE_RECORD_DIR = join20("docs", "build-records", "changes");
16187
16471
  var InvalidChangeIdError = class extends Error {
16188
16472
  id;
16189
16473
  constructor(id) {
@@ -16193,8 +16477,8 @@ var InvalidChangeIdError = class extends Error {
16193
16477
  }
16194
16478
  };
16195
16479
  function assertInsideChangeDir(outDir, candidate, id) {
16196
- const rel = relative4(outDir, resolve5(candidate));
16197
- const isPlainFilename = rel !== "" && rel !== ".." && !isAbsolute5(rel) && rel === basename3(rel);
16480
+ const rel = relative5(outDir, resolve6(candidate));
16481
+ const isPlainFilename = rel !== "" && rel !== ".." && !isAbsolute6(rel) && rel === basename3(rel);
16198
16482
  if (!isPlainFilename)
16199
16483
  throw new InvalidChangeIdError(id);
16200
16484
  }
@@ -16202,9 +16486,9 @@ function writeChangeRecordPair(repoRoot, sortingId, rawRecord, record2, io = NOD
16202
16486
  const checked = sortingIdSchema.safeParse(sortingId);
16203
16487
  if (!checked.success)
16204
16488
  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`);
16489
+ const outDir = resolve6(repoRoot, CHANGE_RECORD_DIR);
16490
+ const jsonPath = join20(outDir, `${checked.data}.json`);
16491
+ const mdPath = join20(outDir, `${checked.data}.md`);
16208
16492
  const mdTmp = `${mdPath}.tmp`;
16209
16493
  for (const candidate of [jsonPath, mdPath, mdTmp]) {
16210
16494
  assertInsideChangeDir(outDir, candidate, sortingId);
@@ -16217,7 +16501,7 @@ function writeChangeRecordPair(repoRoot, sortingId, rawRecord, record2, io = NOD
16217
16501
  }
16218
16502
  const existingJson = readIfPresent(jsonPath);
16219
16503
  if (existingJson === null) {
16220
- return { kind: "in-the-way", path: existsSync13(jsonPath) ? jsonPath : mdPath };
16504
+ return { kind: "in-the-way", path: existsSync14(jsonPath) ? jsonPath : mdPath };
16221
16505
  }
16222
16506
  if (existingJson !== jsonText)
16223
16507
  return { kind: "in-the-way", path: jsonPath };
@@ -16480,7 +16764,7 @@ function writePairLines(deps, repoRoot, sortingId, found) {
16480
16764
  };
16481
16765
  }
16482
16766
  return {
16483
- stdout: `[halfcycle] Its record is in ${join20(CHANGE_RECORD_DIR, `${sortingId}.md`)}.
16767
+ stdout: `[halfcycle] Its record is in ${join21(CHANGE_RECORD_DIR, `${sortingId}.md`)}.
16484
16768
  `,
16485
16769
  stderr: "",
16486
16770
  ok: true
@@ -16721,12 +17005,12 @@ function runChange(repoRoot, args2, deps) {
16721
17005
  // dist/shown-record.js
16722
17006
  import { chmodSync as chmodSync3, mkdirSync as mkdirSync11, readFileSync as readFileSync17, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync8 } from "node:fs";
16723
17007
  import { platform as platform4 } from "node:os";
16724
- import { join as join21 } from "node:path";
17008
+ import { join as join22 } from "node:path";
16725
17009
  function updateShownRecord(engagementId, update, home) {
16726
17010
  let temp = null;
16727
17011
  try {
16728
17012
  const dir = engagementStateDir(engagementId, home);
16729
- const path = join21(dir, SHOWN_PLANS_FILENAME);
17013
+ const path = join22(dir, SHOWN_PLANS_FILENAME);
16730
17014
  let current;
16731
17015
  try {
16732
17016
  current = parseShownRecord(readFileSync17(path, "utf-8"));
@@ -16736,7 +17020,7 @@ function updateShownRecord(engagementId, update, home) {
16736
17020
  const next = update(current);
16737
17021
  const body = serialiseShownRecord(next);
16738
17022
  mkdirSync11(dir, { recursive: true, mode: 448 });
16739
- temp = join21(dir, `.${SHOWN_PLANS_FILENAME}.${process.pid}.tmp`);
17023
+ temp = join22(dir, `.${SHOWN_PLANS_FILENAME}.${process.pid}.tmp`);
16740
17024
  writeFileSync8(temp, body, { mode: 384 });
16741
17025
  if (platform4() !== "win32")
16742
17026
  chmodSync3(temp, 384);
@@ -16813,7 +17097,7 @@ function readAndCheckPlan(repoRoot, phase, description, verb) {
16813
17097
  const fileRel = planFileRel(phase);
16814
17098
  let text;
16815
17099
  try {
16816
- text = readFileSync18(join22(repoRoot, fileRel), "utf-8");
17100
+ text = readFileSync18(join23(repoRoot, fileRel), "utf-8");
16817
17101
  } catch (err) {
16818
17102
  const code = err.code;
16819
17103
  if (code === "ENOENT" || code === "ENOTDIR") {
@@ -16969,7 +17253,7 @@ function removalOwedLines(verb, parts) {
16969
17253
  function redrawLine(repoRoot, phase) {
16970
17254
  let exists;
16971
17255
  try {
16972
- exists = existsSync14(join22(repoRoot, DIAGRAM_DIR_REL, embedImageName(phaseDiagramBase(phase))));
17256
+ exists = existsSync15(join23(repoRoot, DIAGRAM_DIR_REL, embedImageName(phaseDiagramBase(phase))));
16973
17257
  } catch {
16974
17258
  return null;
16975
17259
  }
@@ -17553,7 +17837,7 @@ function readObserveLine(stdout) {
17553
17837
  function runRunnerChild(binary, args2, credential, repoRoot, readLine, fallback) {
17554
17838
  if (binary instanceof Error)
17555
17839
  return Promise.resolve(fallback);
17556
- return new Promise((resolve9) => {
17840
+ return new Promise((resolve10) => {
17557
17841
  try {
17558
17842
  execFile(process.execPath, [binary, ...args2], {
17559
17843
  cwd: repoRoot,
@@ -17563,10 +17847,10 @@ function runRunnerChild(binary, args2, credential, repoRoot, readLine, fallback)
17563
17847
  windowsHide: true
17564
17848
  }, (err, stdout) => {
17565
17849
  const stopped = err !== null && err.killed === true;
17566
- resolve9(stopped || typeof stdout !== "string" ? fallback : readLine(stdout));
17850
+ resolve10(stopped || typeof stdout !== "string" ? fallback : readLine(stdout));
17567
17851
  });
17568
17852
  } catch {
17569
- resolve9(fallback);
17853
+ resolve10(fallback);
17570
17854
  }
17571
17855
  });
17572
17856
  }
@@ -17929,7 +18213,7 @@ var PHASE_MODELS_FILENAME = "phase-models.json";
17929
18213
  var PACING_FOLDER = "pacing";
17930
18214
  function pacingFile(engagementId, name, home) {
17931
18215
  try {
17932
- return join23(engagementStateDir(engagementId, home), PACING_FOLDER, name);
18216
+ return join24(engagementStateDir(engagementId, home), PACING_FOLDER, name);
17933
18217
  } catch {
17934
18218
  return null;
17935
18219
  }
@@ -18101,7 +18385,7 @@ function readRepoCredential(repoRoot, env = process.env, home) {
18101
18385
  if (pinned !== null)
18102
18386
  assertEngagementId(pinned);
18103
18387
  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);
18388
+ const looked = pinned === null ? `${join25(repoRoot, ".halfcycle", "bundle.json")} (which names no engagement)` : engagementEnvPath(pinned, home);
18105
18389
  const values = {};
18106
18390
  for (const key of OPEN_PHASE_ENV_KEYS) {
18107
18391
  const value = (env[key] ?? fromStore?.[key] ?? "").trim();
@@ -18118,7 +18402,7 @@ function readRepoCredential(repoRoot, env = process.env, home) {
18118
18402
  }
18119
18403
  function readPinnedEngagementId(repoRoot) {
18120
18404
  try {
18121
- const pin = JSON.parse(readFileSync20(join24(repoRoot, ".halfcycle", "bundle.json"), "utf-8"));
18405
+ const pin = JSON.parse(readFileSync20(join25(repoRoot, ".halfcycle", "bundle.json"), "utf-8"));
18122
18406
  const id = pin[PIN_ENGAGEMENT_ID_FIELD];
18123
18407
  return typeof id === "string" && id !== "" ? id : null;
18124
18408
  } catch {
@@ -18268,12 +18552,12 @@ async function obtainBuildRecord(credential, repoRoot, phase, io = NODE_RECORD_P
18268
18552
  };
18269
18553
  }
18270
18554
  if (result.kind === "in-the-way") {
18271
- return { kind: "exists", path: relative5(repoRoot, result.path) };
18555
+ return { kind: "exists", path: relative6(repoRoot, result.path) };
18272
18556
  }
18273
18557
  return {
18274
18558
  kind: "written",
18275
- jsonPath: relative5(repoRoot, result.jsonPath),
18276
- mdPath: relative5(repoRoot, result.mdPath),
18559
+ jsonPath: relative6(repoRoot, result.jsonPath),
18560
+ mdPath: relative6(repoRoot, result.mdPath),
18277
18561
  headline: latestCloseHeadline(answer.record),
18278
18562
  stampFailure: clearStampFor(credential.engagementId, phase, home)
18279
18563
  };
@@ -18606,7 +18890,7 @@ async function writeDrawOutcome(repoRoot, deps, outcome, target, warnings, canno
18606
18890
 
18607
18891
  // dist/check-tasks.js
18608
18892
  import { readFileSync as readFileSync21 } from "node:fs";
18609
- import { join as join25, resolve as resolve6 } from "node:path";
18893
+ import { join as join26, resolve as resolve7 } from "node:path";
18610
18894
  var CHECK_TASKS_USAGE = `${ownCommand()} check-tasks <phase> [--with-task-list] [--repo <root>]`;
18611
18895
  function parseCheckTasksArgs(argv) {
18612
18896
  const positionals = [];
@@ -18647,7 +18931,7 @@ function issuePlace(rel, path) {
18647
18931
  function readJsonFile(repoRoot, rel, parse) {
18648
18932
  let text;
18649
18933
  try {
18650
- text = readFileSync21(resolve6(repoRoot, rel), "utf-8");
18934
+ text = readFileSync21(resolve7(repoRoot, rel), "utf-8");
18651
18935
  } catch (err) {
18652
18936
  const code = err.code;
18653
18937
  if (code === "ENOENT" || code === "ENOTDIR")
@@ -18705,7 +18989,7 @@ function readPhasePlanParts(repoRoot, phase, description) {
18705
18989
  const file = planFileRel(phase);
18706
18990
  let text;
18707
18991
  try {
18708
- text = readFileSync21(join25(repoRoot, file), "utf-8");
18992
+ text = readFileSync21(join26(repoRoot, file), "utf-8");
18709
18993
  } catch (err) {
18710
18994
  const code = err.code;
18711
18995
  if (code === "ENOENT" || code === "ENOTDIR")
@@ -18738,7 +19022,7 @@ function readPlanSpecFiles(repoRoot, plan) {
18738
19022
  const rel = `${SPEC_FOLDER}/${slug}.md`;
18739
19023
  let text;
18740
19024
  try {
18741
- text = readFileSync21(join25(repoRoot, rel), "utf-8");
19025
+ text = readFileSync21(join26(repoRoot, rel), "utf-8");
18742
19026
  } catch (err) {
18743
19027
  const code = err.code;
18744
19028
  if (code === "ENOENT" || code === "ENOTDIR") {
@@ -18994,8 +19278,8 @@ function runCheckReport(source, head, deps = {}) {
18994
19278
 
18995
19279
  // dist/setup-committed.js
18996
19280
  import { execFileSync as execFileSync7 } from "node:child_process";
18997
- import { existsSync as existsSync15 } from "node:fs";
18998
- import { join as join26 } from "node:path";
19281
+ import { existsSync as existsSync16 } from "node:fs";
19282
+ import { join as join27 } from "node:path";
18999
19283
  var SETUP_COMMIT_MESSAGE = "Add Halfcycle setup files";
19000
19284
  var SETUP_UNCOMMITTED_LINES = {
19001
19285
  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 +19290,7 @@ function shellWord(path) {
19006
19290
  return /^[A-Za-z0-9._/@+=-]+$/.test(path) ? path : `'${path.replace(/'/g, `'\\''`)}'`;
19007
19291
  }
19008
19292
  function uncommittedSetupFiles(repoRoot) {
19009
- if (!existsSync15(join26(repoRoot, INSTALL_MANIFEST_REL)))
19293
+ if (!existsSync16(join27(repoRoot, INSTALL_MANIFEST_REL)))
19010
19294
  return [];
19011
19295
  const manifest = readPreviousManifest(repoRoot);
19012
19296
  if (manifest === null)
@@ -19250,13 +19534,13 @@ function proveCouldNotStart(err) {
19250
19534
  function runProveChild(binary, credential, repoRoot, words, deps = {}) {
19251
19535
  const out = deps.stdout ?? process.stdout;
19252
19536
  const err = deps.stderr ?? process.stderr;
19253
- return new Promise((resolve9) => {
19537
+ return new Promise((resolve10) => {
19254
19538
  let settled = false;
19255
19539
  const finish = (code) => {
19256
19540
  if (settled)
19257
19541
  return;
19258
19542
  settled = true;
19259
- resolve9(code);
19543
+ resolve10(code);
19260
19544
  };
19261
19545
  let child;
19262
19546
  try {
@@ -19282,9 +19566,9 @@ function runProveChild(binary, credential, repoRoot, words, deps = {}) {
19282
19566
  }
19283
19567
 
19284
19568
  // 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";
19569
+ 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
19570
  import { homedir as homedir7 } from "node:os";
19287
- import { dirname as dirname8, join as join27, relative as relative6, sep as sep2 } from "node:path";
19571
+ import { dirname as dirname8, join as join28, relative as relative7, sep as sep3 } from "node:path";
19288
19572
  var REMOVE_CREDENTIAL_FLAG = "--remove-credential";
19289
19573
  function parseUninstallArgs(args2) {
19290
19574
  let removeCredential = false;
@@ -19301,7 +19585,7 @@ function parseUninstallArgs(args2) {
19301
19585
  }
19302
19586
  function lexists(path) {
19303
19587
  try {
19304
- lstatSync4(path);
19588
+ lstatSync5(path);
19305
19589
  return true;
19306
19590
  } catch {
19307
19591
  return false;
@@ -19319,7 +19603,7 @@ function readMergedJson(targetRepo, rule) {
19319
19603
  const { rel } = rule;
19320
19604
  if (symlinkOnPath(targetRepo, rel))
19321
19605
  return { state: "symlink" };
19322
- const path = join27(targetRepo, rel);
19606
+ const path = join28(targetRepo, rel);
19323
19607
  if (!lexists(path))
19324
19608
  return { state: "absent" };
19325
19609
  let text;
@@ -19342,8 +19626,8 @@ function readMergedJson(targetRepo, rule) {
19342
19626
  }
19343
19627
  function validate(options, rules) {
19344
19628
  const { targetRepo, removeCredential, home } = options;
19345
- const manifestPath = join27(targetRepo, INSTALL_MANIFEST_REL);
19346
- const pinPath = join27(targetRepo, BUNDLE_PIN_REL);
19629
+ const manifestPath = join28(targetRepo, INSTALL_MANIFEST_REL);
19630
+ const pinPath = join28(targetRepo, BUNDLE_PIN_REL);
19347
19631
  if (symlinkOnPath(targetRepo, INSTALL_MANIFEST_REL) || symlinkOnPath(targetRepo, BUNDLE_PIN_REL)) {
19348
19632
  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
19633
  }
@@ -19384,7 +19668,7 @@ function validate(options, rules) {
19384
19668
  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
19669
  }
19386
19670
  if (engagementId !== void 0) {
19387
- const expectedParent = join27(home ?? homedir7(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME);
19671
+ const expectedParent = join28(home ?? homedir7(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME);
19388
19672
  if (dirname8(engagementStateDir(engagementId, home)) !== expectedParent) {
19389
19673
  throw new Refusal(`the project id in ${BUNDLE_PIN_REL} does not name a folder under ~/.halfcycle/engagements. Nothing was removed.`);
19390
19674
  }
@@ -19410,10 +19694,10 @@ function mergedFileRules(writeSet) {
19410
19694
  function writeBack(targetRepo, rel, current, next, lists, name) {
19411
19695
  if (next === current)
19412
19696
  return;
19413
- const path = join27(targetRepo, rel);
19697
+ const path = join28(targetRepo, rel);
19414
19698
  try {
19415
19699
  if (next === null) {
19416
- rmSync8(path);
19700
+ rmSync9(path);
19417
19701
  name(lists.removed, rel, rel);
19418
19702
  } else {
19419
19703
  writeFileSync9(path, next, "utf-8");
@@ -19427,7 +19711,7 @@ function statusLineClaimedFromTree(targetRepo, writeSet) {
19427
19711
  try {
19428
19712
  if (symlinkOnPath(targetRepo, SETTINGS_REL))
19429
19713
  return {};
19430
- const value = JSON.parse(readFileSync23(join27(targetRepo, SETTINGS_REL), "utf-8"));
19714
+ const value = JSON.parse(readFileSync23(join28(targetRepo, SETTINGS_REL), "utf-8"));
19431
19715
  if (!isPlainObject4(value) || !("statusLine" in value))
19432
19716
  return {};
19433
19717
  const hash = canonicalSha256(value["statusLine"]);
@@ -19440,7 +19724,7 @@ function attributionClaimedFromTree(targetRepo, writeSet) {
19440
19724
  try {
19441
19725
  if (symlinkOnPath(targetRepo, SETTINGS_REL))
19442
19726
  return {};
19443
- const value = JSON.parse(readFileSync23(join27(targetRepo, SETTINGS_REL), "utf-8"));
19727
+ const value = JSON.parse(readFileSync23(join28(targetRepo, SETTINGS_REL), "utf-8"));
19444
19728
  if (!isPlainObject4(value) || !("attribution" in value))
19445
19729
  return {};
19446
19730
  const hash = canonicalSha256(value["attribution"]);
@@ -19454,7 +19738,7 @@ function noRecord(targetRepo, writeSet) {
19454
19738
  const claim = (rel, header, lines) => {
19455
19739
  let text;
19456
19740
  try {
19457
- text = symlinkOnPath(targetRepo, rel) ? "" : readFileSync23(join27(targetRepo, rel), "utf-8");
19741
+ text = symlinkOnPath(targetRepo, rel) ? "" : readFileSync23(join28(targetRepo, rel), "utf-8");
19458
19742
  } catch {
19459
19743
  text = "";
19460
19744
  }
@@ -19700,7 +19984,7 @@ function lastOccurrence(text, block) {
19700
19984
  }
19701
19985
  function coveredPathExists(targetRepo, line) {
19702
19986
  const path = line.trim().replace(/^\//, "").replace(/\/$/, "");
19703
- return path !== "" && lexists(join27(targetRepo, path));
19987
+ return path !== "" && lexists(join28(targetRepo, path));
19704
19988
  }
19705
19989
  function selfBuildMergedFileRules() {
19706
19990
  return {
@@ -19714,10 +19998,10 @@ function removeSelfBuildOwnedFile(targetRepo, rel, lists, name) {
19714
19998
  name(lists.leftAlone, `${rel} (a symbolic link, or inside one \u2014 not followed)`, rel);
19715
19999
  return;
19716
20000
  }
19717
- const abs = join27(targetRepo, rel);
20001
+ const abs = join28(targetRepo, rel);
19718
20002
  let isFile;
19719
20003
  try {
19720
- isFile = lstatSync4(abs).isFile();
20004
+ isFile = lstatSync5(abs).isFile();
19721
20005
  } catch {
19722
20006
  return;
19723
20007
  }
@@ -19726,7 +20010,7 @@ function removeSelfBuildOwnedFile(targetRepo, rel, lists, name) {
19726
20010
  return;
19727
20011
  }
19728
20012
  try {
19729
- rmSync8(abs);
20013
+ rmSync9(abs);
19730
20014
  name(lists.removed, rel, rel);
19731
20015
  } catch {
19732
20016
  name(lists.failed, rel, rel);
@@ -19738,14 +20022,14 @@ function removeSelfBuildCrewRoster(targetRepo, lists, name) {
19738
20022
  name(lists.leftAlone, `${rel} (a symbolic link, or inside one \u2014 not followed)`, rel);
19739
20023
  return;
19740
20024
  }
19741
- const abs = join27(targetRepo, rel);
20025
+ const abs = join28(targetRepo, rel);
19742
20026
  let bytes;
19743
20027
  try {
19744
20028
  bytes = readFileSync23(abs);
19745
20029
  } catch {
19746
20030
  return;
19747
20031
  }
19748
- if (!lstatSync4(abs).isFile()) {
20032
+ if (!lstatSync5(abs).isFile()) {
19749
20033
  name(lists.leftAlone, `${rel} (not a file)`, rel);
19750
20034
  return;
19751
20035
  }
@@ -19754,7 +20038,7 @@ function removeSelfBuildCrewRoster(targetRepo, lists, name) {
19754
20038
  return;
19755
20039
  }
19756
20040
  try {
19757
- rmSync8(abs);
20041
+ rmSync9(abs);
19758
20042
  name(lists.removed, rel, rel);
19759
20043
  } catch {
19760
20044
  name(lists.failed, rel, rel);
@@ -19765,14 +20049,14 @@ function removeSelfBuildCrewProfile(targetRepo, rel, text, lists, name) {
19765
20049
  name(lists.leftAlone, `${rel} (a symbolic link, or inside one \u2014 not followed)`, rel);
19766
20050
  return;
19767
20051
  }
19768
- const abs = join27(targetRepo, rel);
20052
+ const abs = join28(targetRepo, rel);
19769
20053
  let bytes;
19770
20054
  try {
19771
20055
  bytes = readFileSync23(abs);
19772
20056
  } catch {
19773
20057
  return;
19774
20058
  }
19775
- if (!lstatSync4(abs).isFile()) {
20059
+ if (!lstatSync5(abs).isFile()) {
19776
20060
  name(lists.leftAlone, `${rel} (not a file)`, rel);
19777
20061
  return;
19778
20062
  }
@@ -19781,7 +20065,7 @@ function removeSelfBuildCrewProfile(targetRepo, rel, text, lists, name) {
19781
20065
  return;
19782
20066
  }
19783
20067
  try {
19784
- rmSync8(abs);
20068
+ rmSync9(abs);
19785
20069
  name(lists.removed, rel, rel);
19786
20070
  } catch {
19787
20071
  name(lists.failed, rel, rel);
@@ -19790,15 +20074,15 @@ function removeSelfBuildCrewProfile(targetRepo, rel, text, lists, name) {
19790
20074
  function removeSelfBuildEmptyDir(targetRepo, dir, lists, name) {
19791
20075
  if (symlinkOnPath(targetRepo, dir))
19792
20076
  return;
19793
- const abs = join27(targetRepo, dir);
20077
+ const abs = join28(targetRepo, dir);
19794
20078
  try {
19795
- if (!lstatSync4(abs).isDirectory() || readdirSync(abs).length > 0)
20079
+ if (!lstatSync5(abs).isDirectory() || readdirSync2(abs).length > 0)
19796
20080
  return;
19797
20081
  } catch {
19798
20082
  return;
19799
20083
  }
19800
20084
  try {
19801
- rmdirSync(abs);
20085
+ rmdirSync2(abs);
19802
20086
  } catch {
19803
20087
  name(lists.failed, `${dir}/`, dir);
19804
20088
  }
@@ -19824,7 +20108,7 @@ function uninstallSelfBuild(options) {
19824
20108
  throw err;
19825
20109
  }
19826
20110
  const { targetRepo } = options;
19827
- const pinExisted = lexists(join27(targetRepo, BUNDLE_PIN_REL));
20111
+ const pinExisted = lexists(join28(targetRepo, BUNDLE_PIN_REL));
19828
20112
  const name = (list2, text, _path) => {
19829
20113
  list2.push(text);
19830
20114
  };
@@ -19911,7 +20195,7 @@ function uninstall(options) {
19911
20195
  }
19912
20196
  const serverRemoved = removeOwnServerApproval(targetRepo);
19913
20197
  if (listRemoved || serverRemoved) {
19914
- if (existsSync16(join27(targetRepo, LOCAL_SETTINGS_REL2))) {
20198
+ if (existsSync17(join28(targetRepo, LOCAL_SETTINGS_REL2))) {
19915
20199
  name(result.restored, LOCAL_SETTINGS_REL2, LOCAL_SETTINGS_REL2);
19916
20200
  } else {
19917
20201
  name(result.removed, LOCAL_SETTINGS_REL2, LOCAL_SETTINGS_REL2);
@@ -19953,7 +20237,7 @@ function uninstall(options) {
19953
20237
  const gitattributes = manifest.gitattributes ?? { created: false, appended: [] };
19954
20238
  trimAppendedFile(targetRepo, gitattributes, deleteWhenEmpty(gitattributes.created), gitattributesRules(writeSet), result, name);
19955
20239
  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) {
20240
+ if (checked.manifest === null && !lexists(join28(targetRepo, BUNDLE_PIN_REL)) && foundNothing) {
19957
20241
  return { outcome: "not-installed", exitCode: 0, ...result, credential: nothingToSay };
19958
20242
  }
19959
20243
  const credential = handleCredential(options, checked);
@@ -19961,7 +20245,7 @@ function uninstall(options) {
19961
20245
  if (result.failed.length === 0 && !credentialFailed) {
19962
20246
  removeInstalledFile(targetRepo, BUNDLE_PIN_REL, recorded.get(BUNDLE_PIN_REL), writeSet, result, name);
19963
20247
  try {
19964
- rmSync8(join27(targetRepo, INSTALL_MANIFEST_REL), { force: true });
20248
+ rmSync9(join28(targetRepo, INSTALL_MANIFEST_REL), { force: true });
19965
20249
  named2.add(INSTALL_MANIFEST_REL);
19966
20250
  } catch {
19967
20251
  name(result.failed, INSTALL_MANIFEST_REL, INSTALL_MANIFEST_REL);
@@ -19996,13 +20280,13 @@ function applyMergedJson(targetRepo, rule, file, deleteWhenEmpty, lists, name, e
19996
20280
  return;
19997
20281
  const empty = Object.keys(value).length === 0 && deleteWhenEmpty;
19998
20282
  writeBack(targetRepo, rel, file.text, empty ? null : inLineEndingOf(file.text, serializeJson(value)), lists, name);
19999
- if (adopted && lexists(join27(targetRepo, rel))) {
20283
+ if (adopted && lexists(join28(targetRepo, rel))) {
20000
20284
  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
20285
  }
20002
20286
  }
20003
20287
  function removeCodexConfigBlock(targetRepo, record2, deleteWhenEmpty, lists, name) {
20004
20288
  const rel = CODEX_CONFIG_REL2;
20005
- const path = join27(targetRepo, rel);
20289
+ const path = join28(targetRepo, rel);
20006
20290
  if (symlinkOnPath(targetRepo, rel)) {
20007
20291
  if (lexists(path))
20008
20292
  name(lists.leftAlone, `${rel} (a symbolic link, or inside one \u2014 not followed)`, rel);
@@ -20010,7 +20294,7 @@ function removeCodexConfigBlock(targetRepo, record2, deleteWhenEmpty, lists, nam
20010
20294
  }
20011
20295
  let text;
20012
20296
  try {
20013
- if (!lstatSync4(path).isFile())
20297
+ if (!lstatSync5(path).isFile())
20014
20298
  return;
20015
20299
  text = readFileSync23(path, "utf-8");
20016
20300
  } catch {
@@ -20047,10 +20331,10 @@ function removeInstalledFile(targetRepo, path, entry, writeSet, lists, name) {
20047
20331
  name(lists.leftAlone, `${path} (a symbolic link, or inside one \u2014 not followed)`, path);
20048
20332
  return;
20049
20333
  }
20050
- const abs = join27(targetRepo, path);
20334
+ const abs = join28(targetRepo, path);
20051
20335
  let isFile;
20052
20336
  try {
20053
- isFile = lstatSync4(abs).isFile();
20337
+ isFile = lstatSync5(abs).isFile();
20054
20338
  } catch {
20055
20339
  return;
20056
20340
  }
@@ -20072,7 +20356,7 @@ function removeInstalledFile(targetRepo, path, entry, writeSet, lists, name) {
20072
20356
  }
20073
20357
  }
20074
20358
  try {
20075
- rmSync8(abs);
20359
+ rmSync9(abs);
20076
20360
  name(lists.removed, path, path);
20077
20361
  } catch {
20078
20362
  name(lists.failed, path, path);
@@ -20081,15 +20365,15 @@ function removeInstalledFile(targetRepo, path, entry, writeSet, lists, name) {
20081
20365
  function removeEmptyDir(targetRepo, dir, writeSet, lists, name) {
20082
20366
  if (!writeSet.dirs.has(dir) || symlinkOnPath(targetRepo, dir))
20083
20367
  return;
20084
- const abs = join27(targetRepo, dir);
20368
+ const abs = join28(targetRepo, dir);
20085
20369
  try {
20086
- if (!lstatSync4(abs).isDirectory() || readdirSync(abs).length > 0)
20370
+ if (!lstatSync5(abs).isDirectory() || readdirSync2(abs).length > 0)
20087
20371
  return;
20088
20372
  } catch {
20089
20373
  return;
20090
20374
  }
20091
20375
  try {
20092
- rmdirSync(abs);
20376
+ rmdirSync2(abs);
20093
20377
  } catch {
20094
20378
  name(lists.failed, `${dir}/`, dir);
20095
20379
  }
@@ -20100,7 +20384,7 @@ function trimAppendedFile(targetRepo, rec, deleteWhenEmpty, rules, lists, name)
20100
20384
  name(lists.leftAlone, `${rel} (a symbolic link \u2014 not followed)`, rel);
20101
20385
  return [];
20102
20386
  }
20103
- const path = join27(targetRepo, rel);
20387
+ const path = join28(targetRepo, rel);
20104
20388
  let before;
20105
20389
  try {
20106
20390
  before = readFileSync23(path, "utf-8");
@@ -20131,12 +20415,12 @@ function handleCredential(options, checked) {
20131
20415
  if (!lexists(dir))
20132
20416
  return { kind: "not-held", dir };
20133
20417
  try {
20134
- if (lstatSync4(dir).isSymbolicLink())
20418
+ if (lstatSync5(dir).isSymbolicLink())
20135
20419
  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);
20420
+ rmSync9(engagementEnvPath(engagementId, home), { force: true });
20421
+ rmSync9(notThisAccountMarkerPath(engagementId, home), { force: true });
20422
+ if (readdirSync2(dir).length === 0)
20423
+ rmdirSync2(dir);
20140
20424
  } catch (err) {
20141
20425
  return { kind: "failed", dir, reason: err instanceof Error ? err.message : String(err) };
20142
20426
  }
@@ -20147,16 +20431,16 @@ function filesUnder(root, rel) {
20147
20431
  const walk = (dirRel) => {
20148
20432
  let names;
20149
20433
  try {
20150
- if (!lstatSync4(join27(root, dirRel)).isDirectory())
20434
+ if (!lstatSync5(join28(root, dirRel)).isDirectory())
20151
20435
  return;
20152
- names = readdirSync(join27(root, dirRel));
20436
+ names = readdirSync2(join28(root, dirRel));
20153
20437
  } catch {
20154
20438
  return;
20155
20439
  }
20156
20440
  for (const entry of names.sort()) {
20157
20441
  const childRel = `${dirRel}/${entry}`;
20158
20442
  try {
20159
- if (lstatSync4(join27(root, childRel)).isDirectory())
20443
+ if (lstatSync5(join28(root, childRel)).isDirectory())
20160
20444
  walk(childRel);
20161
20445
  else
20162
20446
  out.push(childRel);
@@ -20170,9 +20454,9 @@ function filesUnder(root, rel) {
20170
20454
  }
20171
20455
  function shownPath(path, home) {
20172
20456
  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}/`;
20457
+ const rel = relative7(base, path);
20458
+ const under = rel !== "" && !rel.startsWith("..") && !rel.startsWith(sep3);
20459
+ return `${under ? `~/${rel.split(sep3).join("/")}` : path}/`;
20176
20460
  }
20177
20461
  function renderUninstallReport(result, home) {
20178
20462
  if (result.outcome === "refused") {
@@ -20228,7 +20512,7 @@ function renderUninstallReport(result, home) {
20228
20512
  }
20229
20513
 
20230
20514
  // dist/banner-facts.js
20231
- import { basename as basename4, resolve as resolve7 } from "node:path";
20515
+ import { basename as basename4, resolve as resolve8 } from "node:path";
20232
20516
  var BRAND_URL = "halfcycle.ai";
20233
20517
  var TAGLINE = [
20234
20518
  "Fewer cycles.",
@@ -20270,15 +20554,44 @@ var REAL_PROBES = {
20270
20554
  return void 0;
20271
20555
  }
20272
20556
  },
20273
- websiteHost: (env) => {
20557
+ websiteHost: (env, dir) => {
20274
20558
  try {
20275
- const resolved = resolveControlOrigin(env);
20276
- return resolved.source === "default" ? BRAND_URL : new URL(resolved.origin).host;
20559
+ return serviceHost4(env, dir);
20277
20560
  } catch {
20278
20561
  return void 0;
20279
20562
  }
20280
20563
  }
20281
20564
  };
20565
+ function serviceHost4(env, projectDir, home) {
20566
+ const fromEnv = resolveControlOrigin(env);
20567
+ const origin = fromEnv.source === "environment" ? fromEnv.origin : storedServiceOrigin(projectDir, home) ?? fromEnv.origin;
20568
+ if (origin === DEFAULT_CONTROL_ORIGIN)
20569
+ return BRAND_URL;
20570
+ try {
20571
+ return new URL(origin).host || BRAND_URL;
20572
+ } catch {
20573
+ return BRAND_URL;
20574
+ }
20575
+ }
20576
+ function storedServiceOrigin(projectDir, home) {
20577
+ let engagementId;
20578
+ try {
20579
+ engagementId = readBundlePin(projectDir)?.engagementId;
20580
+ } catch {
20581
+ return void 0;
20582
+ }
20583
+ if (typeof engagementId !== "string" || engagementId === "")
20584
+ return void 0;
20585
+ try {
20586
+ const stored = readEngagementEnv(engagementId, home)?.["HALFCYCLE_SERVICE_URL"];
20587
+ if (stored === void 0)
20588
+ return void 0;
20589
+ const resolved = resolveControlOrigin({ HALFCYCLE_SERVICE_URL: stored });
20590
+ return resolved.source === "environment" ? resolved.origin : void 0;
20591
+ } catch {
20592
+ return void 0;
20593
+ }
20594
+ }
20282
20595
  function fact(label, value, narrow) {
20283
20596
  if (value === void 0 || value === "")
20284
20597
  return void 0;
@@ -20304,14 +20617,14 @@ function openingBannerContent(projectDir, env = process.env, probes = REAL_PROBE
20304
20617
  // `Website`, NOT `Plane` — see this function's docblock. (No issue number here:
20305
20618
  // a comment beside a property survives into the published entry, unlike a
20306
20619
  // leading docblock, which esbuild drops.)
20307
- fact("Website", probes.websiteHost(env))
20620
+ fact("Website", probes.websiteHost(env, projectDir))
20308
20621
  ].filter((f) => f !== void 0);
20309
20622
  return {
20310
20623
  head: `halfcycle@${name}`,
20311
20624
  facts,
20312
20625
  tag: TAGLINE,
20313
20626
  tagNarrow: TAGLINE_NARROW,
20314
- url: probes.websiteHost(env) ?? BRAND_URL,
20627
+ url: probes.websiteHost(env, projectDir) ?? BRAND_URL,
20315
20628
  // `''` when even this package's own manifest could not be read. The one-line cut
20316
20629
  // drops the whole `vX.Y.Z` clause in that case rather than printing a bare `v`.
20317
20630
  version: version ?? ""
@@ -20319,7 +20632,7 @@ function openingBannerContent(projectDir, env = process.env, probes = REAL_PROBE
20319
20632
  }
20320
20633
  function safeBasename(dir) {
20321
20634
  try {
20322
- const name = basename4(resolve7(dir)).trim();
20635
+ const name = basename4(resolve8(dir)).trim();
20323
20636
  return name === "" ? "project" : name;
20324
20637
  } catch {
20325
20638
  return "project";
@@ -20661,7 +20974,7 @@ async function runCrew(repoRoot, deps) {
20661
20974
  // dist/pace.js
20662
20975
  import { spawn as spawn3 } from "node:child_process";
20663
20976
  import { readFileSync as readFileSync24 } from "node:fs";
20664
- import { join as join28 } from "node:path";
20977
+ import { join as join29 } from "node:path";
20665
20978
  var PACE_USAGE = `${ownCommand()} pace [--agent claude-code|codex] [--start <phase> [--agents <n>]] [--repo <root>]`;
20666
20979
  function parsePaceArgs(argv) {
20667
20980
  let repoRoot;
@@ -20726,13 +21039,13 @@ function runPaceChild(binary, credential, repoRoot, args2, deps = {}) {
20726
21039
  if (binary instanceof Error)
20727
21040
  return Promise.resolve({ code: 1, stdout: "" });
20728
21041
  const err = deps.stderr ?? process.stderr;
20729
- return new Promise((resolve9) => {
21042
+ return new Promise((resolve10) => {
20730
21043
  let settled = false;
20731
21044
  const done = (output) => {
20732
21045
  if (settled)
20733
21046
  return;
20734
21047
  settled = true;
20735
- resolve9(output);
21048
+ resolve10(output);
20736
21049
  };
20737
21050
  try {
20738
21051
  const child = spawn3(process.execPath, [binary, "pace", ...args2], {
@@ -20775,7 +21088,7 @@ function notPacedLine(reason) {
20775
21088
  }
20776
21089
  function readPromptScript(repoRoot) {
20777
21090
  try {
20778
- return readFileSync24(join28(repoRoot, ".claude", "hooks", "user-prompt-scope.sh"), "utf-8");
21091
+ return readFileSync24(join29(repoRoot, ".claude", "hooks", "user-prompt-scope.sh"), "utf-8");
20779
21092
  } catch {
20780
21093
  return null;
20781
21094
  }
@@ -20835,7 +21148,7 @@ async function runPace(repoRoot, start, deps, agent = null) {
20835
21148
 
20836
21149
  // dist/route.js
20837
21150
  import { readFileSync as readFileSync25 } from "node:fs";
20838
- import { join as join29 } from "node:path";
21151
+ import { join as join30 } from "node:path";
20839
21152
  var ROUTE_USAGE = `${ownCommand()} route <phase> <task> [--agent claude-code|codex] [--repo <root>]`;
20840
21153
  var ROUTE_WORK_READ_DEADLINE_MS = 5e3;
20841
21154
  function parseRouteArgs(argv) {
@@ -20878,7 +21191,7 @@ function parseRouteArgs(argv) {
20878
21191
  return { ok: true, phase: positionals[0], task: positionals[1], agent: agent ?? "claude-code", repoRoot };
20879
21192
  }
20880
21193
  function routingPath(engagementId, home) {
20881
- return join29(engagementStateDir(engagementId, home), ROUTING_FILENAME);
21194
+ return join30(engagementStateDir(engagementId, home), ROUTING_FILENAME);
20882
21195
  }
20883
21196
  function readCheapStarted(path, phase) {
20884
21197
  try {
@@ -20901,8 +21214,8 @@ function lastTierOf(attempts) {
20901
21214
  }
20902
21215
  async function readWorkInTime(deps, credential, phase) {
20903
21216
  let timer;
20904
- const late = new Promise((resolve9) => {
20905
- timer = setTimeout(() => resolve9({ kind: "unreachable" }), ROUTE_WORK_READ_DEADLINE_MS);
21217
+ const late = new Promise((resolve10) => {
21218
+ timer = setTimeout(() => resolve10({ kind: "unreachable" }), ROUTE_WORK_READ_DEADLINE_MS);
20906
21219
  timer.unref?.();
20907
21220
  });
20908
21221
  try {
@@ -20968,10 +21281,10 @@ async function runRoute(repoRoot, phase, taskId, agent, deps) {
20968
21281
  }
20969
21282
 
20970
21283
  // 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";
21284
+ import { spawnSync as spawnSync4 } from "node:child_process";
21285
+ 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
21286
  import { homedir as homedir8 } from "node:os";
20974
- import { join as join30, resolve as resolve8 } from "node:path";
21287
+ import { join as join31, resolve as resolve9 } from "node:path";
20975
21288
  var RESTORE_USAGE = `${ownCommand()} restore [<id>]`;
20976
21289
  function parseRestoreArgs(rest) {
20977
21290
  if (rest.length > 1)
@@ -20984,7 +21297,7 @@ function parseRestoreArgs(rest) {
20984
21297
  var LIST_COMMAND = `${ownCommand()} restore`;
20985
21298
  function runRestore(cwd, id, options = {}) {
20986
21299
  const now = options.now ?? Date.now;
20987
- const top = git2(cwd, ["rev-parse", "--show-toplevel"]);
21300
+ const top = git3(cwd, ["rev-parse", "--show-toplevel"]);
20988
21301
  if (!top.ok || top.text.trim() === "") {
20989
21302
  return refused2("This folder is not in a project kept with git, so Halfcycle has saved nothing here to bring back.");
20990
21303
  }
@@ -20993,7 +21306,7 @@ function runRestore(cwd, id, options = {}) {
20993
21306
  if (engagementId === null) {
20994
21307
  return refused2(`Halfcycle is not set up in this project, so it has saved nothing here. To set it up, run: ${ownCommand()}`);
20995
21308
  }
20996
- const folder2 = join30(options.home ?? homedir8(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, SAVED_DIR_NAME);
21309
+ const folder2 = join31(options.home ?? homedir8(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, SAVED_DIR_NAME);
20997
21310
  const refs = savedRefs(root);
20998
21311
  if (refs === null)
20999
21312
  return refused2("Halfcycle could not read the saved copies in this project, so nothing was put back.");
@@ -21069,7 +21382,7 @@ function restoreOne(root, folder2, refs, rawId) {
21069
21382
  if (p.keptIn === "commit")
21070
21383
  fromCommit.push(p.path);
21071
21384
  else
21072
- restoreCopy(root, join30(folder2, id), p.path, out);
21385
+ restoreCopy(root, join31(folder2, id), p.path, out);
21073
21386
  }
21074
21387
  if (fromCommit.length > 0)
21075
21388
  restoreFromCommit(root, ref, fromCommit, out);
@@ -21097,7 +21410,7 @@ function makeFolders(root, rel) {
21097
21410
  const segments = rel.split("/");
21098
21411
  let at = root;
21099
21412
  for (let i = 0; i < segments.length - 1; i++) {
21100
- at = join30(at, segments[i]);
21413
+ at = join31(at, segments[i]);
21101
21414
  const shown2 = segments.slice(0, i + 1).join("/");
21102
21415
  let st = lstatOrNull3(at);
21103
21416
  if (st === null) {
@@ -21117,7 +21430,7 @@ function makeFolders(root, rel) {
21117
21430
  return null;
21118
21431
  }
21119
21432
  function place(root, rel, content, out) {
21120
- const abs = join30(root, rel);
21433
+ const abs = join31(root, rel);
21121
21434
  const there = lstatOrNull3(abs);
21122
21435
  if (there !== null) {
21123
21436
  if (sameAs(abs, there, content))
@@ -21165,14 +21478,14 @@ function restoreCopy(root, saveDir, rel, out) {
21165
21478
  let at = saveDir;
21166
21479
  const chain = [SAVED_COPIES_DIR_NAME, ...segments];
21167
21480
  for (let i = 0; i < chain.length - 1; i++) {
21168
- at = join30(at, chain[i]);
21481
+ at = join31(at, chain[i]);
21169
21482
  const st2 = lstatOrNull3(at);
21170
21483
  if (st2 === null || st2.isSymbolicLink() || !st2.isDirectory()) {
21171
21484
  out.left.push(`${rel}: the saved copy does not hold it`);
21172
21485
  return;
21173
21486
  }
21174
21487
  }
21175
- const src = join30(at, chain[chain.length - 1]);
21488
+ const src = join31(at, chain[chain.length - 1]);
21176
21489
  const st = lstatOrNull3(src);
21177
21490
  try {
21178
21491
  if (st !== null && st.isSymbolicLink())
@@ -21187,7 +21500,7 @@ function restoreFromCommit(root, ref, rels, out) {
21187
21500
  const entries = /* @__PURE__ */ new Map();
21188
21501
  for (let i = 0; i < rels.length; i += 200) {
21189
21502
  const chunk = rels.slice(i, i + 200);
21190
- const listed = git2(root, ["--literal-pathspecs", "ls-tree", "-z", "--full-tree", `${ref}^{commit}`, "--", ...chunk]);
21503
+ const listed = git3(root, ["--literal-pathspecs", "ls-tree", "-z", "--full-tree", `${ref}^{commit}`, "--", ...chunk]);
21191
21504
  if (!listed.ok) {
21192
21505
  for (const rel of rels)
21193
21506
  out.left.push(`${rel}: the saved copy could not be read`);
@@ -21218,7 +21531,7 @@ function restoreFromCommit(root, ref, rels, out) {
21218
21531
  }
21219
21532
  }
21220
21533
  function readBlobs(root, objects) {
21221
- const r = spawnSync3("git", ["cat-file", "--batch"], {
21534
+ const r = spawnSync4("git", ["cat-file", "--batch"], {
21222
21535
  cwd: root,
21223
21536
  env: gitEnvForNamedRepo(),
21224
21537
  input: `${objects.join("\n")}
@@ -21246,12 +21559,12 @@ function readBlobs(root, objects) {
21246
21559
  function restoreLines(root, ref, saved, out) {
21247
21560
  if (saved.branches.length === 0)
21248
21561
  return;
21249
- const parents = git2(root, ["rev-list", "--parents", "-n", "1", `${ref}^{commit}`]);
21562
+ const parents = git3(root, ["rev-list", "--parents", "-n", "1", `${ref}^{commit}`]);
21250
21563
  const held = new Set(parents.ok ? parents.text.trim().split(/\s+/).slice(1) : []);
21251
21564
  for (const b of saved.branches) {
21252
21565
  const shown2 = `the line of work "${b.name}"`;
21253
21566
  const full = `refs/heads/${b.name}`;
21254
- if (!git2(root, ["check-ref-format", full]).ok) {
21567
+ if (!git3(root, ["check-ref-format", full]).ok) {
21255
21568
  out.left.push(`${shown2}: its name cannot be used`);
21256
21569
  continue;
21257
21570
  }
@@ -21259,7 +21572,7 @@ function restoreLines(root, ref, saved, out) {
21259
21572
  out.left.push(`${shown2}: the saved copy does not hold its commit`);
21260
21573
  continue;
21261
21574
  }
21262
- const now = git2(root, ["rev-parse", "--verify", "--quiet", `${full}^{commit}`]);
21575
+ const now = git3(root, ["rev-parse", "--verify", "--quiet", `${full}^{commit}`]);
21263
21576
  if (now.ok && now.text.trim() !== "") {
21264
21577
  if (now.text.trim() === b.commit)
21265
21578
  out.already.push(`${shown2}, at its saved commit ${b.commit.slice(0, 12)}`);
@@ -21267,7 +21580,7 @@ function restoreLines(root, ref, saved, out) {
21267
21580
  out.left.push(`${shown2}: one with this name exists now; rename it and run this again to bring back the saved one`);
21268
21581
  continue;
21269
21582
  }
21270
- const made = git2(root, ["update-ref", "--no-deref", full, b.commit, ""]);
21583
+ const made = git3(root, ["update-ref", "--no-deref", full, b.commit, ""]);
21271
21584
  if (made.ok)
21272
21585
  out.back.push(`${shown2}, at its saved commit ${b.commit.slice(0, 12)}`);
21273
21586
  else
@@ -21276,14 +21589,14 @@ function restoreLines(root, ref, saved, out) {
21276
21589
  }
21277
21590
  function pinnedEngagement(root) {
21278
21591
  try {
21279
- const pin = JSON.parse(readFileSync26(join30(root, BUNDLE_PIN_REL), "utf-8"));
21592
+ const pin = JSON.parse(readFileSync26(join31(root, BUNDLE_PIN_REL), "utf-8"));
21280
21593
  return isEngagementId(pin.engagementId) ? pin.engagementId : null;
21281
21594
  } catch {
21282
21595
  return null;
21283
21596
  }
21284
21597
  }
21285
21598
  function savedRefs(root) {
21286
- const listed = git2(root, ["for-each-ref", "--format=%(refname)%00%(committerdate:unix)", SAVED_REF_PREFIX]);
21599
+ const listed = git3(root, ["for-each-ref", "--format=%(refname)%00%(committerdate:unix)", SAVED_REF_PREFIX]);
21287
21600
  if (!listed.ok)
21288
21601
  return null;
21289
21602
  const refs = /* @__PURE__ */ new Map();
@@ -21298,11 +21611,11 @@ function savedRefs(root) {
21298
21611
  return refs;
21299
21612
  }
21300
21613
  function readSaved(folder2, id) {
21301
- const dir = join30(folder2, id);
21614
+ const dir = join31(folder2, id);
21302
21615
  const st = lstatOrNull3(dir);
21303
21616
  if (st === null || !st.isDirectory())
21304
21617
  return null;
21305
- const file = join30(dir, SAVED_FILE_NAME);
21618
+ const file = join31(dir, SAVED_FILE_NAME);
21306
21619
  const fst = lstatOrNull3(file);
21307
21620
  if (fst === null || !fst.isFile())
21308
21621
  return null;
@@ -21315,11 +21628,11 @@ function readSaved(folder2, id) {
21315
21628
  }
21316
21629
  function pruneAtRestore(root, folder2, refs, now) {
21317
21630
  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;
21631
+ const common = git3(root, ["rev-parse", "--git-common-dir"]);
21632
+ const repository = common.ok && common.text.trim() !== "" ? realOrSelf(resolve9(root, common.text.trim())) : null;
21320
21633
  let names = [];
21321
21634
  try {
21322
- names = readdirSync2(folder2);
21635
+ names = readdirSync3(folder2);
21323
21636
  } catch {
21324
21637
  names = [];
21325
21638
  }
@@ -21328,7 +21641,7 @@ function pruneAtRestore(root, folder2, refs, now) {
21328
21641
  if (!savedIdSchema.safeParse(id).success)
21329
21642
  continue;
21330
21643
  here.add(id);
21331
- const dir = join30(folder2, id);
21644
+ const dir = join31(folder2, id);
21332
21645
  const st = lstatOrNull3(dir);
21333
21646
  const answer = savedPruneAnswer({
21334
21647
  refMadeAt: refs.get(id) ?? null,
@@ -21336,11 +21649,11 @@ function pruneAtRestore(root, folder2, refs, now) {
21336
21649
  folderRepository: markedRepository(dir)
21337
21650
  }, { repository, now });
21338
21651
  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) {
21652
+ rmSync10(dir, { recursive: true, force: true });
21653
+ else if (answer === "remove-ref-then-folder" && git3(root, ["update-ref", "-d", savedRefOf(id)]).ok) {
21341
21654
  refs.delete(id);
21342
- rmSync9(dir, { recursive: true, force: true });
21343
- } else if (answer === "remove-ref" && git2(root, ["update-ref", "-d", savedRefOf(id)]).ok) {
21655
+ rmSync10(dir, { recursive: true, force: true });
21656
+ } else if (answer === "remove-ref" && git3(root, ["update-ref", "-d", savedRefOf(id)]).ok) {
21344
21657
  refs.delete(id);
21345
21658
  }
21346
21659
  }
@@ -21348,7 +21661,7 @@ function pruneAtRestore(root, folder2, refs, now) {
21348
21661
  if (here.has(id))
21349
21662
  continue;
21350
21663
  const answer = savedPruneAnswer({ refMadeAt: madeAt, folderChangedAt: null, folderRepository: null }, { repository, now });
21351
- if (answer === "remove-ref" && git2(root, ["update-ref", "-d", savedRefOf(id)]).ok)
21664
+ if (answer === "remove-ref" && git3(root, ["update-ref", "-d", savedRefOf(id)]).ok)
21352
21665
  refs.delete(id);
21353
21666
  }
21354
21667
  } catch {
@@ -21356,15 +21669,15 @@ function pruneAtRestore(root, folder2, refs, now) {
21356
21669
  }
21357
21670
  function markedRepository(dir) {
21358
21671
  try {
21359
- const text = readFileSync26(join30(dir, SAVED_REPOSITORY_FILE_NAME), "utf-8").trim();
21672
+ const text = readFileSync26(join31(dir, SAVED_REPOSITORY_FILE_NAME), "utf-8").trim();
21360
21673
  return text === "" ? null : text;
21361
21674
  } catch {
21362
21675
  return null;
21363
21676
  }
21364
21677
  }
21365
- function git2(cwd, args2) {
21678
+ function git3(cwd, args2) {
21366
21679
  try {
21367
- const r = spawnSync3("git", args2, { cwd, encoding: "utf-8", env: gitEnvForNamedRepo(), maxBuffer: 64 * 1024 * 1024 });
21680
+ const r = spawnSync4("git", args2, { cwd, encoding: "utf-8", env: gitEnvForNamedRepo(), maxBuffer: 64 * 1024 * 1024 });
21368
21681
  return r.status === 0 ? { ok: true, text: r.stdout ?? "" } : { ok: false };
21369
21682
  } catch {
21370
21683
  return { ok: false };
@@ -21372,23 +21685,23 @@ function git2(cwd, args2) {
21372
21685
  }
21373
21686
  function lstatOrNull3(p) {
21374
21687
  try {
21375
- return lstatSync5(p);
21688
+ return lstatSync6(p);
21376
21689
  } catch {
21377
21690
  return null;
21378
21691
  }
21379
21692
  }
21380
21693
  function realOrSelf(p) {
21381
21694
  try {
21382
- return realpathSync7(p);
21695
+ return realpathSync8(p);
21383
21696
  } catch {
21384
21697
  return p;
21385
21698
  }
21386
21699
  }
21387
21700
 
21388
21701
  // 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";
21702
+ import { chmodSync as chmodSync4, existsSync as existsSync18, mkdirSync as mkdirSync13, readFileSync as readFileSync27, rmSync as rmSync11, writeFileSync as writeFileSync10 } from "node:fs";
21390
21703
  import { homedir as homedir9, platform as platform5 } from "node:os";
21391
- import { join as join31 } from "node:path";
21704
+ import { join as join32 } from "node:path";
21392
21705
  var REQUEST_SHAPES_USAGE = `${ownCommand()} request-shapes on|off|show|clear`;
21393
21706
  var REQUEST_SHAPES_FORMS = ["on", "off", "show", "clear"];
21394
21707
  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 +21746,7 @@ function formatLine(line) {
21433
21746
  function showLog(logPath2) {
21434
21747
  let text = "";
21435
21748
  try {
21436
- text = existsSync17(logPath2) ? readFileSync27(logPath2, "utf-8") : "";
21749
+ text = existsSync18(logPath2) ? readFileSync27(logPath2, "utf-8") : "";
21437
21750
  } catch {
21438
21751
  text = "";
21439
21752
  }
@@ -21451,9 +21764,9 @@ function ownerOnly(path, mode) {
21451
21764
  chmodSync4(path, mode);
21452
21765
  }
21453
21766
  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);
21767
+ const folder2 = join32(home, HALFCYCLE_DIR_NAME, REQUEST_SHAPES_DIR_NAME);
21768
+ const switchPath = join32(folder2, REQUEST_SHAPES_SWITCH_FILENAME);
21769
+ const logPath2 = join32(folder2, REQUEST_SHAPES_LOG_FILENAME);
21457
21770
  try {
21458
21771
  switch (form) {
21459
21772
  case "on":
@@ -21464,13 +21777,13 @@ function runRequestShapes(form, home = homedir9()) {
21464
21777
  return { stdout: `${REQUEST_SHAPES_ON_LINE}
21465
21778
  `, stderr: "", exitCode: 0 };
21466
21779
  case "off":
21467
- rmSync10(switchPath, { force: true });
21780
+ rmSync11(switchPath, { force: true });
21468
21781
  return { stdout: `${REQUEST_SHAPES_OFF_LINE}
21469
21782
  `, stderr: "", exitCode: 0 };
21470
21783
  case "show":
21471
21784
  return { stdout: showLog(logPath2), stderr: "", exitCode: 0 };
21472
21785
  case "clear":
21473
- if (existsSync17(logPath2)) {
21786
+ if (existsSync18(logPath2)) {
21474
21787
  writeFileSync10(logPath2, "", { mode: 384 });
21475
21788
  ownerOnly(logPath2, 384);
21476
21789
  }
@@ -21779,7 +22092,7 @@ ${USAGE}`);
21779
22092
  if (installArm) {
21780
22093
  const engagementIdArg = positionals[1];
21781
22094
  const engagementTypeRaw = positionals[2] ?? "client";
21782
- if (!existsSync18(targetRepo)) {
22095
+ if (!existsSync19(targetRepo)) {
21783
22096
  process.stderr.write(`${folderMissingRefusal(targetRepo)}
21784
22097
  `);
21785
22098
  process.exit(1);
@@ -22187,6 +22500,13 @@ ${USAGE}`);
22187
22500
  const obtained = await obtainBuildRecord(credential, repoRoot, phaseArg);
22188
22501
  if (obtained.kind === "written") {
22189
22502
  process.stdout.write(writtenLines(obtained));
22503
+ const stamped = readEngagementEnv(credential.engagementId)?.[PHASE_STAMP_ENV_KEY]?.trim();
22504
+ if (stamped === void 0 || stamped === "" || stamped === phaseArg) {
22505
+ const tidied = tidyWorkerCopies(repoRoot);
22506
+ if (tidied.message !== void 0)
22507
+ process.stdout.write(`${tidied.message}
22508
+ `);
22509
+ }
22190
22510
  if (obtained.stampFailure) {
22191
22511
  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
22512
  halfcycle build-record: until ${obtained.stampFailure.path} can be written, the checks run on this computer are still filed under the phase that closed.