halfcycle 0.7.0 → 0.8.0

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
@@ -1,15 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // dist/bin.js
4
- import { existsSync as existsSync12, statSync as statSync2, writeSync as writeSync3 } from "node:fs";
5
-
6
- // dist/install.js
7
- import { execFileSync as execFileSync2 } from "node:child_process";
8
- import { createHash as createHash2 } from "node:crypto";
9
- import { existsSync as existsSync4, lstatSync, mkdirSync as mkdirSync5, readFileSync as readFileSync6, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "node:fs";
10
- import { homedir as homedir2 } from "node:os";
11
- import { dirname as dirname3, isAbsolute as isAbsolute2, join as join6, posix as posix2, relative, resolve } from "node:path";
12
- import { fileURLToPath } from "node:url";
3
+ // dist/request-shapes.js
4
+ import { appendFileSync, existsSync } from "node:fs";
5
+ import { homedir } from "node:os";
6
+ import { join } from "node:path";
13
7
 
14
8
  // ../core/dist/guard-record.js
15
9
  import { z } from "zod";
@@ -512,6 +506,7 @@ var TASK_PLAN_FILE = "docs/build-records/task-plan.json";
512
506
  var TASK_LIST_FILE = "docs/build-records/build-tasks.json";
513
507
  var CRITERIA_RECORD_FILE = "docs/build-records/criteria-check.json";
514
508
  var AGENT_NAME_MAX = 32;
509
+ var SMALL_PLAN_BUILD_TASKS_MAX = 6;
515
510
  var TASK_PLAN_TASKS_MAX = 500;
516
511
  var TASK_PLAN_ID_MAX = 64;
517
512
  var TASK_PLAN_BUILDS_NONE = "none";
@@ -622,14 +617,14 @@ var taskPlanLineText = {
622
617
  O8NoSuchPart: (a, parts, file, partIds) => parts.length === 1 ? `"${a}" builds "${parts[0]}", which is not a part of the system: ${file} has no part with that id. Use an id from ${file}: ${partIds.join(", ")}.` : `"${a}" builds ${parts.map((x) => `"${x}"`).join(", ")}, which are not parts of the system: ${file} has no part with those ids. Use ids from ${file}: ${partIds.join(", ")}.`,
623
618
  O8Unusable: (file, why) => `${file} is there but could not be used, so no task was checked for building a part outside this phase's plan: ${why}. Fix it, then run this again.`,
624
619
  C1Form: (a, x, first) => `"${a}" names "${x}", which is not a criterion. Write it as <spec name> AC<number>, for example "${first} AC1".`,
625
- C1Number: (a, x, folder, slug, n) => `"${a}" names "${x}", but ${folder}/${slug}.md has no AC${n}.`,
620
+ C1Number: (a, x, folder2, slug, n) => `"${a}" names "${x}", but ${folder2}/${slug}.md has no AC${n}.`,
626
621
  C2: (a, x) => `"${a}" names "${x}", which was retired. Remove it, or name the criterion that replaced it.`,
627
622
  C3: (slug, n) => `${slug} AC${n} is not delivered by any build task. Add "${slug} AC${n}" to the criteria of the build task that delivers it.`,
628
623
  C4: (a) => `"${a}" does not say which acceptance criteria it delivers. Add "criteria" with each one as <spec name> AC<number>. If it delivers none, write "criteria": "${TASK_PLAN_CRITERIA_NONE}".`,
629
624
  C5: (a, x, slug) => `"${a}" names "${x}", but "${slug}" is not in the plan's specs. Add it to "specs" if it belongs to this phase, or remove the citation.`,
630
- C6None: (folder) => `The plan does not say which feature designs this phase has. Add "specs" with the name of each file in ${folder}/ this phase builds, without ".md". If it has none, write "specs": "${TASK_PLAN_SPECS_NONE}".`,
631
- C6Malformed: (x, folder) => `The plan's specs name "${x}", which is not a spec name. Use the name of a file in ${folder}/, without ".md".`,
632
- C6Missing: (slug, folder) => `The plan's specs name "${slug}", but there is no ${folder}/${slug}.md.`,
625
+ C6None: (folder2) => `The plan does not say which feature designs this phase has. Add "specs" with the name of each file in ${folder2}/ this phase builds, without ".md". If it has none, write "specs": "${TASK_PLAN_SPECS_NONE}".`,
626
+ C6Malformed: (x, folder2) => `The plan's specs name "${x}", which is not a spec name. Use the name of a file in ${folder2}/, without ".md".`,
627
+ C6Missing: (slug, folder2) => `The plan's specs name "${slug}", but there is no ${folder2}/${slug}.md.`,
633
628
  B1: (s, t, x, more) => `"${s}" and "${t}" both own ${x}${more === 0 ? "" : more === 1 ? " and 1 more path" : ` and ${more} more paths`}. A file belongs to one spec: keep it in the Owns of the spec that mainly changes it, and let the other spec's tasks list it in what they touch.`,
634
629
  B2: (s, x) => `"${s}" lists "${x}" under Owns, which is not a path inside this project. Use a path from the project's top folder; end a folder with /.`,
635
630
  N2: (a, files) => `"${a}" changes files that none of its specs own: ${files.join(", ")}. If that is intended there is nothing to do; otherwise add them to a spec's Owns.`,
@@ -644,7 +639,7 @@ var taskPlanLineText = {
644
639
  V1: () => 'The plan has no check of the finished phase against its acceptance criteria. Add a task of kind "check" with "criteriaCheck": true, held by a crew member who builds none of this phase, that waits for every build task.',
645
640
  V2: (w, c) => `"${w}" is a walk, and it does not wait for the check of the phase against its acceptance criteria. Add "${c}" to its dependsOn.`,
646
641
  V3Unordered: (c, b) => `"${c}" checks the finished phase, but "${b}" could still be running while it does. Add "${b}" to the dependsOn of "${c}", or, if "${b}" fixes what "${c}" found, name it in ${CRITERIA_RECORD_FILE} and make it wait for "${c}".`,
647
- V3Ships: (b, c) => `"${b}" ships to users and waits for "${c}", the check of the finished phase. A deploy or a release comes before that check, so the check sees what is live: add "${b}" to the dependsOn of "${c}" and stop "${b}" waiting for "${c}".`,
642
+ V3Ships: (b, c) => `"${b}" ships to users and waits for "${c}", the check of the finished phase, and no later check waits for "${b}", so no check sees what is live. Add "${b}" to the dependsOn of "${c}" and stop "${b}" waiting for "${c}", or add a check of the phase that waits for "${b}".`,
648
643
  V3Unchecked: (b, c) => `"${b}" waits for "${c}", the check of the finished phase, and no later check waits for "${b}", so no check sees what it builds. Add "${b}" to the dependsOn of "${c}" and stop "${b}" waiting for "${c}", or add a check of the phase that waits for "${b}".`,
649
644
  V4: (x) => `"${x}" does not say which crew member holds it. Add "agent" with that member's callsign.`,
650
645
  V5: (c, m, b) => `"${c}" checks the finished phase, but ${m} also holds "${b}", which builds part of it. Give "${c}" to a crew member who holds no build task in this phase.`,
@@ -711,8 +706,8 @@ function firstSharedItem(a, b) {
711
706
  return other.shown;
712
707
  }
713
708
  }
714
- for (const folder of b.folders) {
715
- if (file.key.startsWith(folder.key))
709
+ for (const folder2 of b.folders) {
710
+ if (file.key.startsWith(folder2.key))
716
711
  return file.shown;
717
712
  }
718
713
  }
@@ -740,8 +735,8 @@ function sharedPaths(a, b) {
740
735
  add(other);
741
736
  }
742
737
  }
743
- for (const folder of b.folders) {
744
- if (file.key.startsWith(folder.key))
738
+ for (const folder2 of b.folders) {
739
+ if (file.key.startsWith(folder2.key))
745
740
  add(file);
746
741
  }
747
742
  }
@@ -1013,19 +1008,19 @@ function liveEdgeCriterionNumbers(criteria) {
1013
1008
  return liveCriterionNumbers(criteria).filter((n) => edge.has(n));
1014
1009
  }
1015
1010
  function specLines(plan, specFiles) {
1016
- const folder = specFiles.folder;
1011
+ const folder2 = specFiles.folder;
1017
1012
  if (plan.specs === TASK_PLAN_SPECS_NONE)
1018
1013
  return [];
1019
1014
  if (plan.specs === void 0 || plan.specs.length === 0) {
1020
- return [refusal("C6", taskPlanLineText.C6None(folder))];
1015
+ return [refusal("C6", taskPlanLineText.C6None(folder2))];
1021
1016
  }
1022
1017
  const criteriaOf = specCriteriaBySlug(specFiles);
1023
1018
  const lines = [];
1024
1019
  for (const name of distinct(plan.specs)) {
1025
1020
  if (!isPlanSpecName(name)) {
1026
- lines.push(refusal("C6", taskPlanLineText.C6Malformed(name, folder)));
1021
+ lines.push(refusal("C6", taskPlanLineText.C6Malformed(name, folder2)));
1027
1022
  } else if ((criteriaOf.get(name) ?? null) === null) {
1028
- lines.push(refusal("C6", taskPlanLineText.C6Missing(name, folder)));
1023
+ lines.push(refusal("C6", taskPlanLineText.C6Missing(name, folder2)));
1029
1024
  }
1030
1025
  }
1031
1026
  const covered = /* @__PURE__ */ new Set();
@@ -1169,7 +1164,7 @@ function ownershipNotes(plan, specFiles) {
1169
1164
  continue;
1170
1165
  const { files: ownedFiles, folders: ownedFolders } = pathItemsOf(ownPaths);
1171
1166
  const ownedKeys = new Set(ownedFiles.map((f) => f.key));
1172
- const unowned = pathItemsOf(task.touches?.files ?? []).files.filter((touched) => !ownedKeys.has(touched.key) && !ownedFolders.some((folder) => touched.key.startsWith(folder.key)));
1167
+ const unowned = pathItemsOf(task.touches?.files ?? []).files.filter((touched) => !ownedKeys.has(touched.key) && !ownedFolders.some((folder2) => touched.key.startsWith(folder2.key)));
1173
1168
  if (unowned.length > 0) {
1174
1169
  lines.push(note("N2", taskPlanLineText.N2(task.id, distinct(unowned.map((f) => f.shown)))));
1175
1170
  }
@@ -1212,8 +1207,9 @@ function placementLines(tasks, before) {
1212
1207
  if (q !== void 0)
1213
1208
  batch.set(a, q);
1214
1209
  });
1210
+ const asksForBatches = checkPositions.length > 0 || !isSmallPlan(tasks);
1215
1211
  tasks.forEach((task, a) => {
1216
- if (build[a] && !batch.has(a))
1212
+ if (asksForBatches && build[a] && !batch.has(a))
1217
1213
  lines.push(refusal("K1", taskPlanLineText.K1(task.id)));
1218
1214
  });
1219
1215
  for (let i = 0; i < checkPositions.length; i += 1) {
@@ -1307,8 +1303,11 @@ function placementLines(tasks, before) {
1307
1303
  function isCriteriaCheck(task) {
1308
1304
  return task.kind === "check" && task.criteriaCheck === true;
1309
1305
  }
1310
- function lacksCriteriaCheck(tasks) {
1311
- return tasks.some((task) => task.kind === "build") && !tasks.some(isCriteriaCheck);
1306
+ function isSmallPlan(tasks) {
1307
+ return tasks.filter((task) => task.kind === "build").length <= SMALL_PLAN_BUILD_TASKS_MAX;
1308
+ }
1309
+ function printsV1(tasks) {
1310
+ return tasks.some((task) => task.kind === "build") && !tasks.some(isCriteriaCheck) && !isSmallPlan(tasks);
1312
1311
  }
1313
1312
  function mustNameHolder(task) {
1314
1313
  return mustNameParts(task) || isCriteriaCheck(task);
@@ -1329,7 +1328,7 @@ function criteriaCheckLines(tasks, before, record2) {
1329
1328
  const walks = positions((task) => task.kind === "walk");
1330
1329
  const releases = positions((task) => task.ships === "release");
1331
1330
  const exempt = (w) => releases.some((r) => waits(r, w));
1332
- if (lacksCriteriaCheck(tasks)) {
1331
+ if (printsV1(tasks)) {
1333
1332
  lines.push(refusal("V1", taskPlanLineText.V1()));
1334
1333
  } else {
1335
1334
  const last = [...checks].reverse().find((c) => !checks.some((other) => other !== c && waits(other, c)));
@@ -1348,10 +1347,10 @@ function criteriaCheckLines(tasks, before, record2) {
1348
1347
  const taskWaits = waits(b, c);
1349
1348
  if (!checkWaits && !taskWaits) {
1350
1349
  lines.push(refusal("V3", taskPlanLineText.V3Unordered(cid, bid)));
1351
- } else if (taskWaits && !fixes.has(bid)) {
1350
+ } else if (taskWaits && !checks.some((other) => other !== c && waits(other, b))) {
1352
1351
  if (tasks[b].ships !== void 0) {
1353
1352
  lines.push(refusal("V3", taskPlanLineText.V3Ships(bid, cid)));
1354
- } else if (!checks.some((other) => other !== c && waits(other, b))) {
1353
+ } else if (!fixes.has(bid)) {
1355
1354
  lines.push(refusal("V3", taskPlanLineText.V3Unchecked(bid, cid)));
1356
1355
  }
1357
1356
  }
@@ -1480,7 +1479,7 @@ function checkTaskPlan(plan, request) {
1480
1479
  const system = request.system;
1481
1480
  const criteriaOf = specCriteriaBySlug(request.specFiles);
1482
1481
  const phasePlan = request.phasePlan;
1483
- const noCriteriaCheck = lacksCriteriaCheck(tasks);
1482
+ const v1Prints = printsV1(tasks);
1484
1483
  if (phasePlan !== void 0 && "unusable" in phasePlan) {
1485
1484
  lines.push(refusal("O8", taskPlanLineText.O8Unusable(phasePlan.file, phasePlanUnusableWhy(phasePlan.unusable, request.phase))));
1486
1485
  }
@@ -1546,7 +1545,7 @@ function checkTaskPlan(plan, request) {
1546
1545
  lines.push(line);
1547
1546
  }
1548
1547
  }
1549
- if (!noCriteriaCheck && mustNameHolder(task) && task.agent === void 0) {
1548
+ if (!v1Prints && mustNameHolder(task) && task.agent === void 0) {
1550
1549
  lines.push(refusal("V4", taskPlanLineText.V4(task.id)));
1551
1550
  }
1552
1551
  const cheap = cheapFirstLine(task, request.crew);
@@ -1601,11 +1600,11 @@ var criteriaRecordLineText = {
1601
1600
  M2: (file, x) => `${file} is from "${x}", which is not a check of the phase against its criteria in the plan. Name the check task that made it.`,
1602
1601
  M3: (file, m, c, k) => `${file} is by ${m}, but "${c}" is held by ${k}. The record names the crew member who holds the check.`,
1603
1602
  M4: (crit) => `${crit} has no row. Check it, and add it as met with what shows it, or as a fix with the gap and the fix task.`,
1604
- M5: (file, x, folder) => `${file} has a row for "${x}", which is not a live criterion of this phase's features in ${folder}. Remove it, or correct it.`,
1603
+ M5: (file, x, folder2) => `${file} has a row for "${x}", which is not a live criterion of this phase's features in ${folder2}. Remove it, or correct it.`,
1605
1604
  M6: (crit) => `${crit} has more than one row. Keep one.`,
1606
1605
  M7: (crit, x) => `${crit} is fixed by "${x}", but the plan has no build task "${x}" that names ${crit} in its criteria. Add the task, or name it there.`,
1607
- M8Unmarked: (crit, folder, slug) => `${crit} is not marked an edge case in ${folder}/${slug}.md, so its row does not carry "edge".`,
1608
- M8Marked: (crit, folder, slug) => `${crit} is marked an edge case in ${folder}/${slug}.md, so its row carries "edge": true.`,
1606
+ M8Unmarked: (crit, folder2, slug) => `${crit} is not marked an edge case in ${folder2}/${slug}.md, so its row does not carry "edge".`,
1607
+ M8Marked: (crit, folder2, slug) => `${crit} is marked an edge case in ${folder2}/${slug}.md, so its row carries "edge": true.`,
1609
1608
  M9Evidence: (crit, t) => `${crit} is an edge case, so it is met only by the test written in "${t}", run on a task that builds it. Give its evidence as a test with "writtenIn": "${t}" and the task that built it.`,
1610
1609
  M9NoTestTask: (crit) => `${crit} is an edge case, and the plan has no task that writes its test, so it cannot be counted as met. Run check-tasks and add that task.`,
1611
1610
  P: (met, fix) => `The record covers every criterion of this phase's features: ${met} met, ${fix} to fix.`
@@ -1647,14 +1646,14 @@ function slugOf(criterion) {
1647
1646
  function refusal2(code, text) {
1648
1647
  return { code, text, refuses: true };
1649
1648
  }
1650
- function edgeRowLines(row, isEdge, testTask, plan, folder) {
1649
+ function edgeRowLines(row, isEdge, testTask, plan, folder2) {
1651
1650
  const crit = row.criterion;
1652
1651
  const lines = [];
1653
1652
  const marked = row.edge === true;
1654
1653
  if (marked && !isEdge)
1655
- lines.push(refusal2("M8", criteriaRecordLineText.M8Unmarked(crit, folder, slugOf(crit))));
1654
+ lines.push(refusal2("M8", criteriaRecordLineText.M8Unmarked(crit, folder2, slugOf(crit))));
1656
1655
  if (!marked && isEdge)
1657
- lines.push(refusal2("M8", criteriaRecordLineText.M8Marked(crit, folder, slugOf(crit))));
1656
+ lines.push(refusal2("M8", criteriaRecordLineText.M8Marked(crit, folder2, slugOf(crit))));
1658
1657
  if (!isEdge || row.status !== "met")
1659
1658
  return lines;
1660
1659
  if (testTask === null) {
@@ -2161,6 +2160,237 @@ function readyTasks(rows) {
2161
2160
  return { ready: startable.map((entry) => entry.id), person };
2162
2161
  }
2163
2162
 
2163
+ // ../core/dist/request-shape.js
2164
+ var REQUEST_SHAPES_DIR_NAME = "debug";
2165
+ var REQUEST_SHAPES_SWITCH_FILENAME = "request-shapes.on";
2166
+ var REQUEST_SHAPES_LOG_FILENAME = "request-shapes.jsonl";
2167
+ var REQUEST_SHAPE_MAX_DEPTH = 8;
2168
+ var REQUEST_SHAPE_MAX_ARRAY_SHAPES = 10;
2169
+ var REQUEST_SHAPE_MAX_KEY_LENGTH = 64;
2170
+ var REQUEST_SHAPE_MAX_LINE_BYTES = 16 * 1024;
2171
+ var KEY_NAME = /^[A-Za-z][A-Za-z0-9_]*$/;
2172
+ var PATH_WORD = /^[a-z-]+$/;
2173
+ function byteLength(text) {
2174
+ return new TextEncoder().encode(text).length;
2175
+ }
2176
+ function keyOf(key) {
2177
+ return key.length <= REQUEST_SHAPE_MAX_KEY_LENGTH && KEY_NAME.test(key) ? key : "<key>";
2178
+ }
2179
+ function shapeOfValue(value, depth) {
2180
+ if (value === null)
2181
+ return "null";
2182
+ if (typeof value === "string")
2183
+ return "string";
2184
+ if (typeof value === "number")
2185
+ return "number";
2186
+ if (typeof value === "boolean")
2187
+ return "boolean";
2188
+ if (depth > REQUEST_SHAPE_MAX_DEPTH)
2189
+ return "\u2026";
2190
+ if (Array.isArray(value)) {
2191
+ const seen = [];
2192
+ for (const element of value) {
2193
+ if (seen.length >= REQUEST_SHAPE_MAX_ARRAY_SHAPES)
2194
+ break;
2195
+ const shape = shapeOfValue(element, depth + 1);
2196
+ if (!seen.includes(shape))
2197
+ seen.push(shape);
2198
+ }
2199
+ return `[${seen.join("|")}]`;
2200
+ }
2201
+ if (typeof value === "object") {
2202
+ const entries = Object.entries(value);
2203
+ if (entries.length === 0)
2204
+ return "{}";
2205
+ return `{ ${entries.map(([k, v]) => `${keyOf(k)}: ${shapeOfValue(v, depth + 1)}`).join(", ")} }`;
2206
+ }
2207
+ return "null";
2208
+ }
2209
+ function requestShapeOf(body) {
2210
+ let parsed;
2211
+ try {
2212
+ parsed = JSON.parse(body);
2213
+ } catch {
2214
+ return `text ${byteLength(body)} bytes`;
2215
+ }
2216
+ return shapeOfValue(parsed, 1);
2217
+ }
2218
+ function requestPathShapeOf(pathname) {
2219
+ return pathname.split("/").map((segment) => segment === "" || PATH_WORD.test(segment) ? segment : ":x").join("/");
2220
+ }
2221
+ function distinct2(names) {
2222
+ const out = [];
2223
+ for (const name of names)
2224
+ if (!out.includes(name))
2225
+ out.push(name);
2226
+ return out;
2227
+ }
2228
+ function isObject(value) {
2229
+ return typeof value === "object" && value !== null;
2230
+ }
2231
+ function isRequestLike(value) {
2232
+ return isObject(value) && !(value instanceof URL) && typeof value["url"] === "string" && typeof value["method"] === "string";
2233
+ }
2234
+ function headerNamesOf(headers) {
2235
+ if (!isObject(headers))
2236
+ return [];
2237
+ const names = [];
2238
+ if (Array.isArray(headers)) {
2239
+ for (const pair of headers)
2240
+ if (Array.isArray(pair) && typeof pair[0] === "string")
2241
+ names.push(pair[0].toLowerCase());
2242
+ return names;
2243
+ }
2244
+ const forEach = headers["forEach"];
2245
+ if (typeof forEach === "function") {
2246
+ forEach.call(headers, (_value, key) => {
2247
+ if (typeof key === "string")
2248
+ names.push(key.toLowerCase());
2249
+ });
2250
+ return names;
2251
+ }
2252
+ return Object.keys(headers).map((k) => k.toLowerCase());
2253
+ }
2254
+ function bytesShape(bytes) {
2255
+ let text;
2256
+ try {
2257
+ text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
2258
+ } catch {
2259
+ return `text ${bytes.byteLength} bytes`;
2260
+ }
2261
+ return requestShapeOf(text);
2262
+ }
2263
+ function bodyShapeOf(body) {
2264
+ if (body === void 0 || body === null)
2265
+ return null;
2266
+ if (typeof body === "string")
2267
+ return requestShapeOf(body);
2268
+ if (body instanceof URLSearchParams)
2269
+ return `text ${byteLength(body.toString())} bytes`;
2270
+ if (body instanceof ArrayBuffer)
2271
+ return bytesShape(new Uint8Array(body));
2272
+ if (ArrayBuffer.isView(body))
2273
+ return bytesShape(new Uint8Array(body.buffer, body.byteOffset, body.byteLength));
2274
+ if (isObject(body) && typeof body["size"] === "number" && typeof body["arrayBuffer"] === "function") {
2275
+ return `text ${body["size"]} bytes`;
2276
+ }
2277
+ return "stream";
2278
+ }
2279
+ function requestLineOf(input, init) {
2280
+ const request = isRequestLike(input) ? input : void 0;
2281
+ const address = request !== void 0 ? request.url : input instanceof URL ? input.href : String(input);
2282
+ let url;
2283
+ try {
2284
+ url = new URL(address);
2285
+ } catch {
2286
+ url = void 0;
2287
+ }
2288
+ const options = isObject(init) ? init : {};
2289
+ const method = (typeof options["method"] === "string" ? options["method"] : request?.method ?? "GET").toUpperCase();
2290
+ const headers = distinct2([
2291
+ ...request !== void 0 ? headerNamesOf(request.headers) : [],
2292
+ ...headerNamesOf(options["headers"])
2293
+ ]);
2294
+ const shape = options["body"] !== void 0 && options["body"] !== null ? bodyShapeOf(options["body"]) : request !== void 0 && request.body !== null && request.body !== void 0 ? "stream" : null;
2295
+ return {
2296
+ method,
2297
+ host: url?.host ?? "",
2298
+ path: url !== void 0 ? requestPathShapeOf(url.pathname) : ":x",
2299
+ query: url !== void 0 ? distinct2([...url.searchParams.keys()]) : [],
2300
+ headers,
2301
+ shape
2302
+ };
2303
+ }
2304
+ function requestShapeLineText(line) {
2305
+ const ordered = (shape) => JSON.stringify({
2306
+ at: line.at,
2307
+ process: line.process,
2308
+ method: line.method,
2309
+ host: line.host,
2310
+ path: line.path,
2311
+ query: line.query,
2312
+ headers: line.headers,
2313
+ shape,
2314
+ status: line.status
2315
+ });
2316
+ const whole = ordered(line.shape);
2317
+ if (byteLength(whole) <= REQUEST_SHAPE_MAX_LINE_BYTES || line.shape === null)
2318
+ return whole;
2319
+ const room = REQUEST_SHAPE_MAX_LINE_BYTES - byteLength(ordered("")) - byteLength("\u2026");
2320
+ if (room <= 0)
2321
+ return ordered("\u2026");
2322
+ let end = Math.min(line.shape.length, room);
2323
+ for (let over = byteLength(line.shape.slice(0, end)) - room; over > 0; over = byteLength(line.shape.slice(0, end)) - room) {
2324
+ end -= over;
2325
+ }
2326
+ const prefix = line.shape.slice(0, Math.max(0, end));
2327
+ const cut = Math.max(prefix.lastIndexOf(", "), prefix.lastIndexOf("|"));
2328
+ return ordered(`${cut > 0 ? prefix.slice(0, cut) : ""}\u2026`);
2329
+ }
2330
+
2331
+ // dist/request-shapes.js
2332
+ var folder = join(homedir(), HALFCYCLE_DIR_NAME, REQUEST_SHAPES_DIR_NAME);
2333
+ var logPath = join(folder, REQUEST_SHAPES_LOG_FILENAME);
2334
+ var switchedOn = existsSync(join(folder, REQUEST_SHAPES_SWITCH_FILENAME));
2335
+ var knownVerbs = [];
2336
+ function knowRequestShapeVerbs(verbs) {
2337
+ knownVerbs = verbs;
2338
+ }
2339
+ function processName() {
2340
+ const verb = process.argv[2];
2341
+ return verb !== void 0 && knownVerbs.includes(verb) ? `cli ${verb}` : "cli other";
2342
+ }
2343
+ function write(call, status) {
2344
+ if (call === void 0)
2345
+ return;
2346
+ try {
2347
+ const text = requestShapeLineText({ at: (/* @__PURE__ */ new Date()).toISOString(), process: processName(), ...call, status });
2348
+ appendFileSync(logPath, `${text}
2349
+ `, { mode: 384 });
2350
+ } catch {
2351
+ }
2352
+ }
2353
+ function describe(args2) {
2354
+ try {
2355
+ return requestLineOf(args2[0], args2[1]);
2356
+ } catch {
2357
+ return void 0;
2358
+ }
2359
+ }
2360
+ if (switchedOn) {
2361
+ const original = globalThis.fetch;
2362
+ if (typeof original === "function") {
2363
+ globalThis.fetch = function recordedFetch(...args2) {
2364
+ const call = describe(args2);
2365
+ let answer;
2366
+ try {
2367
+ answer = original.apply(this, args2);
2368
+ } catch (err) {
2369
+ write(call, "unreachable");
2370
+ throw err;
2371
+ }
2372
+ return answer.then((res) => {
2373
+ write(call, res.status);
2374
+ return res;
2375
+ }, (err) => {
2376
+ write(call, "unreachable");
2377
+ throw err;
2378
+ });
2379
+ };
2380
+ }
2381
+ }
2382
+
2383
+ // dist/bin.js
2384
+ import { existsSync as existsSync14, statSync as statSync2, writeSync as writeSync3 } from "node:fs";
2385
+
2386
+ // dist/install.js
2387
+ import { execFileSync as execFileSync2 } from "node:child_process";
2388
+ import { createHash as createHash2 } from "node:crypto";
2389
+ import { existsSync as existsSync5, lstatSync, mkdirSync as mkdirSync5, readFileSync as readFileSync6, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "node:fs";
2390
+ import { homedir as homedir3 } from "node:os";
2391
+ import { dirname as dirname3, isAbsolute as isAbsolute2, join as join7, posix as posix2, relative, resolve } from "node:path";
2392
+ import { fileURLToPath } from "node:url";
2393
+
2164
2394
  // ../events/dist/result.js
2165
2395
  import { z as z3 } from "zod";
2166
2396
  var firedGuardSchema = z3.object({
@@ -4083,9 +4313,9 @@ function whereOf(owns) {
4083
4313
  return isBareFileName(lastSegment) ? `in ${pattern}` : `under ${pattern}/`;
4084
4314
  }
4085
4315
  if (lastSegment === "*") {
4086
- const folder = segments.slice(0, -1).join("/");
4087
- if (folder !== "" && !hasWildcard(folder))
4088
- return `in the ${folder} folder`;
4316
+ const folder2 = segments.slice(0, -1).join("/");
4317
+ if (folder2 !== "" && !hasWildcard(folder2))
4318
+ return `in the ${folder2} folder`;
4089
4319
  }
4090
4320
  const prefix = leadingLiteralPrefix(segments);
4091
4321
  if (ownsEverythingUnderPrefix(segments))
@@ -6361,6 +6591,10 @@ var limitHeldSchema = z33.object({
6361
6591
  raiseBy: z33.number().int().nonnegative(),
6362
6592
  working: z33.number().int().nonnegative()
6363
6593
  }).strict();
6594
+ var paceUsageSchema = z33.object({
6595
+ used: z33.number().int().nonnegative(),
6596
+ limit: z33.number().int().positive()
6597
+ }).strict();
6364
6598
  var paceRunResultSchema = z33.discriminatedUnion("outcome", [
6365
6599
  z33.object({
6366
6600
  outcome: z33.literal("paced"),
@@ -6372,7 +6606,8 @@ var paceRunResultSchema = z33.discriminatedUnion("outcome", [
6372
6606
  keepFreePercent: keepFreePercentSchema.optional(),
6373
6607
  suggestion: paceSuggestionSchema.optional(),
6374
6608
  limit: z33.literal("reached").optional(),
6375
- limitHeld: limitHeldSchema.optional()
6609
+ limitHeld: limitHeldSchema.optional(),
6610
+ usage: paceUsageSchema.optional()
6376
6611
  }).strict(),
6377
6612
  z33.object({ outcome: z33.literal("not-paced"), reason: z33.enum(PACE_NOT_PACED_REASONS) }).strict()
6378
6613
  ]);
@@ -6463,6 +6698,12 @@ function paceS2Of(window, k, until, now, keepFreePercent) {
6463
6698
  function paceAllowedNowOf(k) {
6464
6699
  return k === 0 ? "Start no more agents now." : `Up to ${String(k)} more ${k === 1 ? "agent" : "agents"} can start now.`;
6465
6700
  }
6701
+ function paceBuildUpToOf(n) {
6702
+ return `This build runs up to ${paceAgentsOf(n)} at once.`;
6703
+ }
6704
+ function limitUsedOf(used, limit) {
6705
+ return `This phase has used ${tokenCountWordsOf(used)} of its limit of ${tokenCountWordsOf(limit)}.`;
6706
+ }
6466
6707
  var PACE_AGENTS_TOO_MANY = `Run at most ${String(PACE_AGENTS_MAX)} agents at once.`;
6467
6708
  var LIMIT_HISTORY_MAX = 20;
6468
6709
  var LIMIT_PARTS = ["half", "three-quarters", "nine-tenths", "limit"];
@@ -6821,9 +7062,13 @@ function routeStartLineOf(holder, task, tier) {
6821
7062
  function routeStartUnknownLineOf(holder, task) {
6822
7063
  return `Start ${holder} on ${task}.`;
6823
7064
  }
7065
+ function routePassedLineOf(task, tier) {
7066
+ return `${task} has passed its review, built on the ${tier} tier. There is nothing to start.`;
7067
+ }
6824
7068
  function routeNoSuchTaskLineOf(task) {
6825
7069
  return `"${task}" is not a task in the plan. Check the task id, then run this again.`;
6826
7070
  }
7071
+ var ROUTING_FILENAME = "routing.json";
6827
7072
  var routingRecordSchema = z36.object({
6828
7073
  phase: z36.string().min(1).max(200),
6829
7074
  cheapStarted: z36.array(z36.string().min(1).max(200)).max(2e3)
@@ -6882,17 +7127,17 @@ function seenModelsLineOf(task, models) {
6882
7127
  // dist/engagement-credential.js
6883
7128
  import { chmodSync as chmodSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
6884
7129
  import { platform as platform2 } from "node:os";
6885
- import { join as join2 } from "node:path";
7130
+ import { join as join3 } from "node:path";
6886
7131
 
6887
7132
  // dist/account-credential.js
6888
7133
  import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
6889
- import { homedir, platform } from "node:os";
6890
- import { dirname, join } from "node:path";
7134
+ import { homedir as homedir2, platform } from "node:os";
7135
+ import { dirname, join as join2 } from "node:path";
6891
7136
  function halfcycleHome(home) {
6892
- return join(home ?? homedir(), HALFCYCLE_DIR_NAME);
7137
+ return join2(home ?? homedir2(), HALFCYCLE_DIR_NAME);
6893
7138
  }
6894
7139
  function accountStorePath(home) {
6895
- return join(halfcycleHome(home), ACCOUNT_STORE_FILENAME);
7140
+ return join2(halfcycleHome(home), ACCOUNT_STORE_FILENAME);
6896
7141
  }
6897
7142
  function readStore(home) {
6898
7143
  try {
@@ -6958,13 +7203,13 @@ function assertEngagementId(engagementId) {
6958
7203
  }
6959
7204
  function engagementStateDir(engagementId, home) {
6960
7205
  assertEngagementId(engagementId);
6961
- return join2(halfcycleHome(home), ENGAGEMENTS_DIR, engagementId);
7206
+ return join3(halfcycleHome(home), ENGAGEMENTS_DIR, engagementId);
6962
7207
  }
6963
7208
  function engagementEnvPath(engagementId, home) {
6964
- return join2(engagementStateDir(engagementId, home), ENV_FILENAME);
7209
+ return join3(engagementStateDir(engagementId, home), ENV_FILENAME);
6965
7210
  }
6966
7211
  function notThisAccountMarkerPath(engagementId, home) {
6967
- return join2(engagementStateDir(engagementId, home), NOT_THIS_ACCOUNT_MARKER_FILENAME);
7212
+ return join3(engagementStateDir(engagementId, home), NOT_THIS_ACCOUNT_MARKER_FILENAME);
6968
7213
  }
6969
7214
  function writeNotThisAccountMarker(engagementId, home) {
6970
7215
  const dir = engagementStateDir(engagementId, home);
@@ -7089,8 +7334,8 @@ halfcycle_env_file() {
7089
7334
 
7090
7335
  // dist/identity.js
7091
7336
  import { execFileSync } from "node:child_process";
7092
- import { existsSync, linkSync, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
7093
- import { dirname as dirname2, isAbsolute, join as join3 } from "node:path";
7337
+ import { existsSync as existsSync2, linkSync, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
7338
+ import { dirname as dirname2, isAbsolute, join as join4 } from "node:path";
7094
7339
  import { randomUUID } from "node:crypto";
7095
7340
  var PROJECT_IDENTITY_NOTE = 'projectId is NOT the engagement id \u2014 it identifies this repository/worktree to Halfcycle, not an engagement. The engagement id lives in .halfcycle/bundle.json, in its "engagementId" field.';
7096
7341
  var PROJECT_IDENTITY_REL = ".halfcycle/project.json";
@@ -7221,7 +7466,7 @@ function readIgnoredPaths(targetRepoRoot, paths) {
7221
7466
  const toplevel = git(targetRepoRoot, ["rev-parse", "--show-toplevel"]);
7222
7467
  if (toplevel === null)
7223
7468
  return null;
7224
- return ignored.map((entry) => isAbsolute(entry.source) ? entry : { ...entry, source: join3(toplevel, entry.source) });
7469
+ return ignored.map((entry) => isAbsolute(entry.source) ? entry : { ...entry, source: join4(toplevel, entry.source) });
7225
7470
  }
7226
7471
  function mintIdentity(targetRepoRoot) {
7227
7472
  return {
@@ -7232,7 +7477,7 @@ function mintIdentity(targetRepoRoot) {
7232
7477
  };
7233
7478
  }
7234
7479
  function plannedRemoteRepair(targetRepoRoot) {
7235
- const path = join3(targetRepoRoot, PROJECT_IDENTITY_REL);
7480
+ const path = join4(targetRepoRoot, PROJECT_IDENTITY_REL);
7236
7481
  let onDisk;
7237
7482
  try {
7238
7483
  const parsed = JSON.parse(readFileSync3(path, "utf-8"));
@@ -7254,7 +7499,7 @@ function plannedRemoteRepair(targetRepoRoot) {
7254
7499
  });
7255
7500
  }
7256
7501
  function replaceProjectIdentityFile(targetRepoRoot, text) {
7257
- const path = join3(targetRepoRoot, PROJECT_IDENTITY_REL);
7502
+ const path = join4(targetRepoRoot, PROJECT_IDENTITY_REL);
7258
7503
  const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
7259
7504
  try {
7260
7505
  writeFileSync3(temporary, text, { encoding: "utf-8", flag: "wx" });
@@ -7265,8 +7510,8 @@ function replaceProjectIdentityFile(targetRepoRoot, text) {
7265
7510
  }
7266
7511
  }
7267
7512
  function planProjectIdentity(targetRepoRoot) {
7268
- const path = join3(targetRepoRoot, PROJECT_IDENTITY_REL);
7269
- if (!existsSync(path)) {
7513
+ const path = join4(targetRepoRoot, PROJECT_IDENTITY_REL);
7514
+ if (!existsSync2(path)) {
7270
7515
  return { action: "add", after: serializeProjectIdentity(mintIdentity(targetRepoRoot)) };
7271
7516
  }
7272
7517
  const repaired = plannedRemoteRepair(targetRepoRoot);
@@ -7284,7 +7529,7 @@ function serializeProjectIdentity(identity) {
7284
7529
  return JSON.stringify(identity, null, 2) + "\n";
7285
7530
  }
7286
7531
  function createProjectIdentityFile(targetRepoRoot, text) {
7287
- const path = join3(targetRepoRoot, PROJECT_IDENTITY_REL);
7532
+ const path = join4(targetRepoRoot, PROJECT_IDENTITY_REL);
7288
7533
  mkdirSync3(dirname2(path), { recursive: true });
7289
7534
  const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
7290
7535
  try {
@@ -7304,8 +7549,8 @@ function createProjectIdentityFile(targetRepoRoot, text) {
7304
7549
  }
7305
7550
 
7306
7551
  // dist/install-plan.js
7307
- import { existsSync as existsSync3, readFileSync as readFileSync5 } from "node:fs";
7308
- import { join as join5, posix } from "node:path";
7552
+ import { existsSync as existsSync4, readFileSync as readFileSync5 } from "node:fs";
7553
+ import { join as join6, posix } from "node:path";
7309
7554
 
7310
7555
  // dist/install-manifest.js
7311
7556
  import { Buffer as Buffer2 } from "node:buffer";
@@ -7656,8 +7901,8 @@ function union(a, b) {
7656
7901
  }
7657
7902
 
7658
7903
  // dist/scan.js
7659
- import { existsSync as existsSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
7660
- import { join as join4 } from "node:path";
7904
+ import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
7905
+ import { join as join5 } from "node:path";
7661
7906
  var HALFCYCLE_STATE_FORMAT = "halfcycle-state-file/v1";
7662
7907
  var HALFCYCLE_STATE_NOTE = [
7663
7908
  "This file is a local cache written ONCE when Halfcycle was installed. It is not a",
@@ -7680,7 +7925,7 @@ var LAYER_ARTEFACT_PATHS = {
7680
7925
  function scanLayers(targetRepo, scannedAt = (/* @__PURE__ */ new Date()).toISOString()) {
7681
7926
  const layers = {};
7682
7927
  for (const [layer, paths] of Object.entries(LAYER_ARTEFACT_PATHS)) {
7683
- const found = paths.filter((p) => existsSync2(join4(targetRepo, p)));
7928
+ const found = paths.filter((p) => existsSync3(join5(targetRepo, p)));
7684
7929
  if (found.length > 0) {
7685
7930
  layers[layer] = {
7686
7931
  value: "asserted",
@@ -7694,8 +7939,8 @@ function scanLayers(targetRepo, scannedAt = (/* @__PURE__ */ new Date()).toISOSt
7694
7939
  }
7695
7940
  var BOOTSTRAP_STATE_REL = ".halfcycle/state.json";
7696
7941
  function planBootstrapScan(targetRepo) {
7697
- const statePath = join4(targetRepo, BOOTSTRAP_STATE_REL);
7698
- if (existsSync2(statePath)) {
7942
+ const statePath = join5(targetRepo, BOOTSTRAP_STATE_REL);
7943
+ if (existsSync3(statePath)) {
7699
7944
  const existing = JSON.parse(readFileSync4(statePath, "utf-8"));
7700
7945
  return { state: existing, text: null };
7701
7946
  }
@@ -7703,8 +7948,8 @@ function planBootstrapScan(targetRepo) {
7703
7948
  return { state, text: JSON.stringify(state, null, 2) + "\n" };
7704
7949
  }
7705
7950
  function writeBootstrapState(targetRepo, text) {
7706
- mkdirSync4(join4(targetRepo, ".halfcycle"), { recursive: true });
7707
- writeFileSync4(join4(targetRepo, BOOTSTRAP_STATE_REL), text, "utf-8");
7951
+ mkdirSync4(join5(targetRepo, ".halfcycle"), { recursive: true });
7952
+ writeFileSync4(join5(targetRepo, BOOTSTRAP_STATE_REL), text, "utf-8");
7708
7953
  }
7709
7954
 
7710
7955
  // dist/install-plan.js
@@ -7854,12 +8099,12 @@ function planInstall(options) {
7854
8099
  throw new Error("[halfcycle] a plan for a project needs its kind as well as its id.");
7855
8100
  }
7856
8101
  }
7857
- if (!existsSync3(targetRepo)) {
8102
+ if (!existsSync4(targetRepo)) {
7858
8103
  throw new Error(`[halfcycle] Target repo does not exist: ${targetRepo}`);
7859
8104
  }
7860
8105
  const writeSet = closedWriteSet();
7861
8106
  const previous = readPreviousManifest(targetRepo);
7862
- const dirsBefore = new Set([...writeSet.dirs].filter((dir) => existsSync3(join5(targetRepo, dir))));
8107
+ const dirsBefore = new Set([...writeSet.dirs].filter((dir) => existsSync4(join6(targetRepo, dir))));
7863
8108
  const { version, commands } = planCommandStubs(targetRepo, previous, writeSet);
7864
8109
  const codexSkills = planCodexSkills(targetRepo, previous, writeSet);
7865
8110
  const vendored = planVendoredBinary(targetRepo);
@@ -7881,7 +8126,7 @@ function planInstall(options) {
7881
8126
  const gitattributes = planGitattributes(targetRepo);
7882
8127
  const identity = planProjectIdentity(targetRepo);
7883
8128
  if (identity.after === null)
7884
- readFileSync5(join5(targetRepo, PROJECT_IDENTITY_REL));
8129
+ readFileSync5(join6(targetRepo, PROJECT_IDENTITY_REL));
7885
8130
  const credentialPlan = !preview && credential !== void 0 ? planEngagementCredential(credential, engagementId, home) : null;
7886
8131
  const credentialWrite = credentialPlan === null ? null : { outcome: credentialPlan.outcome, existed: credentialPlan.existed, path: engagementEnvPath(engagementId, home) };
7887
8132
  const storedAfter = credentialPlan?.storedAfter ?? (preview ? Object.fromEntries(ENGAGEMENT_ENV_KEYS.map((key) => [key, "supplied at sign-in"])) : readEngagementEnv(engagementId, home) ?? {});
@@ -8014,7 +8259,7 @@ function entriesOf(steps, record2) {
8014
8259
  }
8015
8260
  };
8016
8261
  const pending = (rel) => {
8017
- const there = existsSync3(join5(targetRepo, rel));
8262
+ const there = existsSync4(join6(targetRepo, rel));
8018
8263
  entries.push({ path: rel, action: there ? "change" : "add", kind: kindOf(rel), bytes: PENDING });
8019
8264
  };
8020
8265
  steps.commands.forEach(fromFile);
@@ -8062,7 +8307,7 @@ function entriesOf(steps, record2) {
8062
8307
  action: "leave",
8063
8308
  kind: "codex-server",
8064
8309
  bytes: null,
8065
- ...existsSync3(join5(targetRepo, CODEX_CONFIG_REL2)) ? { yours: true } : {}
8310
+ ...existsSync4(join6(targetRepo, CODEX_CONFIG_REL2)) ? { yours: true } : {}
8066
8311
  });
8067
8312
  break;
8068
8313
  }
@@ -8103,11 +8348,11 @@ function outsideOf(steps) {
8103
8348
  outside.push({ kind: "new-project" });
8104
8349
  return outside;
8105
8350
  }
8106
- const write = steps.credentialWrite;
8107
- if (write === null)
8351
+ const write2 = steps.credentialWrite;
8352
+ if (write2 === null)
8108
8353
  return [];
8109
- const action = write.outcome === "skipped" ? "leave" : write.existed ? "change" : "add";
8110
- return [{ kind: "sign-in", path: write.path, action }];
8354
+ const action = write2.outcome === "skipped" ? "leave" : write2.existed ? "change" : "add";
8355
+ return [{ kind: "sign-in", path: write2.path, action }];
8111
8356
  }
8112
8357
  function projectPinned(targetRepo) {
8113
8358
  try {
@@ -8141,10 +8386,10 @@ function applyInstallPlan(plan) {
8141
8386
  putPlannedCodexHooks(targetRepo, steps.codexHooks);
8142
8387
  putPlannedCodexConfig(targetRepo, steps.codexConfig);
8143
8388
  const gitignore = putPlannedGitignore(targetRepo, steps.gitignore);
8144
- const gitignorePath = join5(targetRepo, GITIGNORE_REL);
8145
- const gitignoreAfter = existsSync3(gitignorePath) ? readFileSync5(gitignorePath, "utf-8") : null;
8389
+ const gitignorePath = join6(targetRepo, GITIGNORE_REL);
8390
+ const gitignoreAfter = existsSync4(gitignorePath) ? readFileSync5(gitignorePath, "utf-8") : null;
8146
8391
  const gitattributes = putPlannedGitattributes(targetRepo, steps.gitattributes);
8147
- const gitattributesAfter = readTextOrNull(join5(targetRepo, GITATTRIBUTES_REL));
8392
+ const gitattributesAfter = readTextOrNull(join6(targetRepo, GITATTRIBUTES_REL));
8148
8393
  writeProjectIdentity(targetRepo, steps.identity);
8149
8394
  const credential = steps.credential;
8150
8395
  const credentialOutcome = steps.credentialWrite !== null && credential !== void 0 ? writeEngagementCredential(credential, engagementId, steps.home) : null;
@@ -8165,9 +8410,9 @@ function applyInstallPlan(plan) {
8165
8410
  gitattributesAfter,
8166
8411
  credential: credentialOutcome,
8167
8412
  legacy,
8168
- bytesAfter: (rel) => readFileSync5(join5(targetRepo, rel)),
8169
- dirExistsAfter: (dir) => existsSync3(join5(targetRepo, dir)),
8170
- stateExistsAfter: existsSync3(join5(targetRepo, BOOTSTRAP_STATE_REL))
8413
+ bytesAfter: (rel) => readFileSync5(join6(targetRepo, rel)),
8414
+ dirExistsAfter: (dir) => existsSync4(join6(targetRepo, dir)),
8415
+ stateExistsAfter: existsSync4(join6(targetRepo, BOOTSTRAP_STATE_REL))
8171
8416
  });
8172
8417
  const record2 = planInstallRecord(targetRepo, manifestText);
8173
8418
  putPlannedFile(targetRepo, record2);
@@ -8524,10 +8769,10 @@ function crewProfileRel(agent, callsign) {
8524
8769
  return agent === "claude-code" ? `${CLAUDE_AGENTS_DIR}/${callsign}.md` : `${CODEX_AGENTS_DIR}/${callsign.toLowerCase()}.toml`;
8525
8770
  }
8526
8771
  function crewProfileTarget(targetRepo, rel) {
8527
- const folder = rel.startsWith(`${CLAUDE_AGENTS_DIR}/`) ? CLAUDE_AGENTS_DIR : CODEX_AGENTS_DIR;
8528
- const target = resolve(join6(targetRepo, rel));
8529
- if (dirname3(target) !== resolve(targetRepo, folder)) {
8530
- throw new Error(`[halfcycle] crew profile "${rel}" does not sit directly in ${folder}.`);
8772
+ const folder2 = rel.startsWith(`${CLAUDE_AGENTS_DIR}/`) ? CLAUDE_AGENTS_DIR : CODEX_AGENTS_DIR;
8773
+ const target = resolve(join7(targetRepo, rel));
8774
+ if (dirname3(target) !== resolve(targetRepo, folder2)) {
8775
+ throw new Error(`[halfcycle] crew profile "${rel}" does not sit directly in ${folder2}.`);
8531
8776
  }
8532
8777
  return target;
8533
8778
  }
@@ -8614,35 +8859,35 @@ var __dirname = dirname3(__filename);
8614
8859
  function findBundleRoot(startDir) {
8615
8860
  let candidate = startDir;
8616
8861
  for (let i = 0; i < 4; i++) {
8617
- const manifest = join6(candidate, ".claude-plugin", "plugin.json");
8862
+ const manifest = join7(candidate, ".claude-plugin", "plugin.json");
8618
8863
  try {
8619
- if (existsSync4(manifest))
8864
+ if (existsSync5(manifest))
8620
8865
  return candidate;
8621
8866
  } catch {
8622
8867
  }
8623
8868
  candidate = dirname3(candidate);
8624
8869
  }
8625
- return join6(startDir, "..");
8870
+ return join7(startDir, "..");
8626
8871
  }
8627
8872
  var BUNDLE_ROOT = findBundleRoot(__dirname);
8628
8873
  function resolveVendoredBinary() {
8629
8874
  const candidates = [
8630
- join6(BUNDLE_ROOT, "bin", "bin.bundle.mjs"),
8631
- join6(BUNDLE_ROOT, "..", "runner", "dist", "bin.bundle.mjs")
8875
+ join7(BUNDLE_ROOT, "bin", "bin.bundle.mjs"),
8876
+ join7(BUNDLE_ROOT, "..", "runner", "dist", "bin.bundle.mjs")
8632
8877
  ];
8633
8878
  for (const candidate of candidates) {
8634
- if (existsSync4(candidate))
8879
+ if (existsSync5(candidate))
8635
8880
  return candidate;
8636
8881
  }
8637
8882
  throw new Error(`[halfcycle] Cannot find the self-contained guard binary bin.bundle.mjs. This copy of the halfcycle package is incomplete \u2014 reinstall it, or rebuild it if you are working on it. Looked in: ${candidates.join(", ")}.`);
8638
8883
  }
8639
8884
  function resolvePreFilterShell() {
8640
8885
  const candidates = [
8641
- join6(BUNDLE_ROOT, "bin", PRE_FILTER_SHELL_FILENAME),
8642
- join6(BUNDLE_ROOT, "..", "runner", "dist", PRE_FILTER_SHELL_FILENAME)
8886
+ join7(BUNDLE_ROOT, "bin", PRE_FILTER_SHELL_FILENAME),
8887
+ join7(BUNDLE_ROOT, "..", "runner", "dist", PRE_FILTER_SHELL_FILENAME)
8643
8888
  ];
8644
8889
  for (const candidate of candidates) {
8645
- if (existsSync4(candidate))
8890
+ if (existsSync5(candidate))
8646
8891
  return readFileSync6(candidate, "utf-8");
8647
8892
  }
8648
8893
  throw new Error(`[halfcycle] Cannot find ${PRE_FILTER_SHELL_FILENAME}. This copy of the halfcycle package is incomplete \u2014 reinstall it, or rebuild it if you are working on it. Looked in: ${candidates.join(", ")}.`);
@@ -8682,7 +8927,7 @@ function isAllowlisted(targetRelPath) {
8682
8927
  var CREW_ROSTER_REL = ".halfcycle/crew.json";
8683
8928
  var CAPTURED_INDEX_REL = "test/fixtures/captured/manifest.json";
8684
8929
  function capturedIndexScaffold() {
8685
- return join6(BUNDLE_ROOT, "scaffolding", "test", "fixtures", "captured", "manifest.json");
8930
+ return join7(BUNDLE_ROOT, "scaffolding", "test", "fixtures", "captured", "manifest.json");
8686
8931
  }
8687
8932
  var GITIGNORE_REL = ".gitignore";
8688
8933
  var GITATTRIBUTES_REL = ".gitattributes";
@@ -8757,13 +9002,13 @@ function closedWriteSet() {
8757
9002
  function sha256OfWhatThisVersionWrites(commands) {
8758
9003
  const out = /* @__PURE__ */ new Map();
8759
9004
  const fromFile = (rel, src) => {
8760
- if (existsSync4(src))
9005
+ if (existsSync5(src))
8761
9006
  out.set(rel, fileSha256(readFileSync6(src)));
8762
9007
  };
8763
9008
  for (const cmd of commands) {
8764
- const srcFile = join6(BUNDLE_ROOT, ".claude-plugin", cmd.path);
9009
+ const srcFile = join7(BUNDLE_ROOT, ".claude-plugin", cmd.path);
8765
9010
  fromFile(commandStubRel(cmd.name), srcFile);
8766
- if (existsSync4(srcFile)) {
9011
+ if (existsSync5(srcFile)) {
8767
9012
  out.set(skillRel(cmd.name), fileSha256(Buffer.from(codexSkillContent(cmd.name, readFileSync6(srcFile, "utf-8")), "utf-8")));
8768
9013
  }
8769
9014
  }
@@ -8781,7 +9026,7 @@ function bytesAreOurs(path, bytes, recorded, writeSet) {
8781
9026
  }
8782
9027
  function readPreviousManifest(targetRepo) {
8783
9028
  try {
8784
- return parseManifest(readFileSync6(join6(targetRepo, INSTALL_MANIFEST_REL), "utf-8"));
9029
+ return parseManifest(readFileSync6(join7(targetRepo, INSTALL_MANIFEST_REL), "utf-8"));
8785
9030
  } catch {
8786
9031
  return null;
8787
9032
  }
@@ -8885,8 +9130,8 @@ function findNotShared(targetRepo, manifest, collidedPaths) {
8885
9130
  for (let dir = posix2.dirname(rel); dir !== "."; dir = posix2.dirname(dir)) {
8886
9131
  let plain = dirVerdicts.get(dir);
8887
9132
  if (plain === void 0) {
8888
- const abs = join6(targetRepo, dir);
8889
- plain = !lstatOrNull(abs)?.isSymbolicLink() && lstatOrNull(join6(abs, ".git")) === null;
9133
+ const abs = join7(targetRepo, dir);
9134
+ plain = !lstatOrNull(abs)?.isSymbolicLink() && lstatOrNull(join7(abs, ".git")) === null;
8890
9135
  dirVerdicts.set(dir, plain);
8891
9136
  }
8892
9137
  if (!plain)
@@ -8900,7 +9145,7 @@ function findNotShared(targetRepo, manifest, collidedPaths) {
8900
9145
  ...merged,
8901
9146
  INSTALL_MANIFEST_REL
8902
9147
  ])
8903
- ].filter((rel) => !collided.has(rel)).filter((rel) => lstatOrNull(join6(targetRepo, rel)) !== null).filter(travelsThroughPlainDirs).sort();
9148
+ ].filter((rel) => !collided.has(rel)).filter((rel) => lstatOrNull(join7(targetRepo, rel)) !== null).filter(travelsThroughPlainDirs).sort();
8904
9149
  const ignored = readIgnoredPaths(targetRepo, paths);
8905
9150
  if (ignored === null)
8906
9151
  return null;
@@ -8932,7 +9177,7 @@ function isPlainObject2(value) {
8932
9177
  return value !== null && typeof value === "object" && !Array.isArray(value);
8933
9178
  }
8934
9179
  function readPluginManifest() {
8935
- const manifestPath = join6(BUNDLE_ROOT, ".claude-plugin", "plugin.json");
9180
+ const manifestPath = join7(BUNDLE_ROOT, ".claude-plugin", "plugin.json");
8936
9181
  const raw = readFileSync6(manifestPath, "utf-8");
8937
9182
  return JSON.parse(raw);
8938
9183
  }
@@ -8988,14 +9233,14 @@ ${transformed}`;
8988
9233
  function planManifestCommands(manifest, targetRepo, previous, writeSet) {
8989
9234
  const recordedFiles = new Map((previous?.files ?? []).filter((file) => writeSet.files.has(file.path)).map((file) => [file.path, file]));
8990
9235
  return (manifest.commands ?? []).map((cmd) => {
8991
- const srcFile = join6(BUNDLE_ROOT, ".claude-plugin", cmd.path);
8992
- if (!existsSync4(srcFile)) {
9236
+ const srcFile = join7(BUNDLE_ROOT, ".claude-plugin", cmd.path);
9237
+ if (!existsSync5(srcFile)) {
8993
9238
  throw new Error(`[halfcycle] plugin.json declares command "${cmd.name}" at "${cmd.path}", but no file exists there (${srcFile}). The manifest and commands/ must agree.`);
8994
9239
  }
8995
9240
  const rel = commandStubRel(cmd.name);
8996
9241
  const content = readFileSync6(srcFile, "utf-8");
8997
9242
  const recorded = recordedFiles.get(rel);
8998
- const planned = decideCollisionSafe(join6(targetRepo, rel), targetRepo, content, (current) => bytesAreOurs(rel, current, recorded, writeSet));
9243
+ const planned = decideCollisionSafe(join7(targetRepo, rel), targetRepo, content, (current) => bytesAreOurs(rel, current, recorded, writeSet));
8999
9244
  return { ...planned, editedCollided: planned.outcome === "collided" && recorded !== void 0 };
9000
9245
  });
9001
9246
  }
@@ -9005,7 +9250,7 @@ function putPlannedFile(targetRepoRoot, planned) {
9005
9250
  if (!isAllowlisted(planned.rel)) {
9006
9251
  throw new Error(`[halfcycle] Write-allowlist violation: attempted to write "${planned.rel}". Only the method surface and scaffolding paths are writable.`);
9007
9252
  }
9008
- const target = join6(targetRepoRoot, planned.rel);
9253
+ const target = join7(targetRepoRoot, planned.rel);
9009
9254
  mkdirSync5(dirname3(target), { recursive: true });
9010
9255
  writeFileSync5(target, planned.text, "utf-8");
9011
9256
  }
@@ -9025,7 +9270,7 @@ function decideCollisionSafe(targetAbsPath, targetRepoRoot, content, isOurs) {
9025
9270
  if (!isAllowlisted(rel)) {
9026
9271
  throw new Error(`[halfcycle] Write-allowlist violation: attempted to write "${rel}". Only the method surface and scaffolding paths are writable.`);
9027
9272
  }
9028
- const current = existsSync4(targetAbsPath) ? readFileSync6(targetAbsPath) : null;
9273
+ const current = existsSync5(targetAbsPath) ? readFileSync6(targetAbsPath) : null;
9029
9274
  const text = inLineEndingOf(current?.toString("utf-8") ?? null, content);
9030
9275
  if (current === null)
9031
9276
  return { rel, text, outcome: "written", replacedExisting: false };
@@ -9053,7 +9298,7 @@ function decideOwned(targetAbsPath, targetRepoRoot, content) {
9053
9298
  const text = inLineEndingOf(current, content);
9054
9299
  if (current === text)
9055
9300
  return { rel, text, outcome: "skipped", replacedExisting: false };
9056
- return { rel, text, outcome: "written", replacedExisting: existsSync4(targetAbsPath) };
9301
+ return { rel, text, outcome: "written", replacedExisting: existsSync5(targetAbsPath) };
9057
9302
  }
9058
9303
  var GENERATED_HEADER_LINE_INDEX = 1;
9059
9304
  function decideOwnedGenerated(targetAbsPath, targetRepoRoot, content, generatedMarker) {
@@ -9061,7 +9306,7 @@ function decideOwnedGenerated(targetAbsPath, targetRepoRoot, content, generatedM
9061
9306
  if (!isAllowlisted(rel)) {
9062
9307
  throw new Error(`[halfcycle] Write-allowlist violation: attempted to write "${rel}". Only the method surface and scaffolding paths are writable.`);
9063
9308
  }
9064
- if (!existsSync4(targetAbsPath))
9309
+ if (!existsSync5(targetAbsPath))
9065
9310
  return { rel, text: content, outcome: "written", replacedExisting: false };
9066
9311
  const current = readFileSync6(targetAbsPath, "utf-8");
9067
9312
  if (current === content)
@@ -9079,7 +9324,7 @@ function recordOwnedGenerated(report, planned) {
9079
9324
  return planned.outcome;
9080
9325
  }
9081
9326
  function recordOwned(report, targetAbsPath, targetRepoRoot, content, rel) {
9082
- const existedBefore = existsSync4(targetAbsPath);
9327
+ const existedBefore = existsSync5(targetAbsPath);
9083
9328
  const outcome = writeOwned(targetAbsPath, targetRepoRoot, content);
9084
9329
  record(report, outcome, rel);
9085
9330
  if (outcome === "written" && existedBefore)
@@ -9101,8 +9346,8 @@ var DENY_PATTERNS = [
9101
9346
  ];
9102
9347
  var USER_PROMPT_REMINDER_SENTENCE = "Where this project stands, and what order to work in, is recorded in the Halfcycle service and nowhere in this repository \u2014 call halfcycle_resolve before you answer or plan, because the files here will look complete and current and still be wrong about position.";
9103
9348
  function readExistingSettings(targetRepo) {
9104
- const settingsPath = join6(targetRepo, SETTINGS_REL);
9105
- return existsSync4(settingsPath) ? JSON.parse(readFileSync6(settingsPath, "utf-8")) : void 0;
9349
+ const settingsPath = join7(targetRepo, SETTINGS_REL);
9350
+ return existsSync5(settingsPath) ? JSON.parse(readFileSync6(settingsPath, "utf-8")) : void 0;
9106
9351
  }
9107
9352
  var SettingsParseRefused = class extends Error {
9108
9353
  constructor(message) {
@@ -9111,7 +9356,7 @@ var SettingsParseRefused = class extends Error {
9111
9356
  }
9112
9357
  };
9113
9358
  function settingsParseRefusal(targetRepo, err) {
9114
- const file = join6(targetRepo, SETTINGS_REL);
9359
+ const file = join7(targetRepo, SETTINGS_REL);
9115
9360
  const offset = /position (\d+)/.exec(err instanceof Error ? err.message : "")?.[1];
9116
9361
  let where = "";
9117
9362
  if (offset !== void 0) {
@@ -9125,15 +9370,15 @@ var HALFCYCLE_STATUS_LINE = Object.freeze({
9125
9370
  type: "command",
9126
9371
  command: 'bash "$CLAUDE_PROJECT_DIR"/.claude/hooks/guard-runner.sh statusline'
9127
9372
  });
9128
- function statusLineSetElsewhere(targetRepo, home = homedir2(), env = process.env) {
9129
- const places = [join6(targetRepo, ".claude", "settings.local.json"), join6(home, ".claude", "settings.json")];
9373
+ function statusLineSetElsewhere(targetRepo, home = homedir3(), env = process.env) {
9374
+ const places = [join7(targetRepo, ".claude", "settings.local.json"), join7(home, ".claude", "settings.json")];
9130
9375
  const configDir = env["CLAUDE_CONFIG_DIR"];
9131
9376
  if (configDir !== void 0 && configDir !== "") {
9132
- const expanded = configDir === "~" ? home : configDir.startsWith("~/") ? join6(home, configDir.slice(2)) : configDir;
9133
- places.push(join6(isAbsolute2(expanded) ? expanded : resolve(expanded), "settings.json"));
9377
+ const expanded = configDir === "~" ? home : configDir.startsWith("~/") ? join7(home, configDir.slice(2)) : configDir;
9378
+ places.push(join7(isAbsolute2(expanded) ? expanded : resolve(expanded), "settings.json"));
9134
9379
  }
9135
9380
  return places.some((path) => {
9136
- if (!existsSync4(path))
9381
+ if (!existsSync5(path))
9137
9382
  return false;
9138
9383
  let value;
9139
9384
  try {
@@ -9478,7 +9723,8 @@ ${GENERATED_USER_PROMPT_SCOPE_HEADER} the Halfcycle installer, now also for the
9478
9723
  # <phase>/<part>", "question <phase>/<part>: <words>", "carry <phase>/<part>:
9479
9724
  # <words>", "drop <phase>/<part>: <words>", "reject <phase>/<task>: <words>",
9480
9725
  # "autonomy on" or "autonomy off". Also lets the person who is typing let one
9481
- # step Halfcycle paused run once, by typing "allow" as the whole prompt, and change
9726
+ # step Halfcycle paused run once, by typing "allow", or "allow" and the step's
9727
+ # number ("allow 2"), as the whole prompt, and change
9482
9728
  # how a build is paced, by typing "faster", "run 5 agents", "keep 30% free" or
9483
9729
  # "use up to 70%" as the whole prompt, and answer a phase's usage limit by
9484
9730
  # typing "keep going", "stop", "limit it to 1.5 times" or "limit it to 300
@@ -9488,7 +9734,8 @@ ${GENERATED_USER_PROMPT_SCOPE_HEADER} the Halfcycle installer, now also for the
9488
9734
  # looks only at the start of the prompt's value. Unless the prompt begins, after
9489
9735
  # any whitespace (typed, or escaped in the input), with "approve ", "send back ",
9490
9736
  # "accept ", "question ", "carry ", "drop ", "reject " or "autonomy ", in any case,
9491
- # or is "allow", one of the five pacing phrases or one of the usage-limit phrases
9737
+ # or is "allow" (alone, or with spaces and a number of up to four digits), one
9738
+ # of the five pacing phrases or one of the usage-limit phrases
9492
9739
  # and nothing else but whitespace,
9493
9740
  # it exits 0 with no output, and Node never starts. A quote inside any other field arrives
9494
9741
  # escaped, so no other field (the folder the session is in, for one) can read as
@@ -9516,7 +9763,7 @@ IFS= read -r -d '' HC_INPUT || true
9516
9763
  # \\u escape, or as the UTF-8 bytes of a Unicode space. Compared byte for byte.
9517
9764
  HC_LEAD='[[:space:]]|\\\\[nrtf]|\\\\u(000[9a-d]|0020|00a0|1680|200[0-9a]|202[89f]|205f|3000|feff)'
9518
9765
  HC_LEAD="$HC_LEAD|"$'\\xc2\\xa0|\\xe1\\x9a\\x80|\\xe2\\x80[\\x80-\\x8a\\xa8\\xa9\\xaf]|\\xe2\\x81\\x9f|\\xe3\\x80\\x80|\\xef\\xbb\\xbf'
9519
- 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($HC_LEAD)*\\"|(faster|go faster|run [0-9]{1,3} agents?|keep [0-9]{1,3}% free|use up to [0-9]{1,3}%|keep going|stop|stop here|limit it to [0-9]{1,9}(\\.[0-9]{1,2})? (times|million|billion|million tokens|billion tokens))($HC_LEAD)*\\")"; then
9766
+ 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)*\\"|(faster|go faster|run [0-9]{1,3} agents?|keep [0-9]{1,3}% free|use up to [0-9]{1,3}%|keep going|stop|stop here|limit it to [0-9]{1,9}(\\.[0-9]{1,2})? (times|million|billion|million tokens|billion tokens))($HC_LEAD)*\\")"; then
9520
9767
  exit 0
9521
9768
  fi
9522
9769
 
@@ -9609,8 +9856,8 @@ function removeRetiredGeneratedFile(targetRepo, rel, headerLineIndex, knownHeade
9609
9856
  return decided === "removed" ? removePlannedRetiredFile(targetRepo, rel) : decided;
9610
9857
  }
9611
9858
  function decideRetiredRemoval(targetRepo, rel, headerLineIndex, knownHeaderHashes) {
9612
- const path = join6(targetRepo, rel);
9613
- if (!existsSync4(path))
9859
+ const path = join7(targetRepo, rel);
9860
+ if (!existsSync5(path))
9614
9861
  return "absent";
9615
9862
  let raw;
9616
9863
  try {
@@ -9628,7 +9875,7 @@ function decideRetiredRemoval(targetRepo, rel, headerLineIndex, knownHeaderHashe
9628
9875
  }
9629
9876
  function removePlannedRetiredFile(targetRepo, rel) {
9630
9877
  try {
9631
- rmSync2(join6(targetRepo, rel));
9878
+ rmSync2(join7(targetRepo, rel));
9632
9879
  } catch {
9633
9880
  return "failed";
9634
9881
  }
@@ -10004,9 +10251,9 @@ function gitignoreCovers(content) {
10004
10251
  return REQUIRED_GITIGNORE_ENTRIES.every((entry) => lines.has(entry));
10005
10252
  }
10006
10253
  function planGitignore(targetRepo) {
10007
- const gitignorePath = join6(targetRepo, GITIGNORE_REL);
10254
+ const gitignorePath = join7(targetRepo, GITIGNORE_REL);
10008
10255
  const header = GITIGNORE_HEADER;
10009
- const before = existsSync4(gitignorePath) ? readFileSync6(gitignorePath, "utf-8") : null;
10256
+ const before = existsSync5(gitignorePath) ? readFileSync6(gitignorePath, "utf-8") : null;
10010
10257
  if (before === null) {
10011
10258
  return { before, after: [header, ...REQUIRED_GITIGNORE_ENTRIES].join("\n") + "\n", outcome: "written" };
10012
10259
  }
@@ -10022,7 +10269,7 @@ function putPlannedGitignore(targetRepo, planned) {
10022
10269
  if (planned.outcome !== "written" || planned.after === null)
10023
10270
  return planned.outcome;
10024
10271
  try {
10025
- writeFileSync5(join6(targetRepo, GITIGNORE_REL), planned.after, "utf-8");
10272
+ writeFileSync5(join7(targetRepo, GITIGNORE_REL), planned.after, "utf-8");
10026
10273
  return "written";
10027
10274
  } catch {
10028
10275
  return "failed";
@@ -10041,12 +10288,12 @@ function lfRules(paths) {
10041
10288
  }
10042
10289
  var REQUIRED_GITATTRIBUTES_RULES = lfRules(Object.keys(OWNED_GENERATED_HEADERS));
10043
10290
  function planGitattributes(targetRepo) {
10044
- const path = join6(targetRepo, GITATTRIBUTES_REL);
10291
+ const path = join7(targetRepo, GITATTRIBUTES_REL);
10045
10292
  const rules = REQUIRED_GITATTRIBUTES_RULES;
10046
10293
  const before = readTextOrNull(path);
10047
10294
  if (lstatOrNull(path)?.isSymbolicLink() === true)
10048
10295
  return { before, after: before, outcome: "failed" };
10049
- if (!existsSync4(path)) {
10296
+ if (!existsSync5(path)) {
10050
10297
  return { before, after: [GITATTRIBUTES_HEADER, ...rules].join("\n") + "\n", outcome: "written" };
10051
10298
  }
10052
10299
  if (before === null)
@@ -10064,7 +10311,7 @@ function putPlannedGitattributes(targetRepo, planned) {
10064
10311
  if (planned.outcome !== "written" || planned.after === null)
10065
10312
  return planned.outcome;
10066
10313
  try {
10067
- writeAllowlisted(join6(targetRepo, GITATTRIBUTES_REL), targetRepo, planned.after, []);
10314
+ writeAllowlisted(join7(targetRepo, GITATTRIBUTES_REL), targetRepo, planned.after, []);
10068
10315
  return "written";
10069
10316
  } catch {
10070
10317
  return "failed";
@@ -10098,7 +10345,7 @@ function planBundlePin(targetRepoRoot, version, engagementId, engagementType, cr
10098
10345
  ...carriedMethodVersion !== void 0 ? { methodVersion: carriedMethodVersion } : {},
10099
10346
  ...projectPage !== void 0 ? { projectPage } : {}
10100
10347
  };
10101
- const current = readFileOrNull(join6(targetRepoRoot, BUNDLE_PIN_REL));
10348
+ const current = readFileOrNull(join7(targetRepoRoot, BUNDLE_PIN_REL));
10102
10349
  const text = inLineEndingOf(current, JSON.stringify(pin, null, 2) + "\n");
10103
10350
  return {
10104
10351
  rel: BUNDLE_PIN_REL,
@@ -10129,7 +10376,7 @@ function planBundlePinPreview(targetRepoRoot, version, home) {
10129
10376
  return planBundlePin(targetRepoRoot, version, existing.engagementId, existing.engagementType, void 0, void 0);
10130
10377
  }
10131
10378
  function writeBundlePin(targetRepoRoot, planned, writtenPaths) {
10132
- const pinPath = join6(targetRepoRoot, BUNDLE_PIN_REL);
10379
+ const pinPath = join7(targetRepoRoot, BUNDLE_PIN_REL);
10133
10380
  writeAllowlisted(pinPath, targetRepoRoot, planned.text, writtenPaths);
10134
10381
  }
10135
10382
  function updatePinnedMethodVersion(repoRoot, engagementId, version) {
@@ -10141,7 +10388,7 @@ function updatePinnedMethodVersion(repoRoot, engagementId, version) {
10141
10388
  }
10142
10389
  if (pin === null || pin.engagementId !== engagementId)
10143
10390
  return;
10144
- const pinPath = join6(repoRoot, BUNDLE_PIN_REL);
10391
+ const pinPath = join7(repoRoot, BUNDLE_PIN_REL);
10145
10392
  const updated = { ...pin, methodVersion: version };
10146
10393
  const pinText = inLineEndingOf(readFileOrNull(pinPath), JSON.stringify(updated, null, 2) + "\n");
10147
10394
  try {
@@ -10161,15 +10408,15 @@ function renderCrewRoster() {
10161
10408
  }
10162
10409
  function writeCrewRoster(targetRepoRoot, report) {
10163
10410
  const rendered = renderCrewRoster();
10164
- const crewPath = join6(targetRepoRoot, CREW_ROSTER_REL);
10411
+ const crewPath = join7(targetRepoRoot, CREW_ROSTER_REL);
10165
10412
  return recordOwned(report, crewPath, targetRepoRoot, rendered, CREW_ROSTER_REL);
10166
10413
  }
10167
10414
  function pinCreatedByAccountId(pin) {
10168
10415
  return nonEmpty(pin?.createdByAccountId) ?? nonEmpty(pin?.accountId);
10169
10416
  }
10170
10417
  function readBundlePin(targetRepoRoot) {
10171
- const pinPath = join6(targetRepoRoot, BUNDLE_PIN_REL);
10172
- if (!existsSync4(pinPath))
10418
+ const pinPath = join7(targetRepoRoot, BUNDLE_PIN_REL);
10419
+ if (!existsSync5(pinPath))
10173
10420
  return null;
10174
10421
  return JSON.parse(readFileSync6(pinPath, "utf-8"));
10175
10422
  }
@@ -10194,7 +10441,7 @@ function readPinnedEngagement(targetRepoRoot, home) {
10194
10441
  }
10195
10442
  function readLegacyEnvLocal(targetRepoRoot) {
10196
10443
  try {
10197
- return parseEnvText(readFileSync6(join6(targetRepoRoot, LEGACY_ENV_LOCAL_REL), "utf-8"));
10444
+ return parseEnvText(readFileSync6(join7(targetRepoRoot, LEGACY_ENV_LOCAL_REL), "utf-8"));
10198
10445
  } catch {
10199
10446
  return null;
10200
10447
  }
@@ -10289,8 +10536,8 @@ function engagementCredentialValues(credential, engagementId) {
10289
10536
  }
10290
10537
  var LEGACY_ENV_LOCAL_REL = ".env.local";
10291
10538
  function migrateLegacyEnvLocal(targetRepo, stored) {
10292
- const path = join6(targetRepo, LEGACY_ENV_LOCAL_REL);
10293
- if (!existsSync4(path))
10539
+ const path = join7(targetRepo, LEGACY_ENV_LOCAL_REL);
10540
+ if (!existsSync5(path))
10294
10541
  return { outcome: "absent", unbacked: [], missingForAdoption: [] };
10295
10542
  let raw;
10296
10543
  try {
@@ -10320,7 +10567,7 @@ function migrateLegacyEnvLocal(targetRepo, stored) {
10320
10567
  return { outcome: "stripped", unbacked: [], missingForAdoption: [], keptText: kept.join("\n") };
10321
10568
  }
10322
10569
  function applyLegacyMigration(targetRepo, decided) {
10323
- const path = join6(targetRepo, LEGACY_ENV_LOCAL_REL);
10570
+ const path = join7(targetRepo, LEGACY_ENV_LOCAL_REL);
10324
10571
  try {
10325
10572
  if (decided.outcome === "removed") {
10326
10573
  rmSync2(path);
@@ -10346,23 +10593,23 @@ function planCodexSkills(targetRepo, previous, writeSet) {
10346
10593
  const manifest = readPluginManifest();
10347
10594
  const recordedFiles = new Map((previous?.files ?? []).filter((file) => writeSet.files.has(file.path)).map((file) => [file.path, file]));
10348
10595
  return (manifest.commands ?? []).map((cmd) => {
10349
- const srcFile = join6(BUNDLE_ROOT, ".claude-plugin", cmd.path);
10350
- if (!existsSync4(srcFile)) {
10596
+ const srcFile = join7(BUNDLE_ROOT, ".claude-plugin", cmd.path);
10597
+ if (!existsSync5(srcFile)) {
10351
10598
  throw new Error(`[halfcycle] plugin.json declares command "${cmd.name}" at "${cmd.path}", but no file exists there (${srcFile}). The manifest and commands/ must agree.`);
10352
10599
  }
10353
10600
  const rel = skillRel(cmd.name);
10354
10601
  const content = codexSkillContent(cmd.name, readFileSync6(srcFile, "utf-8"));
10355
10602
  const recorded = recordedFiles.get(rel);
10356
- const planned = decideCollisionSafe(join6(targetRepo, rel), targetRepo, content, (current) => bytesAreOurs(rel, current, recorded, writeSet));
10603
+ const planned = decideCollisionSafe(join7(targetRepo, rel), targetRepo, content, (current) => bytesAreOurs(rel, current, recorded, writeSet));
10357
10604
  return { ...planned, editedCollided: planned.outcome === "collided" && recorded !== void 0 };
10358
10605
  });
10359
10606
  }
10360
10607
  function planVendoredBinary(targetRepo) {
10361
- return decideOwned(join6(targetRepo, ".halfcycle", "bin", "bin.bundle.mjs"), targetRepo, readFileSync6(resolveVendoredBinary(), "utf-8"));
10608
+ return decideOwned(join7(targetRepo, ".halfcycle", "bin", "bin.bundle.mjs"), targetRepo, readFileSync6(resolveVendoredBinary(), "utf-8"));
10362
10609
  }
10363
10610
  function planSettings(targetRepo, previous, home, env) {
10364
- const settingsPath = join6(targetRepo, SETTINGS_REL);
10365
- const preexisted = existsSync4(settingsPath);
10611
+ const settingsPath = join7(targetRepo, SETTINGS_REL);
10612
+ const preexisted = existsSync5(settingsPath);
10366
10613
  const generated = JSON.parse(generateSettingsJson());
10367
10614
  let existing;
10368
10615
  try {
@@ -10386,11 +10633,11 @@ function planSettings(targetRepo, previous, home, env) {
10386
10633
  return { file, preexisted, existing, generated, merged, retiredOurs };
10387
10634
  }
10388
10635
  function planHookScripts(targetRepo) {
10389
- return hookScriptsThisVersionWrites().map(([rel, content]) => decideOwnedGenerated(join6(targetRepo, rel), targetRepo, content, OWNED_GENERATED_HEADERS[rel]));
10636
+ return hookScriptsThisVersionWrites().map(([rel, content]) => decideOwnedGenerated(join7(targetRepo, rel), targetRepo, content, OWNED_GENERATED_HEADERS[rel]));
10390
10637
  }
10391
10638
  function planMcpRegistration(targetRepo, credential) {
10392
- const mcpPath = join6(targetRepo, MCP_REGISTRATION_REL);
10393
- const existing = existsSync4(mcpPath) ? readFileSync6(mcpPath, "utf-8") : null;
10639
+ const mcpPath = join7(targetRepo, MCP_REGISTRATION_REL);
10640
+ const existing = existsSync5(mcpPath) ? readFileSync6(mcpPath, "utf-8") : null;
10394
10641
  if (credential === void 0)
10395
10642
  return { kind: "none", existing };
10396
10643
  if (credential === "pending")
@@ -10402,11 +10649,11 @@ function planMcpRegistration(targetRepo, credential) {
10402
10649
  function putPlannedMcp(targetRepo, planned) {
10403
10650
  if (planned.kind !== "write" || planned.outcome === "skipped")
10404
10651
  return;
10405
- writeAllowlisted(join6(targetRepo, MCP_REGISTRATION_REL), targetRepo, planned.text, []);
10652
+ writeAllowlisted(join7(targetRepo, MCP_REGISTRATION_REL), targetRepo, planned.text, []);
10406
10653
  }
10407
10654
  function fileInFolderPlace(root, rel) {
10408
10655
  for (let dir = posix2.dirname(rel); dir !== "."; dir = posix2.dirname(dir)) {
10409
- const found = lstatOrNull(join6(root, dir));
10656
+ const found = lstatOrNull(join7(root, dir));
10410
10657
  if (found !== null && !found.isDirectory())
10411
10658
  return true;
10412
10659
  }
@@ -10415,7 +10662,7 @@ function fileInFolderPlace(root, rel) {
10415
10662
  function symlinkOnPath(root, rel) {
10416
10663
  let current = root;
10417
10664
  for (const segment of rel.split("/")) {
10418
- current = join6(current, segment);
10665
+ current = join7(current, segment);
10419
10666
  try {
10420
10667
  if (lstatSync(current).isSymbolicLink())
10421
10668
  return true;
@@ -10436,10 +10683,10 @@ function planCodexHooks(targetRepo) {
10436
10683
  });
10437
10684
  if (symlinkOnPath(targetRepo, CODEX_HOOKS_REL2))
10438
10685
  return leave("link", null);
10439
- const folder = lstatOrNull(join6(targetRepo, posix2.dirname(CODEX_HOOKS_REL2)));
10440
- if (folder !== null && !folder.isDirectory())
10686
+ const folder2 = lstatOrNull(join7(targetRepo, posix2.dirname(CODEX_HOOKS_REL2)));
10687
+ if (folder2 !== null && !folder2.isDirectory())
10441
10688
  return leave("unreadable", null);
10442
- const path = join6(targetRepo, CODEX_HOOKS_REL2);
10689
+ const path = join7(targetRepo, CODEX_HOOKS_REL2);
10443
10690
  let before = null;
10444
10691
  if (lstatOrNull(path) !== null) {
10445
10692
  try {
@@ -10477,13 +10724,13 @@ function planCodexHooks(targetRepo) {
10477
10724
  function putPlannedCodexHooks(targetRepo, planned) {
10478
10725
  if (planned.outcome !== "written" || planned.text === null)
10479
10726
  return;
10480
- writeAllowlisted(join6(targetRepo, CODEX_HOOKS_REL2), targetRepo, planned.text, []);
10727
+ writeAllowlisted(join7(targetRepo, CODEX_HOOKS_REL2), targetRepo, planned.text, []);
10481
10728
  }
10482
10729
  function readCodexConfig(targetRepo) {
10483
- const folder = lstatOrNull(join6(targetRepo, posix2.dirname(CODEX_CONFIG_REL2)));
10484
- if (folder !== null && !folder.isDirectory())
10730
+ const folder2 = lstatOrNull(join7(targetRepo, posix2.dirname(CODEX_CONFIG_REL2)));
10731
+ if (folder2 !== null && !folder2.isDirectory())
10485
10732
  return "unreadable";
10486
- const path = join6(targetRepo, CODEX_CONFIG_REL2);
10733
+ const path = join7(targetRepo, CODEX_CONFIG_REL2);
10487
10734
  const stat = lstatOrNull(path);
10488
10735
  if (stat === null)
10489
10736
  return { text: null };
@@ -10537,10 +10784,10 @@ function planCodexConfig(targetRepo, credential) {
10537
10784
  function putPlannedCodexConfig(targetRepo, planned) {
10538
10785
  if (planned.outcome !== "written" || planned.text === null)
10539
10786
  return;
10540
- writeAllowlisted(join6(targetRepo, CODEX_CONFIG_REL2), targetRepo, planned.text, []);
10787
+ writeAllowlisted(join7(targetRepo, CODEX_CONFIG_REL2), targetRepo, planned.text, []);
10541
10788
  }
10542
10789
  function planCrewRoster(targetRepo) {
10543
- return decideOwned(join6(targetRepo, CREW_ROSTER_REL), targetRepo, renderCrewRoster());
10790
+ return decideOwned(join7(targetRepo, CREW_ROSTER_REL), targetRepo, renderCrewRoster());
10544
10791
  }
10545
10792
  function planCrewProfiles(targetRepo, previous, writeSet) {
10546
10793
  const recordedFiles = new Map((previous?.files ?? []).filter((file) => writeSet.files.has(file.path)).map((file) => [file.path, file]));
@@ -10555,7 +10802,7 @@ function planCrewProfiles(targetRepo, previous, writeSet) {
10555
10802
  });
10556
10803
  }
10557
10804
  function planInstallRecord(targetRepo, text) {
10558
- return decideOwned(join6(targetRepo, INSTALL_MANIFEST_REL), targetRepo, text);
10805
+ return decideOwned(join7(targetRepo, INSTALL_MANIFEST_REL), targetRepo, text);
10559
10806
  }
10560
10807
  function accountInstall(steps, actual) {
10561
10808
  const { targetRepo, writeSet, pin } = steps;
@@ -10783,7 +11030,7 @@ function mcpHasHalfcycleEntry(text) {
10783
11030
  function writeMissingSelfBuildCrewProfiles(targetRepoRoot, report) {
10784
11031
  for (const { rel, text } of renderCrewProfiles()) {
10785
11032
  const target = crewProfileTarget(targetRepoRoot, rel);
10786
- if (existsSync4(target))
11033
+ if (existsSync5(target))
10787
11034
  continue;
10788
11035
  writeAllowlisted(target, targetRepoRoot, text, report.writtenPaths);
10789
11036
  }
@@ -10791,7 +11038,7 @@ function writeMissingSelfBuildCrewProfiles(targetRepoRoot, report) {
10791
11038
  function installSelfBuild(options) {
10792
11039
  const { targetRepo, engagementId, credential, freshlyMinted, home } = options;
10793
11040
  assertEngagementId(engagementId);
10794
- if (!existsSync4(targetRepo)) {
11041
+ if (!existsSync5(targetRepo)) {
10795
11042
  throw new Error(`[halfcycle] Target repo does not exist: ${targetRepo}`);
10796
11043
  }
10797
11044
  const report = {
@@ -10802,7 +11049,7 @@ function installSelfBuild(options) {
10802
11049
  editedCollidedPaths: [],
10803
11050
  replacedPaths: []
10804
11051
  };
10805
- const at = (rel) => join6(targetRepo, rel);
11052
+ const at = (rel) => join7(targetRepo, rel);
10806
11053
  const version = readPluginManifest().version;
10807
11054
  const existingMcp = readFileOrNull(at(MCP_REGISTRATION_REL));
10808
11055
  const mcpText = existingMcp === null || !mcpHasHalfcycleEntry(existingMcp) ? inLineEndingOf(existingMcp, generateMcpRegistration(existingMcp, credential.mcpUrl)) : null;
@@ -10813,17 +11060,17 @@ function installSelfBuild(options) {
10813
11060
  if (mcpText !== null) {
10814
11061
  writeAllowlisted(at(MCP_REGISTRATION_REL), targetRepo, mcpText, report.writtenPaths);
10815
11062
  }
10816
- if (!existsSync4(at(VENDORED_BIN_REL))) {
11063
+ if (!existsSync5(at(VENDORED_BIN_REL))) {
10817
11064
  recordOwned(report, at(VENDORED_BIN_REL), targetRepo, readFileSync6(resolveVendoredBinary(), "utf-8"), VENDORED_BIN_REL);
10818
11065
  }
10819
- if (!existsSync4(at(SELF_BUILD_GITIGNORE_REL))) {
11066
+ if (!existsSync5(at(SELF_BUILD_GITIGNORE_REL))) {
10820
11067
  writeAllowlisted(at(SELF_BUILD_GITIGNORE_REL), targetRepo, SELF_BUILD_GITIGNORE_TEXT, report.writtenPaths);
10821
11068
  }
10822
- if (!existsSync4(at(CREW_ROSTER_REL))) {
11069
+ if (!existsSync5(at(CREW_ROSTER_REL))) {
10823
11070
  writeCrewRoster(targetRepo, report);
10824
11071
  }
10825
11072
  writeMissingSelfBuildCrewProfiles(targetRepo, report);
10826
- if (!existsSync4(at(BUNDLE_PIN_REL))) {
11073
+ if (!existsSync5(at(BUNDLE_PIN_REL))) {
10827
11074
  writeBundlePin(targetRepo, planBundlePin(targetRepo, version, engagementId, "self-build", void 0, credential.methodVersion), report.writtenPaths);
10828
11075
  }
10829
11076
  return { writtenPaths: report.writtenPaths, credentialWritten };
@@ -11159,7 +11406,7 @@ function localDeadline(started) {
11159
11406
  return new Date(started.expiresAt).getTime() + started.pollIntervalMs * DEADLINE_SLACK_INTERVALS;
11160
11407
  }
11161
11408
  async function pollForCredential(serviceUrl, started, deps = {}) {
11162
- const write = deps.write ?? defaultWrite;
11409
+ const write2 = deps.write ?? defaultWrite;
11163
11410
  const sleep = deps.sleep ?? defaultSleep;
11164
11411
  const now = deps.now ?? Date.now;
11165
11412
  const url = tokenUrlFor(serviceUrl);
@@ -11181,7 +11428,7 @@ async function pollForCredential(serviceUrl, started, deps = {}) {
11181
11428
  }
11182
11429
  if (now() - announcedAt >= (deps.stillWaitingMs ?? STILL_WAITING_MS)) {
11183
11430
  announcedAt = now();
11184
- write(stillWaitingLine(announcedAt - startedAt, started));
11431
+ write2(stillWaitingLine(announcedAt - startedAt, started));
11185
11432
  }
11186
11433
  await sleep(interval);
11187
11434
  }
@@ -11240,13 +11487,13 @@ async function pollBesideLoopback(serviceUrl, started, wake, deps) {
11240
11487
  }
11241
11488
  }
11242
11489
  async function signIn(serviceUrl, deps = {}) {
11243
- const write = deps.write ?? defaultWrite;
11490
+ const write2 = deps.write ?? defaultWrite;
11244
11491
  const now = deps.now ?? Date.now;
11245
11492
  const env = deps.env ?? process.env;
11246
11493
  if (isNonInteractive(env)) {
11247
11494
  throw new SignInRefused(SIGN_IN_TIMED_OUT, SIGN_IN_NON_INTERACTIVE_MESSAGE);
11248
11495
  }
11249
- write(`[halfcycle] Halfcycle needs an account before it can ${deps.reason ?? DEFAULT_SIGN_IN_REASON} \u2014 signing you in through your browser.
11496
+ write2(`[halfcycle] Halfcycle needs an account before it can ${deps.reason ?? DEFAULT_SIGN_IN_REASON} \u2014 signing you in through your browser.
11250
11497
  `);
11251
11498
  const bind = await bindLoopback(env, deps.onwardEngagementId);
11252
11499
  let closed = false;
@@ -11262,17 +11509,17 @@ async function signIn(serviceUrl, deps = {}) {
11262
11509
  const url = loopbackVerificationUrl(started.verificationUrl, bind.target);
11263
11510
  const attempt = await openBrowser(url, env);
11264
11511
  if (attempt.opened) {
11265
- write(`[halfcycle] Opened your browser to finish signing in. Code: ${started.userCode}. If a browser did not open, go to: ${started.verificationUrl}. This terminal is waiting.
11512
+ write2(`[halfcycle] Opened your browser to finish signing in. Code: ${started.userCode}. If a browser did not open, go to: ${started.verificationUrl}. This terminal is waiting.
11266
11513
  `);
11267
11514
  const startedAt = now();
11268
11515
  const redirect = bind.waitForCallback({
11269
11516
  timeoutMs: localDeadline(started) - startedAt,
11270
- onStillWaiting: (elapsed) => write(stillWaitingLine(elapsed, started)),
11517
+ onStillWaiting: (elapsed) => write2(stillWaitingLine(elapsed, started)),
11271
11518
  stillWaitingMs: deps.stillWaitingMs ?? STILL_WAITING_MS
11272
11519
  });
11273
11520
  const approved = await pollBesideLoopback(serviceUrl, started, redirect.then(() => void 0), deps);
11274
11521
  await closeListener();
11275
- write(`[halfcycle] Signed in \u2014 continuing.
11522
+ write2(`[halfcycle] Signed in \u2014 continuing.
11276
11523
  `);
11277
11524
  return {
11278
11525
  accountId: approved.accountId,
@@ -11281,10 +11528,10 @@ async function signIn(serviceUrl, deps = {}) {
11281
11528
  };
11282
11529
  }
11283
11530
  await closeListener();
11284
- write(`[halfcycle] Falling back to the code-based sign-in: ${attempt.reason}.
11531
+ write2(`[halfcycle] Falling back to the code-based sign-in: ${attempt.reason}.
11285
11532
  `);
11286
11533
  } else {
11287
- write(`[halfcycle] Falling back to the code-based sign-in: ${bind.reason}.
11534
+ write2(`[halfcycle] Falling back to the code-based sign-in: ${bind.reason}.
11288
11535
  `);
11289
11536
  }
11290
11537
  return await deviceCodeSignIn(serviceUrl, started, env, deps);
@@ -11293,21 +11540,21 @@ async function signIn(serviceUrl, deps = {}) {
11293
11540
  }
11294
11541
  }
11295
11542
  async function deviceCodeSignIn(serviceUrl, started, env, deps) {
11296
- const write = deps.write ?? defaultWrite;
11297
- write(`[halfcycle] Your sign-in code: ${started.userCode}
11543
+ const write2 = deps.write ?? defaultWrite;
11544
+ write2(`[halfcycle] Your sign-in code: ${started.userCode}
11298
11545
  `);
11299
11546
  const attempt = await openBrowser(started.verificationUrl, env);
11300
11547
  if (attempt.opened) {
11301
- write(`[halfcycle] Opened your browser at ${started.verificationUrl}
11548
+ write2(`[halfcycle] Opened your browser at ${started.verificationUrl}
11302
11549
  `);
11303
11550
  } else {
11304
- write(`[halfcycle] Open this address to sign in: ${started.verificationUrl}
11551
+ write2(`[halfcycle] Open this address to sign in: ${started.verificationUrl}
11305
11552
  `);
11306
11553
  }
11307
- write(`[halfcycle] Check the code on that page matches the one above, then sign in. This terminal is waiting.
11554
+ write2(`[halfcycle] Check the code on that page matches the one above, then sign in. This terminal is waiting.
11308
11555
  `);
11309
11556
  const approved = await pollForCredential(serviceUrl, started, deps);
11310
- write(`[halfcycle] Signed in \u2014 continuing.
11557
+ write2(`[halfcycle] Signed in \u2014 continuing.
11311
11558
  `);
11312
11559
  return {
11313
11560
  accountId: approved.accountId,
@@ -11947,40 +12194,40 @@ function identitySwitchRemedy(home, interactive) {
11947
12194
  return interactive ? `To act as a different account: run this again and ${SWITCH_ACCOUNT_ANSWER}, ${setOrRemove} to be asked for a sign-in next time.` : `To act as a different account: ${setOrRemove} and run this again with a terminal to sign in.`;
11948
12195
  }
11949
12196
  async function confirmActingIdentity(serviceUrl, credential, act, deps = {}) {
11950
- const write = deps.write ?? ((text) => process.stdout.write(text));
12197
+ const write2 = deps.write ?? ((text) => process.stdout.write(text));
11951
12198
  const colour = deps.colour ?? decideColour({ isTTY: process.stdout.isTTY === true, env: deps.env ?? process.env });
11952
12199
  const identity = await describeAccount(serviceUrl, credential.credential, deps.fetchImpl);
11953
12200
  const plainLabel = accountLabel(identity, credential.accountId, deps.verbose === true);
11954
12201
  const label = plainLabel !== void 0 ? ink("keyBold", plainLabel, colour) : "";
11955
12202
  const acting = actingAs(credential, identity);
11956
12203
  const script = armScript(credential, act, accountLine(identity, label), plainLabel, deps.home);
11957
- write(script.preamble);
12204
+ write2(script.preamble);
11958
12205
  if (!script.asks)
11959
12206
  return acting;
11960
12207
  if (deps.assumeYes === true) {
11961
- write(`[halfcycle] Continuing as that account without asking, because --yes was given.
12208
+ write2(`[halfcycle] Continuing as that account without asking, because --yes was given.
11962
12209
  `);
11963
12210
  return acting;
11964
12211
  }
11965
12212
  const interactive = deps.interactive ?? isInteractiveRun(deps.env);
11966
12213
  if (!interactive) {
11967
12214
  if (script.noTerminalNote !== "")
11968
- write(script.noTerminalNote);
12215
+ write2(script.noTerminalNote);
11969
12216
  return acting;
11970
12217
  }
11971
12218
  const ask = deps.ask ?? askLine;
11972
- write("\n");
12219
+ write2("\n");
11973
12220
  const decision = readDecision(await ask(question(colour)));
11974
12221
  if (decision === "proceed")
11975
12222
  return acting;
11976
12223
  if (decision === "switch-account") {
11977
- write(script.switchOpening);
12224
+ write2(script.switchOpening);
11978
12225
  const replacement = await switchAccount(serviceUrl, deps);
11979
12226
  const replacementIdentity = await describeAccount(serviceUrl, replacement.credential, deps.fetchImpl);
11980
- write(`[halfcycle] Signed in. This install will ${actClause(act)}.
12227
+ write2(`[halfcycle] Signed in. This install will ${actClause(act)}.
11981
12228
  `);
11982
12229
  if (script.afterSwitch !== "")
11983
- write(script.afterSwitch);
12230
+ write2(script.afterSwitch);
11984
12231
  return actingAs(replacement, replacementIdentity);
11985
12232
  }
11986
12233
  throw new SignInRefused(IDENTITY_DECLINED, script.stopRemedy);
@@ -12091,8 +12338,8 @@ async function joinPinnedEngagement(serviceUrl, engagementId, targetRepo, deps =
12091
12338
 
12092
12339
  // dist/pre-create-checks.js
12093
12340
  import { spawnSync } from "node:child_process";
12094
- import { existsSync as existsSync5, readFileSync as readFileSync7, realpathSync } from "node:fs";
12095
- import { delimiter, dirname as dirname4, join as join7 } from "node:path";
12341
+ import { existsSync as existsSync6, readFileSync as readFileSync7, realpathSync } from "node:fs";
12342
+ import { delimiter, dirname as dirname4, join as join8 } from "node:path";
12096
12343
  var KEPT_FOLDER_ID_LINE = 'Nothing on the Halfcycle service needs undoing, and this folder keeps its project id, so running "npx halfcycle" again picks up where it left off.';
12097
12344
  var PreCreateRefused = class extends Error {
12098
12345
  constructor(message) {
@@ -12122,29 +12369,29 @@ var SYSTEM_FOLDERS = [
12122
12369
  "C:\\Program Files",
12123
12370
  "C:\\Program Files (x86)"
12124
12371
  ];
12125
- function refuseSystemFolder(targetRepo, platform5 = process.platform) {
12126
- const key = (folder) => {
12127
- const real = realOrLiteral(folder);
12128
- return platform5 === "win32" ? real.toLowerCase() : real;
12372
+ function refuseSystemFolder(targetRepo, platform6 = process.platform) {
12373
+ const key = (folder2) => {
12374
+ const real = realOrLiteral(folder2);
12375
+ return platform6 === "win32" ? real.toLowerCase() : real;
12129
12376
  };
12130
12377
  const target = key(targetRepo);
12131
- if (SYSTEM_FOLDERS.some((folder) => key(folder) === target)) {
12378
+ if (SYSTEM_FOLDERS.some((folder2) => key(folder2) === target)) {
12132
12379
  throw new PreCreateRefused(`${targetRepo} is a system folder \u2014 run "npx halfcycle" inside a project folder.`);
12133
12380
  }
12134
12381
  }
12135
12382
  function refuseInsideProject(targetRepo) {
12136
- let folder = realOrLiteral(targetRepo);
12137
- for (let parent = dirname4(folder); parent !== folder; folder = parent, parent = dirname4(parent)) {
12138
- if (existsSync5(join7(parent, BUNDLE_PIN_REL))) {
12383
+ let folder2 = realOrLiteral(targetRepo);
12384
+ for (let parent = dirname4(folder2); parent !== folder2; folder2 = parent, parent = dirname4(parent)) {
12385
+ if (existsSync6(join8(parent, BUNDLE_PIN_REL))) {
12139
12386
  throw new PreCreateRefused(`${targetRepo} is inside the Halfcycle project at ${parent} \u2014 open that folder in your coding agent instead, or run "npx halfcycle" in a folder outside it.`);
12140
12387
  }
12141
12388
  }
12142
12389
  }
12143
- function realOrLiteral(folder) {
12390
+ function realOrLiteral(folder2) {
12144
12391
  try {
12145
- return realpathSync(folder);
12392
+ return realpathSync(folder2);
12146
12393
  } catch {
12147
- return folder;
12394
+ return folder2;
12148
12395
  }
12149
12396
  }
12150
12397
  var GIT_INSTALL_ARGS = ["install", "--id", "Git.Git", "-e", "--source", "winget"];
@@ -12159,13 +12406,13 @@ async function offerGitForWindows() {
12159
12406
  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 "npx halfcycle" again.`);
12160
12407
  }
12161
12408
  function gitBashPath(env) {
12162
- const onPath = (env["PATH"] ?? "").split(delimiter).filter((dir) => dir !== "" && existsSync5(join7(dir, "git.exe"))).map((dir) => join7(dir, "..", "bin", "bash.exe"));
12409
+ const onPath = (env["PATH"] ?? "").split(delimiter).filter((dir) => dir !== "" && existsSync6(join8(dir, "git.exe"))).map((dir) => join8(dir, "..", "bin", "bash.exe"));
12163
12410
  const candidates = [
12164
12411
  env["CLAUDE_CODE_GIT_BASH_PATH"],
12165
12412
  ...onPath,
12166
- env["ProgramFiles"] === void 0 ? void 0 : join7(env["ProgramFiles"], "Git", "bin", "bash.exe")
12413
+ env["ProgramFiles"] === void 0 ? void 0 : join8(env["ProgramFiles"], "Git", "bin", "bash.exe")
12167
12414
  ];
12168
- return candidates.find((file) => file !== void 0 && file !== "" && existsSync5(file));
12415
+ return candidates.find((file) => file !== void 0 && file !== "" && existsSync6(file));
12169
12416
  }
12170
12417
  function hasGitBash(env) {
12171
12418
  return gitBashPath(env) !== void 0;
@@ -12174,7 +12421,7 @@ function runWingetInstall() {
12174
12421
  return spawnSync("winget", [...GIT_INSTALL_ARGS], { stdio: "inherit" }).status === 0;
12175
12422
  }
12176
12423
  function unreadableProjectRefusal(targetRepo) {
12177
- return `${join7(targetRepo, PROJECT_IDENTITY_REL)} could not be read as a Halfcycle project file \u2014 delete the .halfcycle folder in ${targetRepo} and run "npx halfcycle" again to start a new project here.`;
12424
+ return `${join8(targetRepo, PROJECT_IDENTITY_REL)} could not be read as a Halfcycle project file \u2014 delete the .halfcycle folder in ${targetRepo} and run "npx halfcycle" again to start a new project here.`;
12178
12425
  }
12179
12426
  function cannotWriteRefusal(targetRepo) {
12180
12427
  return `cannot write to ${targetRepo} \u2014 run "npx halfcycle" in a folder you can write to.`;
@@ -12194,7 +12441,7 @@ function claimFolderId(targetRepo) {
12194
12441
  function readFolderId(targetRepo) {
12195
12442
  let folderId;
12196
12443
  try {
12197
- const text = readFileSync7(join7(targetRepo, PROJECT_IDENTITY_REL), "utf-8");
12444
+ const text = readFileSync7(join8(targetRepo, PROJECT_IDENTITY_REL), "utf-8");
12198
12445
  folderId = JSON.parse(text)?.projectId;
12199
12446
  } catch {
12200
12447
  return void 0;
@@ -12212,7 +12459,7 @@ function checkSettingsParse(targetRepo) {
12212
12459
 
12213
12460
  // dist/folder-refusals.js
12214
12461
  import { realpathSync as realpathSync2 } from "node:fs";
12215
- import { homedir as homedir3 } from "node:os";
12462
+ import { homedir as homedir4 } from "node:os";
12216
12463
  function folderMissingRefusal(targetRepo) {
12217
12464
  return `halfcycle: no such directory: ${targetRepo}`;
12218
12465
  }
@@ -12232,7 +12479,7 @@ function homeFolderRefusal(targetRepo) {
12232
12479
  function isHomeFolder(dir) {
12233
12480
  try {
12234
12481
  const target = realpathSync2(dir);
12235
- const home = realpathSync2(homedir3());
12482
+ const home = realpathSync2(homedir4());
12236
12483
  return process.platform === "win32" ? target.toLowerCase() === home.toLowerCase() : target === home;
12237
12484
  } catch {
12238
12485
  return false;
@@ -12240,16 +12487,16 @@ function isHomeFolder(dir) {
12240
12487
  }
12241
12488
 
12242
12489
  // dist/preview.js
12243
- import { accessSync, constants, existsSync as existsSync6, readFileSync as readFileSync9, statSync } from "node:fs";
12244
- import { join as join9 } from "node:path";
12490
+ import { accessSync, constants, existsSync as existsSync7, readFileSync as readFileSync9, statSync } from "node:fs";
12491
+ import { join as join10 } from "node:path";
12245
12492
 
12246
12493
  // dist/halfcycle-monorepo.js
12247
12494
  import { readFileSync as readFileSync8 } from "node:fs";
12248
- import { join as join8 } from "node:path";
12495
+ import { join as join9 } from "node:path";
12249
12496
  var MONOREPO_PACKAGE_NAME = "halfcycle-monorepo";
12250
12497
  function isHalfcycleMonorepo(dir) {
12251
12498
  try {
12252
- const pkg = JSON.parse(readFileSync8(join8(dir, "package.json"), "utf-8"));
12499
+ const pkg = JSON.parse(readFileSync8(join9(dir, "package.json"), "utf-8"));
12253
12500
  return pkg.name === MONOREPO_PACKAGE_NAME;
12254
12501
  } catch {
12255
12502
  return false;
@@ -12291,8 +12538,8 @@ function runPreview(targetRepo, options) {
12291
12538
  };
12292
12539
  }
12293
12540
  function claudeMdLine(targetRepo) {
12294
- const path = join9(targetRepo, CLAUDE_MD_REL);
12295
- if (!existsSync6(path))
12541
+ const path = join10(targetRepo, CLAUDE_MD_REL);
12542
+ if (!existsSync7(path))
12296
12543
  return void 0;
12297
12544
  let text;
12298
12545
  try {
@@ -12303,7 +12550,7 @@ function claudeMdLine(targetRepo) {
12303
12550
  return text.includes(AGENTS_IMPORT_LINE) ? WORDS.claudeMdAlreadyPoints : WORDS.claudeMdWillAdd;
12304
12551
  }
12305
12552
  function previewRefusal(targetRepo, options) {
12306
- if (!existsSync6(targetRepo))
12553
+ if (!existsSync7(targetRepo))
12307
12554
  return folderMissingRefusal(targetRepo);
12308
12555
  if (!statSync(targetRepo).isDirectory())
12309
12556
  return folderIsFileRefusal(targetRepo);
@@ -12324,7 +12571,7 @@ function previewRefusal(targetRepo, options) {
12324
12571
  } catch {
12325
12572
  return cannotWriteRefusal(targetRepo);
12326
12573
  }
12327
- if (existsSync6(join9(targetRepo, PROJECT_IDENTITY_REL)) && readFolderId(targetRepo) === void 0) {
12574
+ if (existsSync7(join10(targetRepo, PROJECT_IDENTITY_REL)) && readFolderId(targetRepo) === void 0) {
12328
12575
  return unreadableProjectRefusal(targetRepo);
12329
12576
  }
12330
12577
  try {
@@ -12382,9 +12629,9 @@ function renderPreview(plan, missingGitForWindows, claudeMdWords) {
12382
12629
 
12383
12630
  // dist/doctor.js
12384
12631
  import { spawnSync as spawnSync2 } from "node:child_process";
12385
- import { existsSync as existsSync7, readFileSync as readFileSync10, realpathSync as realpathSync3 } from "node:fs";
12386
- import { homedir as homedir4 } from "node:os";
12387
- import { join as join10 } from "node:path";
12632
+ import { existsSync as existsSync8, readFileSync as readFileSync10, realpathSync as realpathSync3 } from "node:fs";
12633
+ import { homedir as homedir5 } from "node:os";
12634
+ import { join as join11 } from "node:path";
12388
12635
  var GUARD_RUNNER_REL2 = ".claude/hooks/guard-runner.sh";
12389
12636
  var CONNECTION_VERB = "check-connection";
12390
12637
  var WRAPPER_TIMEOUT_MS = 3e4;
@@ -12415,7 +12662,7 @@ function runDoctor(root, options) {
12415
12662
  };
12416
12663
  }
12417
12664
  function setUpLine(root, upToDate) {
12418
- if (!existsSync7(join10(root, BUNDLE_PIN_REL))) {
12665
+ if (!existsSync8(join11(root, BUNDLE_PIN_REL))) {
12419
12666
  return {
12420
12667
  state: "no",
12421
12668
  hooksMissing: false,
@@ -12429,9 +12676,9 @@ function setUpLine(root, upToDate) {
12429
12676
  const missing = [];
12430
12677
  if (!pinNamesProject(root))
12431
12678
  missing.push("Halfcycle's project record");
12432
- if (!existsSync7(join10(root, INSTALL_MANIFEST_REL)))
12679
+ if (!existsSync8(join11(root, INSTALL_MANIFEST_REL)))
12433
12680
  missing.push("the install record");
12434
- if (!existsSync7(join10(root, VENDORED_BIN_REL)))
12681
+ if (!existsSync8(join11(root, VENDORED_BIN_REL)))
12435
12682
  missing.push("Halfcycle's check program");
12436
12683
  if (!mcpNamesServer(root))
12437
12684
  missing.push("Halfcycle's server entry");
@@ -12457,25 +12704,25 @@ function pinNamesProject(root) {
12457
12704
  }
12458
12705
  }
12459
12706
  function mcpNamesServer(root) {
12460
- const parsed = readJson(join10(root, MCP_REGISTRATION_REL));
12461
- if (!isObject(parsed) || !isObject(parsed["mcpServers"]))
12707
+ const parsed = readJson(join11(root, MCP_REGISTRATION_REL));
12708
+ if (!isObject2(parsed) || !isObject2(parsed["mcpServers"]))
12462
12709
  return false;
12463
12710
  const entry = parsed["mcpServers"][MCP_SERVER_KEY];
12464
- return isObject(entry) && typeof entry["url"] === "string";
12711
+ return isObject2(entry) && typeof entry["url"] === "string";
12465
12712
  }
12466
12713
  function settingsRegisterEveryCommand(root) {
12467
- const parsed = readJson(join10(root, SETTINGS_REL));
12468
- if (!isObject(parsed) || !isObject(parsed["hooks"]))
12714
+ const parsed = readJson(join11(root, SETTINGS_REL));
12715
+ if (!isObject2(parsed) || !isObject2(parsed["hooks"]))
12469
12716
  return false;
12470
12717
  const registered = /* @__PURE__ */ new Set();
12471
12718
  for (const groups of Object.values(parsed["hooks"])) {
12472
12719
  if (!Array.isArray(groups))
12473
12720
  continue;
12474
12721
  for (const group of groups) {
12475
- if (!isObject(group) || !Array.isArray(group["hooks"]))
12722
+ if (!isObject2(group) || !Array.isArray(group["hooks"]))
12476
12723
  continue;
12477
12724
  for (const hook of group["hooks"]) {
12478
- if (isObject(hook) && typeof hook["command"] === "string")
12725
+ if (isObject2(hook) && typeof hook["command"] === "string")
12479
12726
  registered.add(hook["command"]);
12480
12727
  }
12481
12728
  }
@@ -12495,8 +12742,8 @@ function startWrapper(root, options) {
12495
12742
  }
12496
12743
  shell = gitBash;
12497
12744
  }
12498
- const wrapper = join10(root, GUARD_RUNNER_REL2);
12499
- if (!existsSync7(wrapper))
12745
+ const wrapper = join11(root, GUARD_RUNNER_REL2);
12746
+ if (!existsSync8(wrapper))
12500
12747
  return failed(repairSentence());
12501
12748
  const run = spawnSync2(shell, [wrapper, CONNECTION_VERB], {
12502
12749
  cwd: root,
@@ -12523,8 +12770,8 @@ function startWrapper(root, options) {
12523
12770
  }
12524
12771
  function settingsDisablingHooks(root) {
12525
12772
  for (const rel of [SETTINGS_REL, LOCAL_SETTINGS_REL]) {
12526
- const parsed = readJson(join10(root, rel));
12527
- if (isObject(parsed) && parsed["disableAllHooks"] === true)
12773
+ const parsed = readJson(join11(root, rel));
12774
+ if (isObject2(parsed) && parsed["disableAllHooks"] === true)
12528
12775
  return rel;
12529
12776
  }
12530
12777
  return void 0;
@@ -12539,7 +12786,7 @@ function parseAnswer(stdout) {
12539
12786
  } catch {
12540
12787
  return void 0;
12541
12788
  }
12542
- if (!isObject(parsed) || typeof parsed["kind"] !== "string" || !KNOWN_KINDS.includes(parsed["kind"]))
12789
+ if (!isObject2(parsed) || typeof parsed["kind"] !== "string" || !KNOWN_KINDS.includes(parsed["kind"]))
12543
12790
  return void 0;
12544
12791
  const sentence = parsed["sentence"];
12545
12792
  if (sentence !== null && typeof sentence !== "string")
@@ -12610,8 +12857,8 @@ var CODEX_EVENT_SNAKE = {
12610
12857
  SessionEnd: "session_end"
12611
12858
  };
12612
12859
  function codexLine(root, options) {
12613
- const hooksPath = join10(root, CODEX_HOOKS_REL2);
12614
- if (!existsSync7(hooksPath))
12860
+ const hooksPath = join11(root, CODEX_HOOKS_REL2);
12861
+ if (!existsSync8(hooksPath))
12615
12862
  return void 0;
12616
12863
  const positions = codexEntryPositions(hooksPath);
12617
12864
  if (positions === void 0) {
@@ -12624,7 +12871,7 @@ function codexLine(root, options) {
12624
12871
  }
12625
12872
  function codexEntryPositions(hooksPath) {
12626
12873
  const parsed = readJson(hooksPath);
12627
- if (!isObject(parsed) || !isObject(parsed["hooks"]))
12874
+ if (!isObject2(parsed) || !isObject2(parsed["hooks"]))
12628
12875
  return void 0;
12629
12876
  const positions = [];
12630
12877
  for (const entry of AGENT_PROFILES.codex.wiring) {
@@ -12634,11 +12881,11 @@ function codexEntryPositions(hooksPath) {
12634
12881
  let found;
12635
12882
  for (let g = 0; g < groups.length && found === void 0; g += 1) {
12636
12883
  const group = groups[g];
12637
- if (!isObject(group) || !Array.isArray(group["hooks"]))
12884
+ if (!isObject2(group) || !Array.isArray(group["hooks"]))
12638
12885
  continue;
12639
12886
  for (let h = 0; h < group["hooks"].length; h += 1) {
12640
12887
  const hook = group["hooks"][h];
12641
- if (isObject(hook) && hook["command"] === entry.command) {
12888
+ if (isObject2(hook) && hook["command"] === entry.command) {
12642
12889
  found = { group: g, hook: h };
12643
12890
  break;
12644
12891
  }
@@ -12652,7 +12899,7 @@ function codexEntryPositions(hooksPath) {
12652
12899
  }
12653
12900
  function codexApprovalStatus(root, positions, options) {
12654
12901
  const codexHome = options.env["CODEX_HOME"];
12655
- const configPath = join10(codexHome !== void 0 && codexHome !== "" ? codexHome : join10(homedir4(), ".codex"), "config.toml");
12902
+ const configPath = join11(codexHome !== void 0 && codexHome !== "" ? codexHome : join11(homedir5(), ".codex"), "config.toml");
12656
12903
  let text;
12657
12904
  try {
12658
12905
  text = readFileSync10(configPath, "utf-8");
@@ -12661,7 +12908,7 @@ function codexApprovalStatus(root, positions, options) {
12661
12908
  }
12662
12909
  let hooksPath;
12663
12910
  try {
12664
- hooksPath = join10(realpathSync3(root), CODEX_HOOKS_REL2);
12911
+ hooksPath = join11(realpathSync3(root), CODEX_HOOKS_REL2);
12665
12912
  } catch {
12666
12913
  return "not-approved";
12667
12914
  }
@@ -12691,7 +12938,7 @@ function naturalList(items) {
12691
12938
  return `${items.slice(0, -1).join(", ")} and ${items[items.length - 1]}`;
12692
12939
  }
12693
12940
  function readJson(path) {
12694
- if (!existsSync7(path))
12941
+ if (!existsSync8(path))
12695
12942
  return void 0;
12696
12943
  try {
12697
12944
  return JSON.parse(readFileSync10(path, "utf-8"));
@@ -12699,7 +12946,7 @@ function readJson(path) {
12699
12946
  return void 0;
12700
12947
  }
12701
12948
  }
12702
- function isObject(value) {
12949
+ function isObject2(value) {
12703
12950
  return typeof value === "object" && value !== null && !Array.isArray(value);
12704
12951
  }
12705
12952
 
@@ -12757,13 +13004,13 @@ async function probeMcpOrigin(mcpUrl, token) {
12757
13004
 
12758
13005
  // dist/claude-version-probe.js
12759
13006
  import { execFileSync as execFileSync3 } from "node:child_process";
12760
- import { existsSync as existsSync8, realpathSync as realpathSync4 } from "node:fs";
12761
- import { delimiter as delimiter2, join as join11 } from "node:path";
13007
+ import { existsSync as existsSync9, realpathSync as realpathSync4 } from "node:fs";
13008
+ import { delimiter as delimiter2, join as join12 } from "node:path";
12762
13009
  function execVersionCommand(command, timeoutMs, deps) {
12763
- const platform5 = deps.platform ?? process.platform;
13010
+ const platform6 = deps.platform ?? process.platform;
12764
13011
  const exec = deps.exec ?? execFileSync3;
12765
13012
  try {
12766
- return platform5 === "win32" ? exec("cmd.exe", ["/d", "/s", "/c", `"${command} --version"`], {
13013
+ return platform6 === "win32" ? exec("cmd.exe", ["/d", "/s", "/c", `"${command} --version"`], {
12767
13014
  encoding: "utf-8",
12768
13015
  timeout: timeoutMs,
12769
13016
  stdio: ["ignore", "pipe", "ignore"],
@@ -12800,8 +13047,8 @@ var INSTALL_METHOD_RULES = [
12800
13047
  ];
12801
13048
  function probeClaudeCodeInstallMethod(deps = {}) {
12802
13049
  const env = deps.env ?? process.env;
12803
- const platform5 = deps.platform ?? process.platform;
12804
- const exists = deps.exists ?? existsSync8;
13050
+ const platform6 = deps.platform ?? process.platform;
13051
+ const exists = deps.exists ?? existsSync9;
12805
13052
  const realpath = deps.realpath ?? ((file) => {
12806
13053
  try {
12807
13054
  return realpathSync4(file);
@@ -12809,11 +13056,11 @@ function probeClaudeCodeInstallMethod(deps = {}) {
12809
13056
  return file;
12810
13057
  }
12811
13058
  });
12812
- const names = platform5 === "win32" ? ["claude.exe", "claude.cmd"] : ["claude"];
13059
+ const names = platform6 === "win32" ? ["claude.exe", "claude.cmd"] : ["claude"];
12813
13060
  const dirs = (env["PATH"] ?? "").split(delimiter2).filter((dir) => dir !== "");
12814
13061
  for (const dir of dirs) {
12815
13062
  for (const name of names) {
12816
- const file = join11(dir, name);
13063
+ const file = join12(dir, name);
12817
13064
  if (!exists(file))
12818
13065
  continue;
12819
13066
  const real = realpath(file);
@@ -12848,8 +13095,8 @@ function codexInstallLine(codex) {
12848
13095
  }
12849
13096
 
12850
13097
  // dist/build-record/write.js
12851
- import { existsSync as existsSync9, mkdirSync as mkdirSync6, rmSync as rmSync4 } from "node:fs";
12852
- import { basename as basename2, join as join12, resolve as resolve3, relative as relative2, isAbsolute as isAbsolute3 } from "node:path";
13098
+ import { existsSync as existsSync10, mkdirSync as mkdirSync6, rmSync as rmSync4 } from "node:fs";
13099
+ import { basename as basename2, join as join13, resolve as resolve3, relative as relative2, isAbsolute as isAbsolute3 } from "node:path";
12853
13100
 
12854
13101
  // dist/build-record/render-v2.js
12855
13102
  var HIDDEN = /[\p{Cc}\p{Zl}\p{Zp}\p{Default_Ignorable_Code_Point}]/u;
@@ -13498,7 +13745,7 @@ function replaceOne(io, tmp, finalPath, text) {
13498
13745
  }
13499
13746
 
13500
13747
  // dist/build-record/write.js
13501
- var BUILD_RECORD_DIR = join12("docs", "build-records");
13748
+ var BUILD_RECORD_DIR = join13("docs", "build-records");
13502
13749
  function assertInsideOutDir(outDir, candidate, identity) {
13503
13750
  const rel = relative2(outDir, resolve3(candidate));
13504
13751
  const isPlainFilename = rel !== "" && rel !== ".." && !isAbsolute3(rel) && rel === basename2(rel);
@@ -13536,8 +13783,8 @@ function writeRecordPair(repoRoot, phase, rawRecord, record2, io = NODE_RECORD_P
13536
13783
  }
13537
13784
  const outDir = resolve3(repoRoot, BUILD_RECORD_DIR);
13538
13785
  const segment = renderPhaseSegment(phase);
13539
- const jsonPath = join12(outDir, `${segment}.json`);
13540
- const mdPath = join12(outDir, `${segment}.md`);
13786
+ const jsonPath = join13(outDir, `${segment}.json`);
13787
+ const mdPath = join13(outDir, `${segment}.md`);
13541
13788
  const jsonTmp = `${jsonPath}.tmp`;
13542
13789
  const mdTmp = `${mdPath}.tmp`;
13543
13790
  for (const candidate of [jsonPath, mdPath, jsonTmp, mdTmp]) {
@@ -13551,7 +13798,7 @@ function writeRecordPair(repoRoot, phase, rawRecord, record2, io = NODE_RECORD_P
13551
13798
  }
13552
13799
  const existingJson = readIfPresent(jsonPath);
13553
13800
  if (existingJson === null) {
13554
- return { kind: "in-the-way", path: existsSync9(jsonPath) ? jsonPath : mdPath };
13801
+ return { kind: "in-the-way", path: existsSync10(jsonPath) ? jsonPath : mdPath };
13555
13802
  }
13556
13803
  if (existingJson === jsonText) {
13557
13804
  replaceOne(io, mdTmp, mdPath, mdText);
@@ -13579,20 +13826,20 @@ import { relative as relative5 } from "node:path";
13579
13826
 
13580
13827
  // dist/open-phase.js
13581
13828
  import { readFileSync as readFileSync17 } from "node:fs";
13582
- import { join as join20 } from "node:path";
13829
+ import { join as join21 } from "node:path";
13583
13830
 
13584
13831
  // dist/close-phase.js
13585
13832
  import { execFile, execFileSync as execFileSync5 } from "node:child_process";
13586
13833
  import { readFileSync as readFileSync16 } from "node:fs";
13587
- import { join as join19 } from "node:path";
13834
+ import { join as join20 } from "node:path";
13588
13835
 
13589
13836
  // dist/plan.js
13590
- import { existsSync as existsSync11, readFileSync as readFileSync15 } from "node:fs";
13591
- import { join as join18 } from "node:path";
13837
+ import { existsSync as existsSync12, readFileSync as readFileSync15 } from "node:fs";
13838
+ import { join as join19 } from "node:path";
13592
13839
 
13593
13840
  // dist/draw/write.js
13594
13841
  import { closeSync as closeSync2, lstatSync as lstatSync2, mkdirSync as mkdirSync7, openSync as openSync2, readFileSync as readFileSync12, readSync, realpathSync as realpathSync5, rmSync as rmSync5 } from "node:fs";
13595
- import { dirname as dirname5, isAbsolute as isAbsolute4, join as join13, relative as relative3, resolve as resolve4, sep } from "node:path";
13842
+ import { dirname as dirname5, isAbsolute as isAbsolute4, join as join14, relative as relative3, resolve as resolve4, sep } from "node:path";
13596
13843
  var DOCS_DIR = "docs";
13597
13844
  var DIAGRAMS_DIR = "diagrams";
13598
13845
  var DIAGRAM_DIR_REL = `${DOCS_DIR}/${DIAGRAMS_DIR}`;
@@ -13648,9 +13895,9 @@ function embedImageLink(base, doc) {
13648
13895
  }
13649
13896
  function diagramPaths(repoRoot, base) {
13650
13897
  const root = resolve4(repoRoot);
13651
- const dir = join13(root, DOCS_DIR, DIAGRAMS_DIR);
13898
+ const dir = join14(root, DOCS_DIR, DIAGRAMS_DIR);
13652
13899
  const names = fileNames(base);
13653
- const file = Object.fromEntries(DIAGRAM_FILES.map((which) => [which, join13(dir, names[which])]));
13900
+ const file = Object.fromEntries(DIAGRAM_FILES.map((which) => [which, join14(dir, names[which])]));
13654
13901
  return { repoRoot: root, dir, file };
13655
13902
  }
13656
13903
  function diagramRel(base, which) {
@@ -13923,10 +14170,10 @@ function isMissingRoute(method, status, body) {
13923
14170
  }
13924
14171
 
13925
14172
  // dist/change.js
13926
- import { join as join16 } from "node:path";
14173
+ import { join as join17 } from "node:path";
13927
14174
 
13928
14175
  // dist/cli-contract.js
13929
- 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"];
14176
+ 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"];
13930
14177
  var CONTRACTS = [
13931
14178
  { verb: "install", required: [], conditional: [] },
13932
14179
  { verb: "check-drift", required: [], conditional: [] },
@@ -13945,6 +14192,17 @@ var CONTRACTS = [
13945
14192
  { verb: "preview", required: [], conditional: [] },
13946
14193
  { verb: "doctor", required: [], conditional: [] },
13947
14194
  { verb: "restore", required: [], conditional: [] },
14195
+ {
14196
+ verb: "request-shapes",
14197
+ required: [],
14198
+ conditional: [],
14199
+ forms: [
14200
+ { form: "on", required: [], conditional: [] },
14201
+ { form: "off", required: [], conditional: [] },
14202
+ { form: "show", required: [], conditional: [] },
14203
+ { form: "clear", required: [], conditional: [] }
14204
+ ]
14205
+ },
13948
14206
  {
13949
14207
  verb: "check-report",
13950
14208
  required: [
@@ -14136,11 +14394,11 @@ function placeholderRefusal(verb, tokens) {
14136
14394
  // dist/system-report.js
14137
14395
  import { execFileSync as execFileSync4 } from "node:child_process";
14138
14396
  import { readFileSync as readFileSync13 } from "node:fs";
14139
- import { join as join14 } from "node:path";
14397
+ import { join as join15 } from "node:path";
14140
14398
  function readDescriptionFile(repoRoot) {
14141
14399
  let text;
14142
14400
  try {
14143
- text = readFileSync13(join14(repoRoot, SYSTEM_DESCRIPTION_REL), "utf-8");
14401
+ text = readFileSync13(join15(repoRoot, SYSTEM_DESCRIPTION_REL), "utf-8");
14144
14402
  } catch (err) {
14145
14403
  const code = err.code;
14146
14404
  return code === "ENOENT" || code === "ENOTDIR" ? { kind: "missing" } : { kind: "unreadable" };
@@ -14364,8 +14622,8 @@ async function runSystem(repoRoot, deps) {
14364
14622
  }
14365
14623
 
14366
14624
  // dist/build-record/write-change.js
14367
- import { existsSync as existsSync10, mkdirSync as mkdirSync8 } from "node:fs";
14368
- import { basename as basename3, isAbsolute as isAbsolute5, join as join15, relative as relative4, resolve as resolve5 } from "node:path";
14625
+ import { existsSync as existsSync11, mkdirSync as mkdirSync8 } from "node:fs";
14626
+ import { basename as basename3, isAbsolute as isAbsolute5, join as join16, relative as relative4, resolve as resolve5 } from "node:path";
14369
14627
 
14370
14628
  // dist/build-record/render-change.js
14371
14629
  function counted2(n, one, many) {
@@ -14510,7 +14768,7 @@ function renderChangeRecordMarkdown(record2) {
14510
14768
  }
14511
14769
 
14512
14770
  // dist/build-record/write-change.js
14513
- var CHANGE_RECORD_DIR = join15("docs", "build-records", "changes");
14771
+ var CHANGE_RECORD_DIR = join16("docs", "build-records", "changes");
14514
14772
  var InvalidChangeIdError = class extends Error {
14515
14773
  id;
14516
14774
  constructor(id) {
@@ -14530,8 +14788,8 @@ function writeChangeRecordPair(repoRoot, sortingId, rawRecord, record2, io = NOD
14530
14788
  if (!checked.success)
14531
14789
  throw new InvalidChangeIdError(sortingId);
14532
14790
  const outDir = resolve5(repoRoot, CHANGE_RECORD_DIR);
14533
- const jsonPath = join15(outDir, `${checked.data}.json`);
14534
- const mdPath = join15(outDir, `${checked.data}.md`);
14791
+ const jsonPath = join16(outDir, `${checked.data}.json`);
14792
+ const mdPath = join16(outDir, `${checked.data}.md`);
14535
14793
  const mdTmp = `${mdPath}.tmp`;
14536
14794
  for (const candidate of [jsonPath, mdPath, mdTmp]) {
14537
14795
  assertInsideChangeDir(outDir, candidate, sortingId);
@@ -14544,7 +14802,7 @@ function writeChangeRecordPair(repoRoot, sortingId, rawRecord, record2, io = NOD
14544
14802
  }
14545
14803
  const existingJson = readIfPresent(jsonPath);
14546
14804
  if (existingJson === null) {
14547
- return { kind: "in-the-way", path: existsSync10(jsonPath) ? jsonPath : mdPath };
14805
+ return { kind: "in-the-way", path: existsSync11(jsonPath) ? jsonPath : mdPath };
14548
14806
  }
14549
14807
  if (existingJson !== jsonText)
14550
14808
  return { kind: "in-the-way", path: jsonPath };
@@ -14807,7 +15065,7 @@ function writePairLines(deps, repoRoot, sortingId, found) {
14807
15065
  };
14808
15066
  }
14809
15067
  return {
14810
- stdout: `[halfcycle] Its record is in ${join16(CHANGE_RECORD_DIR, `${sortingId}.md`)}.
15068
+ stdout: `[halfcycle] Its record is in ${join17(CHANGE_RECORD_DIR, `${sortingId}.md`)}.
14811
15069
  `,
14812
15070
  stderr: "",
14813
15071
  ok: true
@@ -15048,12 +15306,12 @@ function runChange(repoRoot, args2, deps) {
15048
15306
  // dist/shown-record.js
15049
15307
  import { chmodSync as chmodSync3, mkdirSync as mkdirSync9, readFileSync as readFileSync14, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync6 } from "node:fs";
15050
15308
  import { platform as platform4 } from "node:os";
15051
- import { join as join17 } from "node:path";
15309
+ import { join as join18 } from "node:path";
15052
15310
  function updateShownRecord(engagementId, update, home) {
15053
15311
  let temp = null;
15054
15312
  try {
15055
15313
  const dir = engagementStateDir(engagementId, home);
15056
- const path = join17(dir, SHOWN_PLANS_FILENAME);
15314
+ const path = join18(dir, SHOWN_PLANS_FILENAME);
15057
15315
  let current;
15058
15316
  try {
15059
15317
  current = parseShownRecord(readFileSync14(path, "utf-8"));
@@ -15063,7 +15321,7 @@ function updateShownRecord(engagementId, update, home) {
15063
15321
  const next = update(current);
15064
15322
  const body = serialiseShownRecord(next);
15065
15323
  mkdirSync9(dir, { recursive: true, mode: 448 });
15066
- temp = join17(dir, `.${SHOWN_PLANS_FILENAME}.${process.pid}.tmp`);
15324
+ temp = join18(dir, `.${SHOWN_PLANS_FILENAME}.${process.pid}.tmp`);
15067
15325
  writeFileSync6(temp, body, { mode: 384 });
15068
15326
  if (platform4() !== "win32")
15069
15327
  chmodSync3(temp, 384);
@@ -15140,7 +15398,7 @@ function readAndCheckPlan(repoRoot, phase, description, verb) {
15140
15398
  const fileRel = planFileRel(phase);
15141
15399
  let text;
15142
15400
  try {
15143
- text = readFileSync15(join18(repoRoot, fileRel), "utf-8");
15401
+ text = readFileSync15(join19(repoRoot, fileRel), "utf-8");
15144
15402
  } catch (err) {
15145
15403
  const code = err.code;
15146
15404
  if (code === "ENOENT" || code === "ENOTDIR") {
@@ -15296,7 +15554,7 @@ function removalOwedLines(verb, parts) {
15296
15554
  function redrawLine(repoRoot, phase) {
15297
15555
  let exists;
15298
15556
  try {
15299
- exists = existsSync11(join18(repoRoot, DIAGRAM_DIR_REL, embedImageName(phaseDiagramBase(phase))));
15557
+ exists = existsSync12(join19(repoRoot, DIAGRAM_DIR_REL, embedImageName(phaseDiagramBase(phase))));
15300
15558
  } catch {
15301
15559
  return null;
15302
15560
  }
@@ -15917,7 +16175,7 @@ function readTimeLine(stdout) {
15917
16175
  }
15918
16176
  }
15919
16177
  function runTimeChild(binary, credential, repoRoot) {
15920
- return runRunnerChild(binary, ["time"], credential, repoRoot, readTimeLine, TIME_UNREACHABLE);
16178
+ return runRunnerChild(binary, ["time", "--final"], credential, repoRoot, readTimeLine, TIME_UNREACHABLE);
15921
16179
  }
15922
16180
  function runProveQueueChild(binary, credential, repoRoot) {
15923
16181
  return runRunnerChild(binary, ["prove", "--send-queued"], credential, repoRoot, () => void 0, void 0);
@@ -16243,7 +16501,7 @@ var PHASE_MODELS_FILENAME = "phase-models.json";
16243
16501
  var PACING_FOLDER = "pacing";
16244
16502
  function pacingFile(engagementId, name, home) {
16245
16503
  try {
16246
- return join19(engagementStateDir(engagementId, home), PACING_FOLDER, name);
16504
+ return join20(engagementStateDir(engagementId, home), PACING_FOLDER, name);
16247
16505
  } catch {
16248
16506
  return null;
16249
16507
  }
@@ -16397,7 +16655,7 @@ function readRepoCredential(repoRoot, env = process.env, home) {
16397
16655
  if (pinned !== null)
16398
16656
  assertEngagementId(pinned);
16399
16657
  const fromStore = pinned === null ? null : readEngagementEnv(pinned, home);
16400
- const looked = pinned === null ? `${join20(repoRoot, ".halfcycle", "bundle.json")} (which names no engagement)` : engagementEnvPath(pinned, home);
16658
+ const looked = pinned === null ? `${join21(repoRoot, ".halfcycle", "bundle.json")} (which names no engagement)` : engagementEnvPath(pinned, home);
16401
16659
  const values = {};
16402
16660
  for (const key of OPEN_PHASE_ENV_KEYS) {
16403
16661
  const value = (env[key] ?? fromStore?.[key] ?? "").trim();
@@ -16414,7 +16672,7 @@ function readRepoCredential(repoRoot, env = process.env, home) {
16414
16672
  }
16415
16673
  function readPinnedEngagementId(repoRoot) {
16416
16674
  try {
16417
- const pin = JSON.parse(readFileSync17(join20(repoRoot, ".halfcycle", "bundle.json"), "utf-8"));
16675
+ const pin = JSON.parse(readFileSync17(join21(repoRoot, ".halfcycle", "bundle.json"), "utf-8"));
16418
16676
  const id = pin[PIN_ENGAGEMENT_ID_FIELD];
16419
16677
  return typeof id === "string" && id !== "" ? id : null;
16420
16678
  } catch {
@@ -16895,7 +17153,7 @@ async function writeDrawOutcome(repoRoot, deps, outcome, target, warnings, canno
16895
17153
 
16896
17154
  // dist/check-tasks.js
16897
17155
  import { readFileSync as readFileSync18 } from "node:fs";
16898
- import { join as join21, resolve as resolve6 } from "node:path";
17156
+ import { join as join22, resolve as resolve6 } from "node:path";
16899
17157
  var CHECK_TASKS_USAGE = `${HALFCYCLE_COMMAND} check-tasks <phase> [--with-task-list] [--repo <root>]`;
16900
17158
  function parseCheckTasksArgs(argv) {
16901
17159
  const positionals = [];
@@ -16993,7 +17251,7 @@ function readPhasePlanParts(repoRoot, phase, description) {
16993
17251
  const file = planFileRel(phase);
16994
17252
  let text;
16995
17253
  try {
16996
- text = readFileSync18(join21(repoRoot, file), "utf-8");
17254
+ text = readFileSync18(join22(repoRoot, file), "utf-8");
16997
17255
  } catch (err) {
16998
17256
  const code = err.code;
16999
17257
  if (code === "ENOENT" || code === "ENOTDIR")
@@ -17026,7 +17284,7 @@ function readPlanSpecFiles(repoRoot, plan) {
17026
17284
  const rel = `${SPEC_FOLDER}/${slug}.md`;
17027
17285
  let text;
17028
17286
  try {
17029
- text = readFileSync18(join21(repoRoot, rel), "utf-8");
17287
+ text = readFileSync18(join22(repoRoot, rel), "utf-8");
17030
17288
  } catch (err) {
17031
17289
  const code = err.code;
17032
17290
  if (code === "ENOENT" || code === "ENOTDIR") {
@@ -17417,8 +17675,8 @@ function runProveChild(binary, credential, repoRoot, words, deps = {}) {
17417
17675
 
17418
17676
  // dist/uninstall.js
17419
17677
  import { lstatSync as lstatSync3, readFileSync as readFileSync20, readdirSync, rmSync as rmSync6, rmdirSync, writeFileSync as writeFileSync7 } from "node:fs";
17420
- import { homedir as homedir5 } from "node:os";
17421
- import { dirname as dirname6, join as join22, relative as relative6, sep as sep2 } from "node:path";
17678
+ import { homedir as homedir6 } from "node:os";
17679
+ import { dirname as dirname6, join as join23, relative as relative6, sep as sep2 } from "node:path";
17422
17680
  var REMOVE_CREDENTIAL_FLAG = "--remove-credential";
17423
17681
  function parseUninstallArgs(args2) {
17424
17682
  let removeCredential = false;
@@ -17453,7 +17711,7 @@ function readMergedJson(targetRepo, rule) {
17453
17711
  const { rel } = rule;
17454
17712
  if (symlinkOnPath(targetRepo, rel))
17455
17713
  return { state: "symlink" };
17456
- const path = join22(targetRepo, rel);
17714
+ const path = join23(targetRepo, rel);
17457
17715
  if (!lexists(path))
17458
17716
  return { state: "absent" };
17459
17717
  let text;
@@ -17476,8 +17734,8 @@ function readMergedJson(targetRepo, rule) {
17476
17734
  }
17477
17735
  function validate(options, rules) {
17478
17736
  const { targetRepo, removeCredential, home } = options;
17479
- const manifestPath = join22(targetRepo, INSTALL_MANIFEST_REL);
17480
- const pinPath = join22(targetRepo, BUNDLE_PIN_REL);
17737
+ const manifestPath = join23(targetRepo, INSTALL_MANIFEST_REL);
17738
+ const pinPath = join23(targetRepo, BUNDLE_PIN_REL);
17481
17739
  if (symlinkOnPath(targetRepo, INSTALL_MANIFEST_REL) || symlinkOnPath(targetRepo, BUNDLE_PIN_REL)) {
17482
17740
  throw new Refusal(`.halfcycle, or a file Halfcycle keeps in it, is a symbolic link here, so nothing was followed and nothing was removed.`);
17483
17741
  }
@@ -17518,7 +17776,7 @@ function validate(options, rules) {
17518
17776
  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.`);
17519
17777
  }
17520
17778
  if (engagementId !== void 0) {
17521
- const expectedParent = join22(home ?? homedir5(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME);
17779
+ const expectedParent = join23(home ?? homedir6(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME);
17522
17780
  if (dirname6(engagementStateDir(engagementId, home)) !== expectedParent) {
17523
17781
  throw new Refusal(`the project id in ${BUNDLE_PIN_REL} does not name a folder under ~/.halfcycle/engagements. Nothing was removed.`);
17524
17782
  }
@@ -17544,7 +17802,7 @@ function mergedFileRules(writeSet) {
17544
17802
  function writeBack(targetRepo, rel, current, next, lists, name) {
17545
17803
  if (next === current)
17546
17804
  return;
17547
- const path = join22(targetRepo, rel);
17805
+ const path = join23(targetRepo, rel);
17548
17806
  try {
17549
17807
  if (next === null) {
17550
17808
  rmSync6(path);
@@ -17561,7 +17819,7 @@ function statusLineClaimedFromTree(targetRepo, writeSet) {
17561
17819
  try {
17562
17820
  if (symlinkOnPath(targetRepo, SETTINGS_REL))
17563
17821
  return {};
17564
- const value = JSON.parse(readFileSync20(join22(targetRepo, SETTINGS_REL), "utf-8"));
17822
+ const value = JSON.parse(readFileSync20(join23(targetRepo, SETTINGS_REL), "utf-8"));
17565
17823
  if (!isPlainObject3(value) || !("statusLine" in value))
17566
17824
  return {};
17567
17825
  const hash = canonicalSha256(value["statusLine"]);
@@ -17575,7 +17833,7 @@ function noRecord(targetRepo, writeSet) {
17575
17833
  const claim = (rel, header, lines) => {
17576
17834
  let text;
17577
17835
  try {
17578
- text = symlinkOnPath(targetRepo, rel) ? "" : readFileSync20(join22(targetRepo, rel), "utf-8");
17836
+ text = symlinkOnPath(targetRepo, rel) ? "" : readFileSync20(join23(targetRepo, rel), "utf-8");
17579
17837
  } catch {
17580
17838
  text = "";
17581
17839
  }
@@ -17816,7 +18074,7 @@ function lastOccurrence(text, block) {
17816
18074
  }
17817
18075
  function coveredPathExists(targetRepo, line) {
17818
18076
  const path = line.trim().replace(/^\//, "").replace(/\/$/, "");
17819
- return path !== "" && lexists(join22(targetRepo, path));
18077
+ return path !== "" && lexists(join23(targetRepo, path));
17820
18078
  }
17821
18079
  function selfBuildMergedFileRules() {
17822
18080
  return {
@@ -17830,7 +18088,7 @@ function removeSelfBuildOwnedFile(targetRepo, rel, lists, name) {
17830
18088
  name(lists.leftAlone, `${rel} (a symbolic link, or inside one \u2014 not followed)`, rel);
17831
18089
  return;
17832
18090
  }
17833
- const abs = join22(targetRepo, rel);
18091
+ const abs = join23(targetRepo, rel);
17834
18092
  let isFile;
17835
18093
  try {
17836
18094
  isFile = lstatSync3(abs).isFile();
@@ -17854,7 +18112,7 @@ function removeSelfBuildCrewRoster(targetRepo, lists, name) {
17854
18112
  name(lists.leftAlone, `${rel} (a symbolic link, or inside one \u2014 not followed)`, rel);
17855
18113
  return;
17856
18114
  }
17857
- const abs = join22(targetRepo, rel);
18115
+ const abs = join23(targetRepo, rel);
17858
18116
  let bytes;
17859
18117
  try {
17860
18118
  bytes = readFileSync20(abs);
@@ -17881,7 +18139,7 @@ function removeSelfBuildCrewProfile(targetRepo, rel, text, lists, name) {
17881
18139
  name(lists.leftAlone, `${rel} (a symbolic link, or inside one \u2014 not followed)`, rel);
17882
18140
  return;
17883
18141
  }
17884
- const abs = join22(targetRepo, rel);
18142
+ const abs = join23(targetRepo, rel);
17885
18143
  let bytes;
17886
18144
  try {
17887
18145
  bytes = readFileSync20(abs);
@@ -17906,7 +18164,7 @@ function removeSelfBuildCrewProfile(targetRepo, rel, text, lists, name) {
17906
18164
  function removeSelfBuildEmptyDir(targetRepo, dir, lists, name) {
17907
18165
  if (symlinkOnPath(targetRepo, dir))
17908
18166
  return;
17909
- const abs = join22(targetRepo, dir);
18167
+ const abs = join23(targetRepo, dir);
17910
18168
  try {
17911
18169
  if (!lstatSync3(abs).isDirectory() || readdirSync(abs).length > 0)
17912
18170
  return;
@@ -17940,7 +18198,7 @@ function uninstallSelfBuild(options) {
17940
18198
  throw err;
17941
18199
  }
17942
18200
  const { targetRepo } = options;
17943
- const pinExisted = lexists(join22(targetRepo, BUNDLE_PIN_REL));
18201
+ const pinExisted = lexists(join23(targetRepo, BUNDLE_PIN_REL));
17944
18202
  const name = (list2, text, _path) => {
17945
18203
  list2.push(text);
17946
18204
  };
@@ -18056,7 +18314,7 @@ function uninstall(options) {
18056
18314
  const gitattributes = manifest.gitattributes ?? { created: false, appended: [] };
18057
18315
  trimAppendedFile(targetRepo, gitattributes, deleteWhenEmpty(gitattributes.created), gitattributesRules(writeSet), result, name);
18058
18316
  const foundNothing = result.removed.length + result.restored.length + result.kept.length + result.failed.length === 0 && result.keptIgnoreLines.length === 0;
18059
- if (checked.manifest === null && !lexists(join22(targetRepo, BUNDLE_PIN_REL)) && foundNothing) {
18317
+ if (checked.manifest === null && !lexists(join23(targetRepo, BUNDLE_PIN_REL)) && foundNothing) {
18060
18318
  return { outcome: "not-installed", exitCode: 0, ...result, credential: nothingToSay };
18061
18319
  }
18062
18320
  const credential = handleCredential(options, checked);
@@ -18064,7 +18322,7 @@ function uninstall(options) {
18064
18322
  if (result.failed.length === 0 && !credentialFailed) {
18065
18323
  removeInstalledFile(targetRepo, BUNDLE_PIN_REL, recorded.get(BUNDLE_PIN_REL), writeSet, result, name);
18066
18324
  try {
18067
- rmSync6(join22(targetRepo, INSTALL_MANIFEST_REL), { force: true });
18325
+ rmSync6(join23(targetRepo, INSTALL_MANIFEST_REL), { force: true });
18068
18326
  named2.add(INSTALL_MANIFEST_REL);
18069
18327
  } catch {
18070
18328
  name(result.failed, INSTALL_MANIFEST_REL, INSTALL_MANIFEST_REL);
@@ -18099,13 +18357,13 @@ function applyMergedJson(targetRepo, rule, file, deleteWhenEmpty, lists, name, e
18099
18357
  return;
18100
18358
  const empty = Object.keys(value).length === 0 && deleteWhenEmpty;
18101
18359
  writeBack(targetRepo, rel, file.text, empty ? null : inLineEndingOf(file.text, serializeJson(value)), lists, name);
18102
- if (adopted && lexists(join22(targetRepo, rel))) {
18360
+ if (adopted && lexists(join23(targetRepo, rel))) {
18103
18361
  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);
18104
18362
  }
18105
18363
  }
18106
18364
  function removeCodexConfigBlock(targetRepo, record2, deleteWhenEmpty, lists, name) {
18107
18365
  const rel = CODEX_CONFIG_REL2;
18108
- const path = join22(targetRepo, rel);
18366
+ const path = join23(targetRepo, rel);
18109
18367
  if (symlinkOnPath(targetRepo, rel)) {
18110
18368
  if (lexists(path))
18111
18369
  name(lists.leftAlone, `${rel} (a symbolic link, or inside one \u2014 not followed)`, rel);
@@ -18150,7 +18408,7 @@ function removeInstalledFile(targetRepo, path, entry, writeSet, lists, name) {
18150
18408
  name(lists.leftAlone, `${path} (a symbolic link, or inside one \u2014 not followed)`, path);
18151
18409
  return;
18152
18410
  }
18153
- const abs = join22(targetRepo, path);
18411
+ const abs = join23(targetRepo, path);
18154
18412
  let isFile;
18155
18413
  try {
18156
18414
  isFile = lstatSync3(abs).isFile();
@@ -18184,7 +18442,7 @@ function removeInstalledFile(targetRepo, path, entry, writeSet, lists, name) {
18184
18442
  function removeEmptyDir(targetRepo, dir, writeSet, lists, name) {
18185
18443
  if (!writeSet.dirs.has(dir) || symlinkOnPath(targetRepo, dir))
18186
18444
  return;
18187
- const abs = join22(targetRepo, dir);
18445
+ const abs = join23(targetRepo, dir);
18188
18446
  try {
18189
18447
  if (!lstatSync3(abs).isDirectory() || readdirSync(abs).length > 0)
18190
18448
  return;
@@ -18203,7 +18461,7 @@ function trimAppendedFile(targetRepo, rec, deleteWhenEmpty, rules, lists, name)
18203
18461
  name(lists.leftAlone, `${rel} (a symbolic link \u2014 not followed)`, rel);
18204
18462
  return [];
18205
18463
  }
18206
- const path = join22(targetRepo, rel);
18464
+ const path = join23(targetRepo, rel);
18207
18465
  let before;
18208
18466
  try {
18209
18467
  before = readFileSync20(path, "utf-8");
@@ -18250,16 +18508,16 @@ function filesUnder(root, rel) {
18250
18508
  const walk = (dirRel) => {
18251
18509
  let names;
18252
18510
  try {
18253
- if (!lstatSync3(join22(root, dirRel)).isDirectory())
18511
+ if (!lstatSync3(join23(root, dirRel)).isDirectory())
18254
18512
  return;
18255
- names = readdirSync(join22(root, dirRel));
18513
+ names = readdirSync(join23(root, dirRel));
18256
18514
  } catch {
18257
18515
  return;
18258
18516
  }
18259
18517
  for (const entry of names.sort()) {
18260
18518
  const childRel = `${dirRel}/${entry}`;
18261
18519
  try {
18262
- if (lstatSync3(join22(root, childRel)).isDirectory())
18520
+ if (lstatSync3(join23(root, childRel)).isDirectory())
18263
18521
  walk(childRel);
18264
18522
  else
18265
18523
  out.push(childRel);
@@ -18272,7 +18530,7 @@ function filesUnder(root, rel) {
18272
18530
  return out;
18273
18531
  }
18274
18532
  function shownPath(path, home) {
18275
- const base = home ?? homedir5();
18533
+ const base = home ?? homedir6();
18276
18534
  const rel = relative6(base, path);
18277
18535
  const under = rel !== "" && !rel.startsWith("..") && !rel.startsWith(sep2);
18278
18536
  return `${under ? `~/${rel.split(sep2).join("/")}` : path}/`;
@@ -18332,7 +18590,7 @@ function renderUninstallReport(result, home) {
18332
18590
 
18333
18591
  // dist/banner-facts.js
18334
18592
  import { readFileSync as readFileSync21 } from "node:fs";
18335
- import { basename as basename4, join as join23, resolve as resolve7 } from "node:path";
18593
+ import { basename as basename4, join as join24, resolve as resolve7 } from "node:path";
18336
18594
  var BRAND_URL = "halfcycle.ai";
18337
18595
  var TAGLINE = [
18338
18596
  "Fewer cycles.",
@@ -18345,7 +18603,7 @@ var TAGLINE_NARROW = [
18345
18603
  ];
18346
18604
  function bundleVersion() {
18347
18605
  try {
18348
- const pkg = JSON.parse(readFileSync21(join23(BUNDLE_ROOT, "package.json"), "utf-8"));
18606
+ const pkg = JSON.parse(readFileSync21(join24(BUNDLE_ROOT, "package.json"), "utf-8"));
18349
18607
  return typeof pkg.version === "string" ? pkg.version : void 0;
18350
18608
  } catch {
18351
18609
  return void 0;
@@ -18441,7 +18699,7 @@ function safeBasename(dir) {
18441
18699
  // dist/pace.js
18442
18700
  import { spawn as spawn3 } from "node:child_process";
18443
18701
  import { readFileSync as readFileSync22 } from "node:fs";
18444
- import { join as join24 } from "node:path";
18702
+ import { join as join25 } from "node:path";
18445
18703
  var PACE_USAGE = `${HALFCYCLE_COMMAND} pace [--agent claude-code|codex] [--start <phase> [--agents <n>]] [--repo <root>]`;
18446
18704
  function parsePaceArgs(argv) {
18447
18705
  let repoRoot;
@@ -18555,7 +18813,7 @@ function notPacedLine(reason) {
18555
18813
  }
18556
18814
  function readPromptScript(repoRoot) {
18557
18815
  try {
18558
- return readFileSync22(join24(repoRoot, ".claude", "hooks", "user-prompt-scope.sh"), "utf-8");
18816
+ return readFileSync22(join25(repoRoot, ".claude", "hooks", "user-prompt-scope.sh"), "utf-8");
18559
18817
  } catch {
18560
18818
  return null;
18561
18819
  }
@@ -18593,14 +18851,17 @@ async function runPace(repoRoot, start, deps, agent = null) {
18593
18851
  return { stdout: `${paceStartLinesOf(result.suggestion, behind).join("\n")}
18594
18852
  `, stderr: "", exitCode: 0 };
18595
18853
  }
18596
- const lines = [paceAllowedNowOf(result.allowedNow)];
18854
+ const share = result.keepFreePercent ?? KEEP_FREE_DEFAULT;
18855
+ const lines = [paceBuildUpToOf(result.number), paceAllowedNowOf(result.allowedNow), share === 0 ? PACE_P5 : paceP3Of(share)];
18856
+ if (result.limit !== "reached" && result.usage !== void 0) {
18857
+ lines.push(limitUsedOf(result.usage.used, result.usage.limit));
18858
+ }
18597
18859
  if (result.limit === "reached" && result.limitHeld !== void 0) {
18598
18860
  const held = result.limitHeld;
18599
18861
  lines.push(limitReachedLineOf(held.used, held.limit, held.working, held.raiseBy));
18600
18862
  }
18601
18863
  if (result.windowKind !== void 0 && result.until !== void 0) {
18602
18864
  const until = new Date(result.until);
18603
- const share = result.keepFreePercent ?? KEEP_FREE_DEFAULT;
18604
18865
  if (result.pace === "slowing")
18605
18866
  lines.push(paceS1Of(result.windowKind, result.allowedNow, until, deps.now(), share));
18606
18867
  else if (result.pace === "holding")
@@ -18611,10 +18872,9 @@ async function runPace(repoRoot, start, deps, agent = null) {
18611
18872
  }
18612
18873
 
18613
18874
  // dist/route.js
18614
- import { mkdirSync as mkdirSync10, readFileSync as readFileSync23, renameSync as renameSync4, writeFileSync as writeFileSync8 } from "node:fs";
18615
- import { dirname as dirname7, join as join25 } from "node:path";
18875
+ import { readFileSync as readFileSync23 } from "node:fs";
18876
+ import { join as join26 } from "node:path";
18616
18877
  var ROUTE_USAGE = `${HALFCYCLE_COMMAND} route <phase> <task> [--agent claude-code|codex] [--repo <root>]`;
18617
- var ROUTING_FILENAME = "routing.json";
18618
18878
  var ROUTE_WORK_READ_DEADLINE_MS = 5e3;
18619
18879
  function parseRouteArgs(argv) {
18620
18880
  const positionals = [];
@@ -18656,7 +18916,7 @@ function parseRouteArgs(argv) {
18656
18916
  return { ok: true, phase: positionals[0], task: positionals[1], agent: agent ?? "claude-code", repoRoot };
18657
18917
  }
18658
18918
  function routingPath(engagementId, home) {
18659
- return join25(engagementStateDir(engagementId, home), ROUTING_FILENAME);
18919
+ return join26(engagementStateDir(engagementId, home), ROUTING_FILENAME);
18660
18920
  }
18661
18921
  function readCheapStarted(path, phase) {
18662
18922
  try {
@@ -18668,19 +18928,11 @@ function readCheapStarted(path, phase) {
18668
18928
  return [];
18669
18929
  }
18670
18930
  }
18671
- function recordCheapStarted(path, phase, task) {
18672
- const started = readCheapStarted(path, phase);
18673
- const record2 = { phase, cheapStarted: started.includes(task) ? started : [...started, task] };
18674
- mkdirSync10(dirname7(path), { recursive: true, mode: 448 });
18675
- const tmp = `${path}.${String(process.pid)}.tmp`;
18676
- writeFileSync8(tmp, `${JSON.stringify(record2)}
18677
- `, { mode: 384 });
18678
- renameSync4(tmp, path);
18679
- }
18680
- function attemptsOf(read, task) {
18931
+ function workOf(read, task) {
18681
18932
  if (read.kind !== "work")
18682
18933
  return null;
18683
- return read.work.tasks.find((t) => t.task === task)?.attempts ?? [];
18934
+ const entry = read.work.tasks.find((t) => t.task === task);
18935
+ return { attempts: entry?.attempts ?? [], passed: entry?.standing === "passed" };
18684
18936
  }
18685
18937
  function lastTierOf(attempts) {
18686
18938
  return [...attempts].sort((a, b) => b.attempt - a.attempt)[0].builderTier;
@@ -18734,9 +18986,11 @@ async function runRoute(repoRoot, phase, taskId, agent, deps) {
18734
18986
  } catch {
18735
18987
  return answer(routeStartLineOf(holder, taskId, member.tier));
18736
18988
  }
18737
- const attempts = attemptsOf(await readWorkInTime(deps, credential, phase), taskId);
18738
- if (attempts !== null && attempts.length > 0) {
18739
- return answer(routeStartLineOf(holder, taskId, tierAboveOf(lastTierOf(attempts))));
18989
+ const work = workOf(await readWorkInTime(deps, credential, phase), taskId);
18990
+ if (work !== null && work.attempts.length > 0) {
18991
+ if (work.passed)
18992
+ return answer(routePassedLineOf(taskId, lastTierOf(work.attempts)));
18993
+ return answer(routeStartLineOf(holder, taskId, tierAboveOf(lastTierOf(work.attempts))));
18740
18994
  }
18741
18995
  if (agent === "codex" && !hasCheaperRole(member))
18742
18996
  return answer(routeStartLineOf(holder, taskId, member.tier));
@@ -18748,19 +19002,14 @@ async function runRoute(repoRoot, phase, taskId, agent, deps) {
18748
19002
  }
18749
19003
  if (readCheapStarted(path, phase).includes(taskId))
18750
19004
  return answer(routeStartLineOf(holder, taskId, member.tier));
18751
- try {
18752
- recordCheapStarted(path, phase, taskId);
18753
- } catch {
18754
- return answer(routeStartLineOf(holder, taskId, member.tier));
18755
- }
18756
19005
  return answer(agent === "codex" ? routeCheaperCodexLineOf(holder, taskId) : routeCheaperClaudeLineOf(holder, taskId, AGENT_PROFILES["claude-code"].tierModels.fast.model));
18757
19006
  }
18758
19007
 
18759
19008
  // dist/restore.js
18760
19009
  import { spawnSync as spawnSync3 } from "node:child_process";
18761
- import { closeSync as closeSync3, fchmodSync, lstatSync as lstatSync4, mkdirSync as mkdirSync11, openSync as openSync3, readdirSync as readdirSync2, readFileSync as readFileSync24, readlinkSync, realpathSync as realpathSync6, rmSync as rmSync7, symlinkSync, writeSync as writeSync2 } from "node:fs";
18762
- import { homedir as homedir6 } from "node:os";
18763
- import { join as join26, resolve as resolve8 } from "node:path";
19010
+ import { closeSync as closeSync3, fchmodSync, lstatSync as lstatSync4, mkdirSync as mkdirSync10, openSync as openSync3, readdirSync as readdirSync2, readFileSync as readFileSync24, readlinkSync, realpathSync as realpathSync6, rmSync as rmSync7, symlinkSync, writeSync as writeSync2 } from "node:fs";
19011
+ import { homedir as homedir7 } from "node:os";
19012
+ import { join as join27, resolve as resolve8 } from "node:path";
18764
19013
  var RESTORE_USAGE = `${HALFCYCLE_COMMAND} restore [<id>]`;
18765
19014
  function parseRestoreArgs(rest) {
18766
19015
  if (rest.length > 1)
@@ -18782,28 +19031,28 @@ function runRestore(cwd, id, options = {}) {
18782
19031
  if (engagementId === null) {
18783
19032
  return refused2(`Halfcycle is not set up in this project, so it has saved nothing here. To set it up, run: ${HALFCYCLE_COMMAND}`);
18784
19033
  }
18785
- const folder = join26(options.home ?? homedir6(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, SAVED_DIR_NAME);
19034
+ const folder2 = join27(options.home ?? homedir7(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, SAVED_DIR_NAME);
18786
19035
  const refs = savedRefs(root);
18787
19036
  if (refs === null)
18788
19037
  return refused2("Halfcycle could not read the saved copies in this project, so nothing was put back.");
18789
- pruneAtRestore(root, folder, refs, now());
19038
+ pruneAtRestore(root, folder2, refs, now());
18790
19039
  for (const [saveId, madeAt] of [...refs]) {
18791
19040
  const answer = savedPruneAnswer({ refMadeAt: madeAt, folderChangedAt: 0, folderRepository: null }, { repository: null, now: now() });
18792
19041
  if (answer !== "keep")
18793
19042
  refs.delete(saveId);
18794
19043
  }
18795
19044
  if (id === void 0)
18796
- return list(root, folder, refs);
18797
- return restoreOne(root, folder, refs, id);
19045
+ return list(root, folder2, refs);
19046
+ return restoreOne(root, folder2, refs, id);
18798
19047
  }
18799
19048
  function refused2(line) {
18800
19049
  return { stdout: "", stderr: `${line}
18801
19050
  `, exitCode: 1 };
18802
19051
  }
18803
- function list(root, folder, refs) {
19052
+ function list(root, folder2, refs) {
18804
19053
  const rows = [];
18805
19054
  for (const [id, at] of refs) {
18806
- const saved = readSaved(folder, id);
19055
+ const saved = readSaved(folder2, id);
18807
19056
  if (saved !== null)
18808
19057
  rows.push({ id, at, what: saved.what });
18809
19058
  }
@@ -18833,14 +19082,14 @@ function whenOf(ms) {
18833
19082
  function printable(text) {
18834
19083
  return text.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/g, " ");
18835
19084
  }
18836
- function restoreOne(root, folder, refs, rawId) {
19085
+ function restoreOne(root, folder2, refs, rawId) {
18837
19086
  const valid = savedIdSchema.safeParse(rawId);
18838
19087
  const named2 = valid.success ? `"${rawId}"` : "with that name";
18839
19088
  if (!valid.success || !refs.has(rawId)) {
18840
19089
  return refused2(`No saved copy ${named2} is kept in this project. Saved copies are kept for 7 days. To see the ones there are, run: ${LIST_COMMAND}`);
18841
19090
  }
18842
19091
  const id = valid.data;
18843
- const saved = readSaved(folder, id);
19092
+ const saved = readSaved(folder2, id);
18844
19093
  if (saved === null)
18845
19094
  return refused2(`The saved copy "${id}" could not be read, so nothing was put back.`);
18846
19095
  const ref = savedRefOf(id);
@@ -18858,7 +19107,7 @@ function restoreOne(root, folder, refs, rawId) {
18858
19107
  if (p.keptIn === "commit")
18859
19108
  fromCommit.push(p.path);
18860
19109
  else
18861
- restoreCopy(root, join26(folder, id), p.path, out);
19110
+ restoreCopy(root, join27(folder2, id), p.path, out);
18862
19111
  }
18863
19112
  if (fromCommit.length > 0)
18864
19113
  restoreFromCommit(root, ref, fromCommit, out);
@@ -18886,12 +19135,12 @@ function makeFolders(root, rel) {
18886
19135
  const segments = rel.split("/");
18887
19136
  let at = root;
18888
19137
  for (let i = 0; i < segments.length - 1; i++) {
18889
- at = join26(at, segments[i]);
19138
+ at = join27(at, segments[i]);
18890
19139
  const shown2 = segments.slice(0, i + 1).join("/");
18891
19140
  let st = lstatOrNull3(at);
18892
19141
  if (st === null) {
18893
19142
  try {
18894
- mkdirSync11(at);
19143
+ mkdirSync10(at);
18895
19144
  } catch {
18896
19145
  }
18897
19146
  st = lstatOrNull3(at);
@@ -18906,7 +19155,7 @@ function makeFolders(root, rel) {
18906
19155
  return null;
18907
19156
  }
18908
19157
  function place(root, rel, content, out) {
18909
- const abs = join26(root, rel);
19158
+ const abs = join27(root, rel);
18910
19159
  const there = lstatOrNull3(abs);
18911
19160
  if (there !== null) {
18912
19161
  if (sameAs(abs, there, content))
@@ -18954,14 +19203,14 @@ function restoreCopy(root, saveDir, rel, out) {
18954
19203
  let at = saveDir;
18955
19204
  const chain = [SAVED_COPIES_DIR_NAME, ...segments];
18956
19205
  for (let i = 0; i < chain.length - 1; i++) {
18957
- at = join26(at, chain[i]);
19206
+ at = join27(at, chain[i]);
18958
19207
  const st2 = lstatOrNull3(at);
18959
19208
  if (st2 === null || st2.isSymbolicLink() || !st2.isDirectory()) {
18960
19209
  out.left.push(`${rel}: the saved copy does not hold it`);
18961
19210
  return;
18962
19211
  }
18963
19212
  }
18964
- const src = join26(at, chain[chain.length - 1]);
19213
+ const src = join27(at, chain[chain.length - 1]);
18965
19214
  const st = lstatOrNull3(src);
18966
19215
  try {
18967
19216
  if (st !== null && st.isSymbolicLink())
@@ -19065,7 +19314,7 @@ function restoreLines(root, ref, saved, out) {
19065
19314
  }
19066
19315
  function pinnedEngagement(root) {
19067
19316
  try {
19068
- const pin = JSON.parse(readFileSync24(join26(root, BUNDLE_PIN_REL), "utf-8"));
19317
+ const pin = JSON.parse(readFileSync24(join27(root, BUNDLE_PIN_REL), "utf-8"));
19069
19318
  return isEngagementId(pin.engagementId) ? pin.engagementId : null;
19070
19319
  } catch {
19071
19320
  return null;
@@ -19086,12 +19335,12 @@ function savedRefs(root) {
19086
19335
  }
19087
19336
  return refs;
19088
19337
  }
19089
- function readSaved(folder, id) {
19090
- const dir = join26(folder, id);
19338
+ function readSaved(folder2, id) {
19339
+ const dir = join27(folder2, id);
19091
19340
  const st = lstatOrNull3(dir);
19092
19341
  if (st === null || !st.isDirectory())
19093
19342
  return null;
19094
- const file = join26(dir, SAVED_FILE_NAME);
19343
+ const file = join27(dir, SAVED_FILE_NAME);
19095
19344
  const fst = lstatOrNull3(file);
19096
19345
  if (fst === null || !fst.isFile())
19097
19346
  return null;
@@ -19102,13 +19351,13 @@ function readSaved(folder, id) {
19102
19351
  return null;
19103
19352
  }
19104
19353
  }
19105
- function pruneAtRestore(root, folder, refs, now) {
19354
+ function pruneAtRestore(root, folder2, refs, now) {
19106
19355
  try {
19107
19356
  const common = git2(root, ["rev-parse", "--git-common-dir"]);
19108
19357
  const repository = common.ok && common.text.trim() !== "" ? realOrSelf(resolve8(root, common.text.trim())) : null;
19109
19358
  let names = [];
19110
19359
  try {
19111
- names = readdirSync2(folder);
19360
+ names = readdirSync2(folder2);
19112
19361
  } catch {
19113
19362
  names = [];
19114
19363
  }
@@ -19117,7 +19366,7 @@ function pruneAtRestore(root, folder, refs, now) {
19117
19366
  if (!savedIdSchema.safeParse(id).success)
19118
19367
  continue;
19119
19368
  here.add(id);
19120
- const dir = join26(folder, id);
19369
+ const dir = join27(folder2, id);
19121
19370
  const st = lstatOrNull3(dir);
19122
19371
  const answer = savedPruneAnswer({
19123
19372
  refMadeAt: refs.get(id) ?? null,
@@ -19145,7 +19394,7 @@ function pruneAtRestore(root, folder, refs, now) {
19145
19394
  }
19146
19395
  function markedRepository(dir) {
19147
19396
  try {
19148
- const text = readFileSync24(join26(dir, SAVED_REPOSITORY_FILE_NAME), "utf-8").trim();
19397
+ const text = readFileSync24(join27(dir, SAVED_REPOSITORY_FILE_NAME), "utf-8").trim();
19149
19398
  return text === "" ? null : text;
19150
19399
  } catch {
19151
19400
  return null;
@@ -19174,7 +19423,110 @@ function realOrSelf(p) {
19174
19423
  }
19175
19424
  }
19176
19425
 
19426
+ // dist/request-shapes-verb.js
19427
+ import { chmodSync as chmodSync4, existsSync as existsSync13, mkdirSync as mkdirSync11, readFileSync as readFileSync25, rmSync as rmSync8, writeFileSync as writeFileSync8 } from "node:fs";
19428
+ import { homedir as homedir8, platform as platform5 } from "node:os";
19429
+ import { join as join28 } from "node:path";
19430
+ var REQUEST_SHAPES_USAGE = `${HALFCYCLE_COMMAND} request-shapes on|off|show|clear`;
19431
+ var REQUEST_SHAPES_FORMS = ["on", "off", "show", "clear"];
19432
+ 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.";
19433
+ var REQUEST_SHAPES_OFF_LINE = "Halfcycle has stopped writing down requests. What it wrote is still there: run request-shapes show to read it, or request-shapes clear to remove it.";
19434
+ var REQUEST_SHAPES_EMPTY_LINE = "No requests have been written down.";
19435
+ var REQUEST_SHAPES_CLEARED_LINE = "Halfcycle has removed the requests it wrote down.";
19436
+ function parseRequestShapesArgs(rest) {
19437
+ if (rest.length !== 1)
19438
+ return null;
19439
+ const form = rest[0];
19440
+ return REQUEST_SHAPES_FORMS.includes(form ?? "") ? form : null;
19441
+ }
19442
+ function asLoggedLine(text) {
19443
+ let value;
19444
+ try {
19445
+ value = JSON.parse(text);
19446
+ } catch {
19447
+ return null;
19448
+ }
19449
+ if (typeof value !== "object" || value === null)
19450
+ return null;
19451
+ const v = value;
19452
+ const str = (k) => typeof v[k] === "string" ? v[k] : "";
19453
+ return {
19454
+ at: str("at"),
19455
+ process: str("process"),
19456
+ method: str("method"),
19457
+ host: str("host"),
19458
+ path: str("path"),
19459
+ query: Array.isArray(v["query"]) ? v["query"].filter((q) => typeof q === "string") : [],
19460
+ shape: typeof v["shape"] === "string" ? v["shape"] : null,
19461
+ status: typeof v["status"] === "number" || typeof v["status"] === "string" ? v["status"] : ""
19462
+ };
19463
+ }
19464
+ function formatLine(line) {
19465
+ const at = line.at.replace(/\.\d+Z$/, "Z");
19466
+ const query = line.query.length > 0 ? `?${line.query.join("&")}` : "";
19467
+ const head = `${at} ${line.process.padEnd(22)} ${line.method.padEnd(4)} ${line.host} ${line.path}${query} -> ${String(line.status)}`;
19468
+ return line.shape === null ? head : `${head}
19469
+ ${line.shape}`;
19470
+ }
19471
+ function showLog(logPath2) {
19472
+ let text = "";
19473
+ try {
19474
+ text = existsSync13(logPath2) ? readFileSync25(logPath2, "utf-8") : "";
19475
+ } catch {
19476
+ text = "";
19477
+ }
19478
+ const lines = text.split("\n").filter((l) => l.trim() !== "").map(asLoggedLine).filter((l) => l !== null);
19479
+ if (lines.length === 0)
19480
+ return `${REQUEST_SHAPES_EMPTY_LINE}
19481
+ `;
19482
+ const count = lines.length === 1 ? "1 request." : `${lines.length} requests.`;
19483
+ return `${lines.map(formatLine).join("\n")}
19484
+ ${count}
19485
+ `;
19486
+ }
19487
+ function ownerOnly(path, mode) {
19488
+ if (platform5() !== "win32")
19489
+ chmodSync4(path, mode);
19490
+ }
19491
+ function runRequestShapes(form, home = homedir8()) {
19492
+ const folder2 = join28(home, HALFCYCLE_DIR_NAME, REQUEST_SHAPES_DIR_NAME);
19493
+ const switchPath = join28(folder2, REQUEST_SHAPES_SWITCH_FILENAME);
19494
+ const logPath2 = join28(folder2, REQUEST_SHAPES_LOG_FILENAME);
19495
+ try {
19496
+ switch (form) {
19497
+ case "on":
19498
+ mkdirSync11(folder2, { recursive: true, mode: 448 });
19499
+ ownerOnly(folder2, 448);
19500
+ writeFileSync8(switchPath, "", { mode: 384 });
19501
+ ownerOnly(switchPath, 384);
19502
+ return { stdout: `${REQUEST_SHAPES_ON_LINE}
19503
+ `, stderr: "", exitCode: 0 };
19504
+ case "off":
19505
+ rmSync8(switchPath, { force: true });
19506
+ return { stdout: `${REQUEST_SHAPES_OFF_LINE}
19507
+ `, stderr: "", exitCode: 0 };
19508
+ case "show":
19509
+ return { stdout: showLog(logPath2), stderr: "", exitCode: 0 };
19510
+ case "clear":
19511
+ if (existsSync13(logPath2)) {
19512
+ writeFileSync8(logPath2, "", { mode: 384 });
19513
+ ownerOnly(logPath2, 384);
19514
+ }
19515
+ return { stdout: `${REQUEST_SHAPES_CLEARED_LINE}
19516
+ `, stderr: "", exitCode: 0 };
19517
+ }
19518
+ } catch {
19519
+ return {
19520
+ stdout: "",
19521
+ stderr: `halfcycle request-shapes: Halfcycle could not change its settings folder in your home folder. Check that you can write to it, then run this again.
19522
+ `,
19523
+ exitCode: 1
19524
+ };
19525
+ }
19526
+ }
19527
+
19177
19528
  // dist/bin.js
19529
+ knowRequestShapeVerbs(CLI_VERBS);
19178
19530
  var ASSUME_YES_FLAGS = /* @__PURE__ */ new Set(["--yes", "-y"]);
19179
19531
  var QUIET_FLAGS = /* @__PURE__ */ new Set(["--quiet", "-q"]);
19180
19532
  var VERBOSE_FLAGS = /* @__PURE__ */ new Set(["--verbose", "-v"]);
@@ -19267,6 +19619,9 @@ var USAGE = ` ${HALFCYCLE_COMMAND} [install] [target-repo] [engagement-id] [sel
19267
19619
  show what setting up this folder would add, change, remove or leave alone; writes nothing
19268
19620
  ${HALFCYCLE_COMMAND} doctor
19269
19621
  check this folder: set up, signed in, connected, checks running, up to date; says what to do for each one that is not; changes nothing
19622
+ ${REQUEST_SHAPES_USAGE}
19623
+ write down the shape of every request this computer sends to Halfcycle, never what is in
19624
+ them; show what was written; empty it; stop
19270
19625
  ${RESTORE_USAGE}
19271
19626
  list the copies Halfcycle saved before a change it let through, or bring one back; never
19272
19627
  replaces a file or a line of work that exists now
@@ -19396,6 +19751,22 @@ ${USAGE}`);
19396
19751
  process.exitCode = report.exitCode;
19397
19752
  return;
19398
19753
  }
19754
+ if (cmd === "request-shapes") {
19755
+ const form = parseRequestShapesArgs(rest);
19756
+ if (form === null) {
19757
+ process.stderr.write(`halfcycle request-shapes: usage: ${REQUEST_SHAPES_USAGE}
19758
+ `);
19759
+ process.exitCode = 2;
19760
+ return;
19761
+ }
19762
+ const run = runRequestShapes(form);
19763
+ if (run.stdout !== "")
19764
+ process.stdout.write(run.stdout);
19765
+ if (run.stderr !== "")
19766
+ process.stderr.write(run.stderr);
19767
+ process.exitCode = run.exitCode;
19768
+ return;
19769
+ }
19399
19770
  if (cmd === "restore") {
19400
19771
  const parsed = parseRestoreArgs(rest);
19401
19772
  if (!parsed.ok) {
@@ -19415,7 +19786,7 @@ ${USAGE}`);
19415
19786
  if (installArm) {
19416
19787
  const engagementIdArg = positionals[1];
19417
19788
  const engagementTypeRaw = positionals[2] ?? "client";
19418
- if (!existsSync12(targetRepo)) {
19789
+ if (!existsSync14(targetRepo)) {
19419
19790
  process.stderr.write(`${folderMissingRefusal(targetRepo)}
19420
19791
  `);
19421
19792
  process.exit(1);