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/.claude-plugin/plugin.json +1 -1
- package/bin/bin.bundle.mjs +1649 -628
- package/dist/bin.d.ts +5 -0
- package/dist/bin.d.ts.map +1 -1
- package/dist/bin.js +0 -0
- package/dist/cli-contract.d.ts +1 -1
- package/dist/cli-contract.d.ts.map +1 -1
- package/dist/cli.js +745 -374
- package/dist/cli.js.map +3 -3
- package/dist/close-phase.d.ts +5 -0
- package/dist/close-phase.d.ts.map +1 -1
- package/dist/index.js +8 -3
- package/dist/index.js.map +2 -2
- package/dist/install.d.ts.map +1 -1
- package/dist/pace.d.ts +4 -2
- package/dist/pace.d.ts.map +1 -1
- package/dist/request-shapes-verb.d.ts +36 -0
- package/dist/request-shapes-verb.d.ts.map +1 -0
- package/dist/request-shapes.d.ts +25 -0
- package/dist/request-shapes.d.ts.map +1 -0
- package/dist/route.d.ts +19 -18
- package/dist/route.d.ts.map +1 -1
- package/package.json +11 -11
package/dist/cli.js
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// dist/
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
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,
|
|
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: (
|
|
631
|
-
C6Malformed: (x,
|
|
632
|
-
C6Missing: (slug,
|
|
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
|
|
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
|
|
715
|
-
if (file.key.startsWith(
|
|
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
|
|
744
|
-
if (file.key.startsWith(
|
|
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
|
|
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(
|
|
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,
|
|
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,
|
|
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((
|
|
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
|
|
1311
|
-
return tasks.
|
|
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 (
|
|
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 && !
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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,
|
|
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,
|
|
1608
|
-
M8Marked: (crit,
|
|
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,
|
|
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,
|
|
1654
|
+
lines.push(refusal2("M8", criteriaRecordLineText.M8Unmarked(crit, folder2, slugOf(crit))));
|
|
1656
1655
|
if (!marked && isEdge)
|
|
1657
|
-
lines.push(refusal2("M8", criteriaRecordLineText.M8Marked(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
|
|
4087
|
-
if (
|
|
4088
|
-
return `in the ${
|
|
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
|
|
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
|
|
7137
|
+
return join2(home ?? homedir2(), HALFCYCLE_DIR_NAME);
|
|
6893
7138
|
}
|
|
6894
7139
|
function accountStorePath(home) {
|
|
6895
|
-
return
|
|
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
|
|
7206
|
+
return join3(halfcycleHome(home), ENGAGEMENTS_DIR, engagementId);
|
|
6962
7207
|
}
|
|
6963
7208
|
function engagementEnvPath(engagementId, home) {
|
|
6964
|
-
return
|
|
7209
|
+
return join3(engagementStateDir(engagementId, home), ENV_FILENAME);
|
|
6965
7210
|
}
|
|
6966
7211
|
function notThisAccountMarkerPath(engagementId, home) {
|
|
6967
|
-
return
|
|
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
|
|
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:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
7269
|
-
if (!
|
|
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 =
|
|
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
|
|
7308
|
-
import { join as
|
|
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
|
|
7660
|
-
import { join as
|
|
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) =>
|
|
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 =
|
|
7698
|
-
if (
|
|
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(
|
|
7707
|
-
writeFileSync4(
|
|
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 (!
|
|
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) =>
|
|
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(
|
|
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 =
|
|
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
|
-
...
|
|
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
|
|
8107
|
-
if (
|
|
8351
|
+
const write2 = steps.credentialWrite;
|
|
8352
|
+
if (write2 === null)
|
|
8108
8353
|
return [];
|
|
8109
|
-
const action =
|
|
8110
|
-
return [{ kind: "sign-in", path:
|
|
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 =
|
|
8145
|
-
const gitignoreAfter =
|
|
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(
|
|
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(
|
|
8169
|
-
dirExistsAfter: (dir) =>
|
|
8170
|
-
stateExistsAfter:
|
|
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
|
|
8528
|
-
const target = resolve(
|
|
8529
|
-
if (dirname3(target) !== resolve(targetRepo,
|
|
8530
|
-
throw new Error(`[halfcycle] crew profile "${rel}" does not sit directly in ${
|
|
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 =
|
|
8862
|
+
const manifest = join7(candidate, ".claude-plugin", "plugin.json");
|
|
8618
8863
|
try {
|
|
8619
|
-
if (
|
|
8864
|
+
if (existsSync5(manifest))
|
|
8620
8865
|
return candidate;
|
|
8621
8866
|
} catch {
|
|
8622
8867
|
}
|
|
8623
8868
|
candidate = dirname3(candidate);
|
|
8624
8869
|
}
|
|
8625
|
-
return
|
|
8870
|
+
return join7(startDir, "..");
|
|
8626
8871
|
}
|
|
8627
8872
|
var BUNDLE_ROOT = findBundleRoot(__dirname);
|
|
8628
8873
|
function resolveVendoredBinary() {
|
|
8629
8874
|
const candidates = [
|
|
8630
|
-
|
|
8631
|
-
|
|
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 (
|
|
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
|
-
|
|
8642
|
-
|
|
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 (
|
|
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
|
|
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 (
|
|
9005
|
+
if (existsSync5(src))
|
|
8761
9006
|
out.set(rel, fileSha256(readFileSync6(src)));
|
|
8762
9007
|
};
|
|
8763
9008
|
for (const cmd of commands) {
|
|
8764
|
-
const srcFile =
|
|
9009
|
+
const srcFile = join7(BUNDLE_ROOT, ".claude-plugin", cmd.path);
|
|
8765
9010
|
fromFile(commandStubRel(cmd.name), srcFile);
|
|
8766
|
-
if (
|
|
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(
|
|
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 =
|
|
8889
|
-
plain = !lstatOrNull(abs)?.isSymbolicLink() && lstatOrNull(
|
|
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(
|
|
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 =
|
|
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 =
|
|
8992
|
-
if (!
|
|
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(
|
|
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 =
|
|
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 =
|
|
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:
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
9105
|
-
return
|
|
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 =
|
|
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 =
|
|
9129
|
-
const places = [
|
|
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("~/") ?
|
|
9133
|
-
places.push(
|
|
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 (!
|
|
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"
|
|
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",
|
|
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 =
|
|
9613
|
-
if (!
|
|
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(
|
|
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 =
|
|
10254
|
+
const gitignorePath = join7(targetRepo, GITIGNORE_REL);
|
|
10008
10255
|
const header = GITIGNORE_HEADER;
|
|
10009
|
-
const before =
|
|
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(
|
|
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 =
|
|
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 (!
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
10172
|
-
if (!
|
|
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(
|
|
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 =
|
|
10293
|
-
if (!
|
|
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 =
|
|
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 =
|
|
10350
|
-
if (!
|
|
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(
|
|
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(
|
|
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 =
|
|
10365
|
-
const preexisted =
|
|
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(
|
|
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 =
|
|
10393
|
-
const existing =
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
|
10440
|
-
if (
|
|
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 =
|
|
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(
|
|
10727
|
+
writeAllowlisted(join7(targetRepo, CODEX_HOOKS_REL2), targetRepo, planned.text, []);
|
|
10481
10728
|
}
|
|
10482
10729
|
function readCodexConfig(targetRepo) {
|
|
10483
|
-
const
|
|
10484
|
-
if (
|
|
10730
|
+
const folder2 = lstatOrNull(join7(targetRepo, posix2.dirname(CODEX_CONFIG_REL2)));
|
|
10731
|
+
if (folder2 !== null && !folder2.isDirectory())
|
|
10485
10732
|
return "unreadable";
|
|
10486
|
-
const path =
|
|
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(
|
|
10787
|
+
writeAllowlisted(join7(targetRepo, CODEX_CONFIG_REL2), targetRepo, planned.text, []);
|
|
10541
10788
|
}
|
|
10542
10789
|
function planCrewRoster(targetRepo) {
|
|
10543
|
-
return decideOwned(
|
|
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(
|
|
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 (
|
|
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 (!
|
|
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) =>
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
11069
|
+
if (!existsSync5(at(CREW_ROSTER_REL))) {
|
|
10823
11070
|
writeCrewRoster(targetRepo, report);
|
|
10824
11071
|
}
|
|
10825
11072
|
writeMissingSelfBuildCrewProfiles(targetRepo, report);
|
|
10826
|
-
if (!
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
11531
|
+
write2(`[halfcycle] Falling back to the code-based sign-in: ${attempt.reason}.
|
|
11285
11532
|
`);
|
|
11286
11533
|
} else {
|
|
11287
|
-
|
|
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
|
|
11297
|
-
|
|
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
|
-
|
|
11548
|
+
write2(`[halfcycle] Opened your browser at ${started.verificationUrl}
|
|
11302
11549
|
`);
|
|
11303
11550
|
} else {
|
|
11304
|
-
|
|
11551
|
+
write2(`[halfcycle] Open this address to sign in: ${started.verificationUrl}
|
|
11305
11552
|
`);
|
|
11306
11553
|
}
|
|
11307
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
12204
|
+
write2(script.preamble);
|
|
11958
12205
|
if (!script.asks)
|
|
11959
12206
|
return acting;
|
|
11960
12207
|
if (deps.assumeYes === true) {
|
|
11961
|
-
|
|
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
|
-
|
|
12215
|
+
write2(script.noTerminalNote);
|
|
11969
12216
|
return acting;
|
|
11970
12217
|
}
|
|
11971
12218
|
const ask = deps.ask ?? askLine;
|
|
11972
|
-
|
|
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
|
-
|
|
12224
|
+
write2(script.switchOpening);
|
|
11978
12225
|
const replacement = await switchAccount(serviceUrl, deps);
|
|
11979
12226
|
const replacementIdentity = await describeAccount(serviceUrl, replacement.credential, deps.fetchImpl);
|
|
11980
|
-
|
|
12227
|
+
write2(`[halfcycle] Signed in. This install will ${actClause(act)}.
|
|
11981
12228
|
`);
|
|
11982
12229
|
if (script.afterSwitch !== "")
|
|
11983
|
-
|
|
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
|
|
12095
|
-
import { delimiter, dirname as dirname4, join as
|
|
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,
|
|
12126
|
-
const key = (
|
|
12127
|
-
const real = realOrLiteral(
|
|
12128
|
-
return
|
|
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((
|
|
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
|
|
12137
|
-
for (let parent = dirname4(
|
|
12138
|
-
if (
|
|
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(
|
|
12390
|
+
function realOrLiteral(folder2) {
|
|
12144
12391
|
try {
|
|
12145
|
-
return realpathSync(
|
|
12392
|
+
return realpathSync(folder2);
|
|
12146
12393
|
} catch {
|
|
12147
|
-
return
|
|
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 !== "" &&
|
|
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 :
|
|
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 !== "" &&
|
|
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 `${
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
12244
|
-
import { join as
|
|
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
|
|
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(
|
|
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 =
|
|
12295
|
-
if (!
|
|
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 (!
|
|
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 (
|
|
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
|
|
12386
|
-
import { homedir as
|
|
12387
|
-
import { join as
|
|
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 (!
|
|
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 (!
|
|
12679
|
+
if (!existsSync8(join11(root, INSTALL_MANIFEST_REL)))
|
|
12433
12680
|
missing.push("the install record");
|
|
12434
|
-
if (!
|
|
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(
|
|
12461
|
-
if (!
|
|
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
|
|
12711
|
+
return isObject2(entry) && typeof entry["url"] === "string";
|
|
12465
12712
|
}
|
|
12466
12713
|
function settingsRegisterEveryCommand(root) {
|
|
12467
|
-
const parsed = readJson(
|
|
12468
|
-
if (!
|
|
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 (!
|
|
12722
|
+
if (!isObject2(group) || !Array.isArray(group["hooks"]))
|
|
12476
12723
|
continue;
|
|
12477
12724
|
for (const hook of group["hooks"]) {
|
|
12478
|
-
if (
|
|
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 =
|
|
12499
|
-
if (!
|
|
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(
|
|
12527
|
-
if (
|
|
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 (!
|
|
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 =
|
|
12614
|
-
if (!
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
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 =
|
|
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 =
|
|
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 (!
|
|
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
|
|
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
|
|
12761
|
-
import { delimiter as delimiter2, join as
|
|
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
|
|
13010
|
+
const platform6 = deps.platform ?? process.platform;
|
|
12764
13011
|
const exec = deps.exec ?? execFileSync3;
|
|
12765
13012
|
try {
|
|
12766
|
-
return
|
|
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
|
|
12804
|
-
const exists = deps.exists ??
|
|
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 =
|
|
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 =
|
|
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
|
|
12852
|
-
import { basename as basename2, join as
|
|
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 =
|
|
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 =
|
|
13540
|
-
const mdPath =
|
|
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:
|
|
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
|
|
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
|
|
13834
|
+
import { join as join20 } from "node:path";
|
|
13588
13835
|
|
|
13589
13836
|
// dist/plan.js
|
|
13590
|
-
import { existsSync as
|
|
13591
|
-
import { join as
|
|
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
|
|
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 =
|
|
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,
|
|
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
|
|
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
|
|
14397
|
+
import { join as join15 } from "node:path";
|
|
14140
14398
|
function readDescriptionFile(repoRoot) {
|
|
14141
14399
|
let text;
|
|
14142
14400
|
try {
|
|
14143
|
-
text = readFileSync13(
|
|
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
|
|
14368
|
-
import { basename as basename3, isAbsolute as isAbsolute5, join as
|
|
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 =
|
|
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 =
|
|
14534
|
-
const mdPath =
|
|
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:
|
|
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 ${
|
|
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
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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
|
|
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 ? `${
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
17421
|
-
import { dirname as dirname6, join as
|
|
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 =
|
|
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 =
|
|
17480
|
-
const pinPath =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
18511
|
+
if (!lstatSync3(join23(root, dirRel)).isDirectory())
|
|
18254
18512
|
return;
|
|
18255
|
-
names = readdirSync(
|
|
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(
|
|
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 ??
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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 {
|
|
18615
|
-
import {
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
18738
|
-
if (
|
|
18739
|
-
|
|
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
|
|
18762
|
-
import { homedir as
|
|
18763
|
-
import { join as
|
|
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
|
|
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,
|
|
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,
|
|
18797
|
-
return restoreOne(root,
|
|
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,
|
|
19052
|
+
function list(root, folder2, refs) {
|
|
18804
19053
|
const rows = [];
|
|
18805
19054
|
for (const [id, at] of refs) {
|
|
18806
|
-
const saved = readSaved(
|
|
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,
|
|
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(
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
19090
|
-
const dir =
|
|
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 =
|
|
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,
|
|
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(
|
|
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 =
|
|
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(
|
|
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 (!
|
|
19789
|
+
if (!existsSync14(targetRepo)) {
|
|
19419
19790
|
process.stderr.write(`${folderMissingRefusal(targetRepo)}
|
|
19420
19791
|
`);
|
|
19421
19792
|
process.exit(1);
|