halfcycle 0.7.1 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/bin/bin.bundle.mjs +1628 -637
- 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 +769 -378
- 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/doctor.d.ts +9 -0
- package/dist/doctor.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)
|
|
@@ -6879,20 +7124,26 @@ function seenModelsLineOf(task, models) {
|
|
|
6879
7124
|
return `Seen in your coding agent's own records: ${task} ran on ${joinedOf(models)}.`;
|
|
6880
7125
|
}
|
|
6881
7126
|
|
|
7127
|
+
// ../events/dist/checks-report-words.js
|
|
7128
|
+
var CHECKS_NO_REPORT_YET_STATE = "Set up, no report yet";
|
|
7129
|
+
var CHECKS_NO_REPORT_YET_WHEN = "Halfcycle's checks report after the first file edit they can see. A new shell file counts once added to git.";
|
|
7130
|
+
var CHECKS_IF_STILL_UNREPORTED = "If edits go unreported: Codex, approve /hooks; Claude Code, trust this folder.";
|
|
7131
|
+
var CHECKS_GONE_QUIET = "Halfcycle's checks have not reported from this machine lately.";
|
|
7132
|
+
|
|
6882
7133
|
// dist/engagement-credential.js
|
|
6883
7134
|
import { chmodSync as chmodSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
6884
7135
|
import { platform as platform2 } from "node:os";
|
|
6885
|
-
import { join as
|
|
7136
|
+
import { join as join3 } from "node:path";
|
|
6886
7137
|
|
|
6887
7138
|
// dist/account-credential.js
|
|
6888
7139
|
import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
6889
|
-
import { homedir, platform } from "node:os";
|
|
6890
|
-
import { dirname, join } from "node:path";
|
|
7140
|
+
import { homedir as homedir2, platform } from "node:os";
|
|
7141
|
+
import { dirname, join as join2 } from "node:path";
|
|
6891
7142
|
function halfcycleHome(home) {
|
|
6892
|
-
return
|
|
7143
|
+
return join2(home ?? homedir2(), HALFCYCLE_DIR_NAME);
|
|
6893
7144
|
}
|
|
6894
7145
|
function accountStorePath(home) {
|
|
6895
|
-
return
|
|
7146
|
+
return join2(halfcycleHome(home), ACCOUNT_STORE_FILENAME);
|
|
6896
7147
|
}
|
|
6897
7148
|
function readStore(home) {
|
|
6898
7149
|
try {
|
|
@@ -6958,13 +7209,13 @@ function assertEngagementId(engagementId) {
|
|
|
6958
7209
|
}
|
|
6959
7210
|
function engagementStateDir(engagementId, home) {
|
|
6960
7211
|
assertEngagementId(engagementId);
|
|
6961
|
-
return
|
|
7212
|
+
return join3(halfcycleHome(home), ENGAGEMENTS_DIR, engagementId);
|
|
6962
7213
|
}
|
|
6963
7214
|
function engagementEnvPath(engagementId, home) {
|
|
6964
|
-
return
|
|
7215
|
+
return join3(engagementStateDir(engagementId, home), ENV_FILENAME);
|
|
6965
7216
|
}
|
|
6966
7217
|
function notThisAccountMarkerPath(engagementId, home) {
|
|
6967
|
-
return
|
|
7218
|
+
return join3(engagementStateDir(engagementId, home), NOT_THIS_ACCOUNT_MARKER_FILENAME);
|
|
6968
7219
|
}
|
|
6969
7220
|
function writeNotThisAccountMarker(engagementId, home) {
|
|
6970
7221
|
const dir = engagementStateDir(engagementId, home);
|
|
@@ -7089,8 +7340,8 @@ halfcycle_env_file() {
|
|
|
7089
7340
|
|
|
7090
7341
|
// dist/identity.js
|
|
7091
7342
|
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
|
|
7343
|
+
import { existsSync as existsSync2, linkSync, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
7344
|
+
import { dirname as dirname2, isAbsolute, join as join4 } from "node:path";
|
|
7094
7345
|
import { randomUUID } from "node:crypto";
|
|
7095
7346
|
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
7347
|
var PROJECT_IDENTITY_REL = ".halfcycle/project.json";
|
|
@@ -7221,7 +7472,7 @@ function readIgnoredPaths(targetRepoRoot, paths) {
|
|
|
7221
7472
|
const toplevel = git(targetRepoRoot, ["rev-parse", "--show-toplevel"]);
|
|
7222
7473
|
if (toplevel === null)
|
|
7223
7474
|
return null;
|
|
7224
|
-
return ignored.map((entry) => isAbsolute(entry.source) ? entry : { ...entry, source:
|
|
7475
|
+
return ignored.map((entry) => isAbsolute(entry.source) ? entry : { ...entry, source: join4(toplevel, entry.source) });
|
|
7225
7476
|
}
|
|
7226
7477
|
function mintIdentity(targetRepoRoot) {
|
|
7227
7478
|
return {
|
|
@@ -7232,7 +7483,7 @@ function mintIdentity(targetRepoRoot) {
|
|
|
7232
7483
|
};
|
|
7233
7484
|
}
|
|
7234
7485
|
function plannedRemoteRepair(targetRepoRoot) {
|
|
7235
|
-
const path =
|
|
7486
|
+
const path = join4(targetRepoRoot, PROJECT_IDENTITY_REL);
|
|
7236
7487
|
let onDisk;
|
|
7237
7488
|
try {
|
|
7238
7489
|
const parsed = JSON.parse(readFileSync3(path, "utf-8"));
|
|
@@ -7254,7 +7505,7 @@ function plannedRemoteRepair(targetRepoRoot) {
|
|
|
7254
7505
|
});
|
|
7255
7506
|
}
|
|
7256
7507
|
function replaceProjectIdentityFile(targetRepoRoot, text) {
|
|
7257
|
-
const path =
|
|
7508
|
+
const path = join4(targetRepoRoot, PROJECT_IDENTITY_REL);
|
|
7258
7509
|
const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
7259
7510
|
try {
|
|
7260
7511
|
writeFileSync3(temporary, text, { encoding: "utf-8", flag: "wx" });
|
|
@@ -7265,8 +7516,8 @@ function replaceProjectIdentityFile(targetRepoRoot, text) {
|
|
|
7265
7516
|
}
|
|
7266
7517
|
}
|
|
7267
7518
|
function planProjectIdentity(targetRepoRoot) {
|
|
7268
|
-
const path =
|
|
7269
|
-
if (!
|
|
7519
|
+
const path = join4(targetRepoRoot, PROJECT_IDENTITY_REL);
|
|
7520
|
+
if (!existsSync2(path)) {
|
|
7270
7521
|
return { action: "add", after: serializeProjectIdentity(mintIdentity(targetRepoRoot)) };
|
|
7271
7522
|
}
|
|
7272
7523
|
const repaired = plannedRemoteRepair(targetRepoRoot);
|
|
@@ -7284,7 +7535,7 @@ function serializeProjectIdentity(identity) {
|
|
|
7284
7535
|
return JSON.stringify(identity, null, 2) + "\n";
|
|
7285
7536
|
}
|
|
7286
7537
|
function createProjectIdentityFile(targetRepoRoot, text) {
|
|
7287
|
-
const path =
|
|
7538
|
+
const path = join4(targetRepoRoot, PROJECT_IDENTITY_REL);
|
|
7288
7539
|
mkdirSync3(dirname2(path), { recursive: true });
|
|
7289
7540
|
const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
7290
7541
|
try {
|
|
@@ -7304,8 +7555,8 @@ function createProjectIdentityFile(targetRepoRoot, text) {
|
|
|
7304
7555
|
}
|
|
7305
7556
|
|
|
7306
7557
|
// dist/install-plan.js
|
|
7307
|
-
import { existsSync as
|
|
7308
|
-
import { join as
|
|
7558
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5 } from "node:fs";
|
|
7559
|
+
import { join as join6, posix } from "node:path";
|
|
7309
7560
|
|
|
7310
7561
|
// dist/install-manifest.js
|
|
7311
7562
|
import { Buffer as Buffer2 } from "node:buffer";
|
|
@@ -7656,8 +7907,8 @@ function union(a, b) {
|
|
|
7656
7907
|
}
|
|
7657
7908
|
|
|
7658
7909
|
// dist/scan.js
|
|
7659
|
-
import { existsSync as
|
|
7660
|
-
import { join as
|
|
7910
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
7911
|
+
import { join as join5 } from "node:path";
|
|
7661
7912
|
var HALFCYCLE_STATE_FORMAT = "halfcycle-state-file/v1";
|
|
7662
7913
|
var HALFCYCLE_STATE_NOTE = [
|
|
7663
7914
|
"This file is a local cache written ONCE when Halfcycle was installed. It is not a",
|
|
@@ -7680,7 +7931,7 @@ var LAYER_ARTEFACT_PATHS = {
|
|
|
7680
7931
|
function scanLayers(targetRepo, scannedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
7681
7932
|
const layers = {};
|
|
7682
7933
|
for (const [layer, paths] of Object.entries(LAYER_ARTEFACT_PATHS)) {
|
|
7683
|
-
const found = paths.filter((p) =>
|
|
7934
|
+
const found = paths.filter((p) => existsSync3(join5(targetRepo, p)));
|
|
7684
7935
|
if (found.length > 0) {
|
|
7685
7936
|
layers[layer] = {
|
|
7686
7937
|
value: "asserted",
|
|
@@ -7694,8 +7945,8 @@ function scanLayers(targetRepo, scannedAt = (/* @__PURE__ */ new Date()).toISOSt
|
|
|
7694
7945
|
}
|
|
7695
7946
|
var BOOTSTRAP_STATE_REL = ".halfcycle/state.json";
|
|
7696
7947
|
function planBootstrapScan(targetRepo) {
|
|
7697
|
-
const statePath =
|
|
7698
|
-
if (
|
|
7948
|
+
const statePath = join5(targetRepo, BOOTSTRAP_STATE_REL);
|
|
7949
|
+
if (existsSync3(statePath)) {
|
|
7699
7950
|
const existing = JSON.parse(readFileSync4(statePath, "utf-8"));
|
|
7700
7951
|
return { state: existing, text: null };
|
|
7701
7952
|
}
|
|
@@ -7703,8 +7954,8 @@ function planBootstrapScan(targetRepo) {
|
|
|
7703
7954
|
return { state, text: JSON.stringify(state, null, 2) + "\n" };
|
|
7704
7955
|
}
|
|
7705
7956
|
function writeBootstrapState(targetRepo, text) {
|
|
7706
|
-
mkdirSync4(
|
|
7707
|
-
writeFileSync4(
|
|
7957
|
+
mkdirSync4(join5(targetRepo, ".halfcycle"), { recursive: true });
|
|
7958
|
+
writeFileSync4(join5(targetRepo, BOOTSTRAP_STATE_REL), text, "utf-8");
|
|
7708
7959
|
}
|
|
7709
7960
|
|
|
7710
7961
|
// dist/install-plan.js
|
|
@@ -7854,12 +8105,12 @@ function planInstall(options) {
|
|
|
7854
8105
|
throw new Error("[halfcycle] a plan for a project needs its kind as well as its id.");
|
|
7855
8106
|
}
|
|
7856
8107
|
}
|
|
7857
|
-
if (!
|
|
8108
|
+
if (!existsSync4(targetRepo)) {
|
|
7858
8109
|
throw new Error(`[halfcycle] Target repo does not exist: ${targetRepo}`);
|
|
7859
8110
|
}
|
|
7860
8111
|
const writeSet = closedWriteSet();
|
|
7861
8112
|
const previous = readPreviousManifest(targetRepo);
|
|
7862
|
-
const dirsBefore = new Set([...writeSet.dirs].filter((dir) =>
|
|
8113
|
+
const dirsBefore = new Set([...writeSet.dirs].filter((dir) => existsSync4(join6(targetRepo, dir))));
|
|
7863
8114
|
const { version, commands } = planCommandStubs(targetRepo, previous, writeSet);
|
|
7864
8115
|
const codexSkills = planCodexSkills(targetRepo, previous, writeSet);
|
|
7865
8116
|
const vendored = planVendoredBinary(targetRepo);
|
|
@@ -7881,7 +8132,7 @@ function planInstall(options) {
|
|
|
7881
8132
|
const gitattributes = planGitattributes(targetRepo);
|
|
7882
8133
|
const identity = planProjectIdentity(targetRepo);
|
|
7883
8134
|
if (identity.after === null)
|
|
7884
|
-
readFileSync5(
|
|
8135
|
+
readFileSync5(join6(targetRepo, PROJECT_IDENTITY_REL));
|
|
7885
8136
|
const credentialPlan = !preview && credential !== void 0 ? planEngagementCredential(credential, engagementId, home) : null;
|
|
7886
8137
|
const credentialWrite = credentialPlan === null ? null : { outcome: credentialPlan.outcome, existed: credentialPlan.existed, path: engagementEnvPath(engagementId, home) };
|
|
7887
8138
|
const storedAfter = credentialPlan?.storedAfter ?? (preview ? Object.fromEntries(ENGAGEMENT_ENV_KEYS.map((key) => [key, "supplied at sign-in"])) : readEngagementEnv(engagementId, home) ?? {});
|
|
@@ -8014,7 +8265,7 @@ function entriesOf(steps, record2) {
|
|
|
8014
8265
|
}
|
|
8015
8266
|
};
|
|
8016
8267
|
const pending = (rel) => {
|
|
8017
|
-
const there =
|
|
8268
|
+
const there = existsSync4(join6(targetRepo, rel));
|
|
8018
8269
|
entries.push({ path: rel, action: there ? "change" : "add", kind: kindOf(rel), bytes: PENDING });
|
|
8019
8270
|
};
|
|
8020
8271
|
steps.commands.forEach(fromFile);
|
|
@@ -8062,7 +8313,7 @@ function entriesOf(steps, record2) {
|
|
|
8062
8313
|
action: "leave",
|
|
8063
8314
|
kind: "codex-server",
|
|
8064
8315
|
bytes: null,
|
|
8065
|
-
...
|
|
8316
|
+
...existsSync4(join6(targetRepo, CODEX_CONFIG_REL2)) ? { yours: true } : {}
|
|
8066
8317
|
});
|
|
8067
8318
|
break;
|
|
8068
8319
|
}
|
|
@@ -8103,11 +8354,11 @@ function outsideOf(steps) {
|
|
|
8103
8354
|
outside.push({ kind: "new-project" });
|
|
8104
8355
|
return outside;
|
|
8105
8356
|
}
|
|
8106
|
-
const
|
|
8107
|
-
if (
|
|
8357
|
+
const write2 = steps.credentialWrite;
|
|
8358
|
+
if (write2 === null)
|
|
8108
8359
|
return [];
|
|
8109
|
-
const action =
|
|
8110
|
-
return [{ kind: "sign-in", path:
|
|
8360
|
+
const action = write2.outcome === "skipped" ? "leave" : write2.existed ? "change" : "add";
|
|
8361
|
+
return [{ kind: "sign-in", path: write2.path, action }];
|
|
8111
8362
|
}
|
|
8112
8363
|
function projectPinned(targetRepo) {
|
|
8113
8364
|
try {
|
|
@@ -8141,10 +8392,10 @@ function applyInstallPlan(plan) {
|
|
|
8141
8392
|
putPlannedCodexHooks(targetRepo, steps.codexHooks);
|
|
8142
8393
|
putPlannedCodexConfig(targetRepo, steps.codexConfig);
|
|
8143
8394
|
const gitignore = putPlannedGitignore(targetRepo, steps.gitignore);
|
|
8144
|
-
const gitignorePath =
|
|
8145
|
-
const gitignoreAfter =
|
|
8395
|
+
const gitignorePath = join6(targetRepo, GITIGNORE_REL);
|
|
8396
|
+
const gitignoreAfter = existsSync4(gitignorePath) ? readFileSync5(gitignorePath, "utf-8") : null;
|
|
8146
8397
|
const gitattributes = putPlannedGitattributes(targetRepo, steps.gitattributes);
|
|
8147
|
-
const gitattributesAfter = readTextOrNull(
|
|
8398
|
+
const gitattributesAfter = readTextOrNull(join6(targetRepo, GITATTRIBUTES_REL));
|
|
8148
8399
|
writeProjectIdentity(targetRepo, steps.identity);
|
|
8149
8400
|
const credential = steps.credential;
|
|
8150
8401
|
const credentialOutcome = steps.credentialWrite !== null && credential !== void 0 ? writeEngagementCredential(credential, engagementId, steps.home) : null;
|
|
@@ -8165,9 +8416,9 @@ function applyInstallPlan(plan) {
|
|
|
8165
8416
|
gitattributesAfter,
|
|
8166
8417
|
credential: credentialOutcome,
|
|
8167
8418
|
legacy,
|
|
8168
|
-
bytesAfter: (rel) => readFileSync5(
|
|
8169
|
-
dirExistsAfter: (dir) =>
|
|
8170
|
-
stateExistsAfter:
|
|
8419
|
+
bytesAfter: (rel) => readFileSync5(join6(targetRepo, rel)),
|
|
8420
|
+
dirExistsAfter: (dir) => existsSync4(join6(targetRepo, dir)),
|
|
8421
|
+
stateExistsAfter: existsSync4(join6(targetRepo, BOOTSTRAP_STATE_REL))
|
|
8171
8422
|
});
|
|
8172
8423
|
const record2 = planInstallRecord(targetRepo, manifestText);
|
|
8173
8424
|
putPlannedFile(targetRepo, record2);
|
|
@@ -8524,10 +8775,10 @@ function crewProfileRel(agent, callsign) {
|
|
|
8524
8775
|
return agent === "claude-code" ? `${CLAUDE_AGENTS_DIR}/${callsign}.md` : `${CODEX_AGENTS_DIR}/${callsign.toLowerCase()}.toml`;
|
|
8525
8776
|
}
|
|
8526
8777
|
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 ${
|
|
8778
|
+
const folder2 = rel.startsWith(`${CLAUDE_AGENTS_DIR}/`) ? CLAUDE_AGENTS_DIR : CODEX_AGENTS_DIR;
|
|
8779
|
+
const target = resolve(join7(targetRepo, rel));
|
|
8780
|
+
if (dirname3(target) !== resolve(targetRepo, folder2)) {
|
|
8781
|
+
throw new Error(`[halfcycle] crew profile "${rel}" does not sit directly in ${folder2}.`);
|
|
8531
8782
|
}
|
|
8532
8783
|
return target;
|
|
8533
8784
|
}
|
|
@@ -8614,35 +8865,35 @@ var __dirname = dirname3(__filename);
|
|
|
8614
8865
|
function findBundleRoot(startDir) {
|
|
8615
8866
|
let candidate = startDir;
|
|
8616
8867
|
for (let i = 0; i < 4; i++) {
|
|
8617
|
-
const manifest =
|
|
8868
|
+
const manifest = join7(candidate, ".claude-plugin", "plugin.json");
|
|
8618
8869
|
try {
|
|
8619
|
-
if (
|
|
8870
|
+
if (existsSync5(manifest))
|
|
8620
8871
|
return candidate;
|
|
8621
8872
|
} catch {
|
|
8622
8873
|
}
|
|
8623
8874
|
candidate = dirname3(candidate);
|
|
8624
8875
|
}
|
|
8625
|
-
return
|
|
8876
|
+
return join7(startDir, "..");
|
|
8626
8877
|
}
|
|
8627
8878
|
var BUNDLE_ROOT = findBundleRoot(__dirname);
|
|
8628
8879
|
function resolveVendoredBinary() {
|
|
8629
8880
|
const candidates = [
|
|
8630
|
-
|
|
8631
|
-
|
|
8881
|
+
join7(BUNDLE_ROOT, "bin", "bin.bundle.mjs"),
|
|
8882
|
+
join7(BUNDLE_ROOT, "..", "runner", "dist", "bin.bundle.mjs")
|
|
8632
8883
|
];
|
|
8633
8884
|
for (const candidate of candidates) {
|
|
8634
|
-
if (
|
|
8885
|
+
if (existsSync5(candidate))
|
|
8635
8886
|
return candidate;
|
|
8636
8887
|
}
|
|
8637
8888
|
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
8889
|
}
|
|
8639
8890
|
function resolvePreFilterShell() {
|
|
8640
8891
|
const candidates = [
|
|
8641
|
-
|
|
8642
|
-
|
|
8892
|
+
join7(BUNDLE_ROOT, "bin", PRE_FILTER_SHELL_FILENAME),
|
|
8893
|
+
join7(BUNDLE_ROOT, "..", "runner", "dist", PRE_FILTER_SHELL_FILENAME)
|
|
8643
8894
|
];
|
|
8644
8895
|
for (const candidate of candidates) {
|
|
8645
|
-
if (
|
|
8896
|
+
if (existsSync5(candidate))
|
|
8646
8897
|
return readFileSync6(candidate, "utf-8");
|
|
8647
8898
|
}
|
|
8648
8899
|
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 +8933,7 @@ function isAllowlisted(targetRelPath) {
|
|
|
8682
8933
|
var CREW_ROSTER_REL = ".halfcycle/crew.json";
|
|
8683
8934
|
var CAPTURED_INDEX_REL = "test/fixtures/captured/manifest.json";
|
|
8684
8935
|
function capturedIndexScaffold() {
|
|
8685
|
-
return
|
|
8936
|
+
return join7(BUNDLE_ROOT, "scaffolding", "test", "fixtures", "captured", "manifest.json");
|
|
8686
8937
|
}
|
|
8687
8938
|
var GITIGNORE_REL = ".gitignore";
|
|
8688
8939
|
var GITATTRIBUTES_REL = ".gitattributes";
|
|
@@ -8757,13 +9008,13 @@ function closedWriteSet() {
|
|
|
8757
9008
|
function sha256OfWhatThisVersionWrites(commands) {
|
|
8758
9009
|
const out = /* @__PURE__ */ new Map();
|
|
8759
9010
|
const fromFile = (rel, src) => {
|
|
8760
|
-
if (
|
|
9011
|
+
if (existsSync5(src))
|
|
8761
9012
|
out.set(rel, fileSha256(readFileSync6(src)));
|
|
8762
9013
|
};
|
|
8763
9014
|
for (const cmd of commands) {
|
|
8764
|
-
const srcFile =
|
|
9015
|
+
const srcFile = join7(BUNDLE_ROOT, ".claude-plugin", cmd.path);
|
|
8765
9016
|
fromFile(commandStubRel(cmd.name), srcFile);
|
|
8766
|
-
if (
|
|
9017
|
+
if (existsSync5(srcFile)) {
|
|
8767
9018
|
out.set(skillRel(cmd.name), fileSha256(Buffer.from(codexSkillContent(cmd.name, readFileSync6(srcFile, "utf-8")), "utf-8")));
|
|
8768
9019
|
}
|
|
8769
9020
|
}
|
|
@@ -8781,7 +9032,7 @@ function bytesAreOurs(path, bytes, recorded, writeSet) {
|
|
|
8781
9032
|
}
|
|
8782
9033
|
function readPreviousManifest(targetRepo) {
|
|
8783
9034
|
try {
|
|
8784
|
-
return parseManifest(readFileSync6(
|
|
9035
|
+
return parseManifest(readFileSync6(join7(targetRepo, INSTALL_MANIFEST_REL), "utf-8"));
|
|
8785
9036
|
} catch {
|
|
8786
9037
|
return null;
|
|
8787
9038
|
}
|
|
@@ -8885,8 +9136,8 @@ function findNotShared(targetRepo, manifest, collidedPaths) {
|
|
|
8885
9136
|
for (let dir = posix2.dirname(rel); dir !== "."; dir = posix2.dirname(dir)) {
|
|
8886
9137
|
let plain = dirVerdicts.get(dir);
|
|
8887
9138
|
if (plain === void 0) {
|
|
8888
|
-
const abs =
|
|
8889
|
-
plain = !lstatOrNull(abs)?.isSymbolicLink() && lstatOrNull(
|
|
9139
|
+
const abs = join7(targetRepo, dir);
|
|
9140
|
+
plain = !lstatOrNull(abs)?.isSymbolicLink() && lstatOrNull(join7(abs, ".git")) === null;
|
|
8890
9141
|
dirVerdicts.set(dir, plain);
|
|
8891
9142
|
}
|
|
8892
9143
|
if (!plain)
|
|
@@ -8900,7 +9151,7 @@ function findNotShared(targetRepo, manifest, collidedPaths) {
|
|
|
8900
9151
|
...merged,
|
|
8901
9152
|
INSTALL_MANIFEST_REL
|
|
8902
9153
|
])
|
|
8903
|
-
].filter((rel) => !collided.has(rel)).filter((rel) => lstatOrNull(
|
|
9154
|
+
].filter((rel) => !collided.has(rel)).filter((rel) => lstatOrNull(join7(targetRepo, rel)) !== null).filter(travelsThroughPlainDirs).sort();
|
|
8904
9155
|
const ignored = readIgnoredPaths(targetRepo, paths);
|
|
8905
9156
|
if (ignored === null)
|
|
8906
9157
|
return null;
|
|
@@ -8932,7 +9183,7 @@ function isPlainObject2(value) {
|
|
|
8932
9183
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
8933
9184
|
}
|
|
8934
9185
|
function readPluginManifest() {
|
|
8935
|
-
const manifestPath =
|
|
9186
|
+
const manifestPath = join7(BUNDLE_ROOT, ".claude-plugin", "plugin.json");
|
|
8936
9187
|
const raw = readFileSync6(manifestPath, "utf-8");
|
|
8937
9188
|
return JSON.parse(raw);
|
|
8938
9189
|
}
|
|
@@ -8988,14 +9239,14 @@ ${transformed}`;
|
|
|
8988
9239
|
function planManifestCommands(manifest, targetRepo, previous, writeSet) {
|
|
8989
9240
|
const recordedFiles = new Map((previous?.files ?? []).filter((file) => writeSet.files.has(file.path)).map((file) => [file.path, file]));
|
|
8990
9241
|
return (manifest.commands ?? []).map((cmd) => {
|
|
8991
|
-
const srcFile =
|
|
8992
|
-
if (!
|
|
9242
|
+
const srcFile = join7(BUNDLE_ROOT, ".claude-plugin", cmd.path);
|
|
9243
|
+
if (!existsSync5(srcFile)) {
|
|
8993
9244
|
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
9245
|
}
|
|
8995
9246
|
const rel = commandStubRel(cmd.name);
|
|
8996
9247
|
const content = readFileSync6(srcFile, "utf-8");
|
|
8997
9248
|
const recorded = recordedFiles.get(rel);
|
|
8998
|
-
const planned = decideCollisionSafe(
|
|
9249
|
+
const planned = decideCollisionSafe(join7(targetRepo, rel), targetRepo, content, (current) => bytesAreOurs(rel, current, recorded, writeSet));
|
|
8999
9250
|
return { ...planned, editedCollided: planned.outcome === "collided" && recorded !== void 0 };
|
|
9000
9251
|
});
|
|
9001
9252
|
}
|
|
@@ -9005,7 +9256,7 @@ function putPlannedFile(targetRepoRoot, planned) {
|
|
|
9005
9256
|
if (!isAllowlisted(planned.rel)) {
|
|
9006
9257
|
throw new Error(`[halfcycle] Write-allowlist violation: attempted to write "${planned.rel}". Only the method surface and scaffolding paths are writable.`);
|
|
9007
9258
|
}
|
|
9008
|
-
const target =
|
|
9259
|
+
const target = join7(targetRepoRoot, planned.rel);
|
|
9009
9260
|
mkdirSync5(dirname3(target), { recursive: true });
|
|
9010
9261
|
writeFileSync5(target, planned.text, "utf-8");
|
|
9011
9262
|
}
|
|
@@ -9025,7 +9276,7 @@ function decideCollisionSafe(targetAbsPath, targetRepoRoot, content, isOurs) {
|
|
|
9025
9276
|
if (!isAllowlisted(rel)) {
|
|
9026
9277
|
throw new Error(`[halfcycle] Write-allowlist violation: attempted to write "${rel}". Only the method surface and scaffolding paths are writable.`);
|
|
9027
9278
|
}
|
|
9028
|
-
const current =
|
|
9279
|
+
const current = existsSync5(targetAbsPath) ? readFileSync6(targetAbsPath) : null;
|
|
9029
9280
|
const text = inLineEndingOf(current?.toString("utf-8") ?? null, content);
|
|
9030
9281
|
if (current === null)
|
|
9031
9282
|
return { rel, text, outcome: "written", replacedExisting: false };
|
|
@@ -9053,7 +9304,7 @@ function decideOwned(targetAbsPath, targetRepoRoot, content) {
|
|
|
9053
9304
|
const text = inLineEndingOf(current, content);
|
|
9054
9305
|
if (current === text)
|
|
9055
9306
|
return { rel, text, outcome: "skipped", replacedExisting: false };
|
|
9056
|
-
return { rel, text, outcome: "written", replacedExisting:
|
|
9307
|
+
return { rel, text, outcome: "written", replacedExisting: existsSync5(targetAbsPath) };
|
|
9057
9308
|
}
|
|
9058
9309
|
var GENERATED_HEADER_LINE_INDEX = 1;
|
|
9059
9310
|
function decideOwnedGenerated(targetAbsPath, targetRepoRoot, content, generatedMarker) {
|
|
@@ -9061,7 +9312,7 @@ function decideOwnedGenerated(targetAbsPath, targetRepoRoot, content, generatedM
|
|
|
9061
9312
|
if (!isAllowlisted(rel)) {
|
|
9062
9313
|
throw new Error(`[halfcycle] Write-allowlist violation: attempted to write "${rel}". Only the method surface and scaffolding paths are writable.`);
|
|
9063
9314
|
}
|
|
9064
|
-
if (!
|
|
9315
|
+
if (!existsSync5(targetAbsPath))
|
|
9065
9316
|
return { rel, text: content, outcome: "written", replacedExisting: false };
|
|
9066
9317
|
const current = readFileSync6(targetAbsPath, "utf-8");
|
|
9067
9318
|
if (current === content)
|
|
@@ -9079,7 +9330,7 @@ function recordOwnedGenerated(report, planned) {
|
|
|
9079
9330
|
return planned.outcome;
|
|
9080
9331
|
}
|
|
9081
9332
|
function recordOwned(report, targetAbsPath, targetRepoRoot, content, rel) {
|
|
9082
|
-
const existedBefore =
|
|
9333
|
+
const existedBefore = existsSync5(targetAbsPath);
|
|
9083
9334
|
const outcome = writeOwned(targetAbsPath, targetRepoRoot, content);
|
|
9084
9335
|
record(report, outcome, rel);
|
|
9085
9336
|
if (outcome === "written" && existedBefore)
|
|
@@ -9101,8 +9352,8 @@ var DENY_PATTERNS = [
|
|
|
9101
9352
|
];
|
|
9102
9353
|
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
9354
|
function readExistingSettings(targetRepo) {
|
|
9104
|
-
const settingsPath =
|
|
9105
|
-
return
|
|
9355
|
+
const settingsPath = join7(targetRepo, SETTINGS_REL);
|
|
9356
|
+
return existsSync5(settingsPath) ? JSON.parse(readFileSync6(settingsPath, "utf-8")) : void 0;
|
|
9106
9357
|
}
|
|
9107
9358
|
var SettingsParseRefused = class extends Error {
|
|
9108
9359
|
constructor(message) {
|
|
@@ -9111,7 +9362,7 @@ var SettingsParseRefused = class extends Error {
|
|
|
9111
9362
|
}
|
|
9112
9363
|
};
|
|
9113
9364
|
function settingsParseRefusal(targetRepo, err) {
|
|
9114
|
-
const file =
|
|
9365
|
+
const file = join7(targetRepo, SETTINGS_REL);
|
|
9115
9366
|
const offset = /position (\d+)/.exec(err instanceof Error ? err.message : "")?.[1];
|
|
9116
9367
|
let where = "";
|
|
9117
9368
|
if (offset !== void 0) {
|
|
@@ -9125,15 +9376,15 @@ var HALFCYCLE_STATUS_LINE = Object.freeze({
|
|
|
9125
9376
|
type: "command",
|
|
9126
9377
|
command: 'bash "$CLAUDE_PROJECT_DIR"/.claude/hooks/guard-runner.sh statusline'
|
|
9127
9378
|
});
|
|
9128
|
-
function statusLineSetElsewhere(targetRepo, home =
|
|
9129
|
-
const places = [
|
|
9379
|
+
function statusLineSetElsewhere(targetRepo, home = homedir3(), env = process.env) {
|
|
9380
|
+
const places = [join7(targetRepo, ".claude", "settings.local.json"), join7(home, ".claude", "settings.json")];
|
|
9130
9381
|
const configDir = env["CLAUDE_CONFIG_DIR"];
|
|
9131
9382
|
if (configDir !== void 0 && configDir !== "") {
|
|
9132
|
-
const expanded = configDir === "~" ? home : configDir.startsWith("~/") ?
|
|
9133
|
-
places.push(
|
|
9383
|
+
const expanded = configDir === "~" ? home : configDir.startsWith("~/") ? join7(home, configDir.slice(2)) : configDir;
|
|
9384
|
+
places.push(join7(isAbsolute2(expanded) ? expanded : resolve(expanded), "settings.json"));
|
|
9134
9385
|
}
|
|
9135
9386
|
return places.some((path) => {
|
|
9136
|
-
if (!
|
|
9387
|
+
if (!existsSync5(path))
|
|
9137
9388
|
return false;
|
|
9138
9389
|
let value;
|
|
9139
9390
|
try {
|
|
@@ -9478,7 +9729,8 @@ ${GENERATED_USER_PROMPT_SCOPE_HEADER} the Halfcycle installer, now also for the
|
|
|
9478
9729
|
# <phase>/<part>", "question <phase>/<part>: <words>", "carry <phase>/<part>:
|
|
9479
9730
|
# <words>", "drop <phase>/<part>: <words>", "reject <phase>/<task>: <words>",
|
|
9480
9731
|
# "autonomy on" or "autonomy off". Also lets the person who is typing let one
|
|
9481
|
-
# step Halfcycle paused run once, by typing "allow"
|
|
9732
|
+
# step Halfcycle paused run once, by typing "allow", or "allow" and the step's
|
|
9733
|
+
# number ("allow 2"), as the whole prompt, and change
|
|
9482
9734
|
# how a build is paced, by typing "faster", "run 5 agents", "keep 30% free" or
|
|
9483
9735
|
# "use up to 70%" as the whole prompt, and answer a phase's usage limit by
|
|
9484
9736
|
# typing "keep going", "stop", "limit it to 1.5 times" or "limit it to 300
|
|
@@ -9488,7 +9740,8 @@ ${GENERATED_USER_PROMPT_SCOPE_HEADER} the Halfcycle installer, now also for the
|
|
|
9488
9740
|
# looks only at the start of the prompt's value. Unless the prompt begins, after
|
|
9489
9741
|
# any whitespace (typed, or escaped in the input), with "approve ", "send back ",
|
|
9490
9742
|
# "accept ", "question ", "carry ", "drop ", "reject " or "autonomy ", in any case,
|
|
9491
|
-
# or is "allow",
|
|
9743
|
+
# or is "allow" (alone, or with spaces and a number of up to four digits), one
|
|
9744
|
+
# of the five pacing phrases or one of the usage-limit phrases
|
|
9492
9745
|
# and nothing else but whitespace,
|
|
9493
9746
|
# it exits 0 with no output, and Node never starts. A quote inside any other field arrives
|
|
9494
9747
|
# escaped, so no other field (the folder the session is in, for one) can read as
|
|
@@ -9516,7 +9769,7 @@ IFS= read -r -d '' HC_INPUT || true
|
|
|
9516
9769
|
# \\u escape, or as the UTF-8 bytes of a Unicode space. Compared byte for byte.
|
|
9517
9770
|
HC_LEAD='[[:space:]]|\\\\[nrtf]|\\\\u(000[9a-d]|0020|00a0|1680|200[0-9a]|202[89f]|205f|3000|feff)'
|
|
9518
9771
|
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
|
|
9772
|
+
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
9773
|
exit 0
|
|
9521
9774
|
fi
|
|
9522
9775
|
|
|
@@ -9609,8 +9862,8 @@ function removeRetiredGeneratedFile(targetRepo, rel, headerLineIndex, knownHeade
|
|
|
9609
9862
|
return decided === "removed" ? removePlannedRetiredFile(targetRepo, rel) : decided;
|
|
9610
9863
|
}
|
|
9611
9864
|
function decideRetiredRemoval(targetRepo, rel, headerLineIndex, knownHeaderHashes) {
|
|
9612
|
-
const path =
|
|
9613
|
-
if (!
|
|
9865
|
+
const path = join7(targetRepo, rel);
|
|
9866
|
+
if (!existsSync5(path))
|
|
9614
9867
|
return "absent";
|
|
9615
9868
|
let raw;
|
|
9616
9869
|
try {
|
|
@@ -9628,7 +9881,7 @@ function decideRetiredRemoval(targetRepo, rel, headerLineIndex, knownHeaderHashe
|
|
|
9628
9881
|
}
|
|
9629
9882
|
function removePlannedRetiredFile(targetRepo, rel) {
|
|
9630
9883
|
try {
|
|
9631
|
-
rmSync2(
|
|
9884
|
+
rmSync2(join7(targetRepo, rel));
|
|
9632
9885
|
} catch {
|
|
9633
9886
|
return "failed";
|
|
9634
9887
|
}
|
|
@@ -10004,9 +10257,9 @@ function gitignoreCovers(content) {
|
|
|
10004
10257
|
return REQUIRED_GITIGNORE_ENTRIES.every((entry) => lines.has(entry));
|
|
10005
10258
|
}
|
|
10006
10259
|
function planGitignore(targetRepo) {
|
|
10007
|
-
const gitignorePath =
|
|
10260
|
+
const gitignorePath = join7(targetRepo, GITIGNORE_REL);
|
|
10008
10261
|
const header = GITIGNORE_HEADER;
|
|
10009
|
-
const before =
|
|
10262
|
+
const before = existsSync5(gitignorePath) ? readFileSync6(gitignorePath, "utf-8") : null;
|
|
10010
10263
|
if (before === null) {
|
|
10011
10264
|
return { before, after: [header, ...REQUIRED_GITIGNORE_ENTRIES].join("\n") + "\n", outcome: "written" };
|
|
10012
10265
|
}
|
|
@@ -10022,7 +10275,7 @@ function putPlannedGitignore(targetRepo, planned) {
|
|
|
10022
10275
|
if (planned.outcome !== "written" || planned.after === null)
|
|
10023
10276
|
return planned.outcome;
|
|
10024
10277
|
try {
|
|
10025
|
-
writeFileSync5(
|
|
10278
|
+
writeFileSync5(join7(targetRepo, GITIGNORE_REL), planned.after, "utf-8");
|
|
10026
10279
|
return "written";
|
|
10027
10280
|
} catch {
|
|
10028
10281
|
return "failed";
|
|
@@ -10041,12 +10294,12 @@ function lfRules(paths) {
|
|
|
10041
10294
|
}
|
|
10042
10295
|
var REQUIRED_GITATTRIBUTES_RULES = lfRules(Object.keys(OWNED_GENERATED_HEADERS));
|
|
10043
10296
|
function planGitattributes(targetRepo) {
|
|
10044
|
-
const path =
|
|
10297
|
+
const path = join7(targetRepo, GITATTRIBUTES_REL);
|
|
10045
10298
|
const rules = REQUIRED_GITATTRIBUTES_RULES;
|
|
10046
10299
|
const before = readTextOrNull(path);
|
|
10047
10300
|
if (lstatOrNull(path)?.isSymbolicLink() === true)
|
|
10048
10301
|
return { before, after: before, outcome: "failed" };
|
|
10049
|
-
if (!
|
|
10302
|
+
if (!existsSync5(path)) {
|
|
10050
10303
|
return { before, after: [GITATTRIBUTES_HEADER, ...rules].join("\n") + "\n", outcome: "written" };
|
|
10051
10304
|
}
|
|
10052
10305
|
if (before === null)
|
|
@@ -10064,7 +10317,7 @@ function putPlannedGitattributes(targetRepo, planned) {
|
|
|
10064
10317
|
if (planned.outcome !== "written" || planned.after === null)
|
|
10065
10318
|
return planned.outcome;
|
|
10066
10319
|
try {
|
|
10067
|
-
writeAllowlisted(
|
|
10320
|
+
writeAllowlisted(join7(targetRepo, GITATTRIBUTES_REL), targetRepo, planned.after, []);
|
|
10068
10321
|
return "written";
|
|
10069
10322
|
} catch {
|
|
10070
10323
|
return "failed";
|
|
@@ -10098,7 +10351,7 @@ function planBundlePin(targetRepoRoot, version, engagementId, engagementType, cr
|
|
|
10098
10351
|
...carriedMethodVersion !== void 0 ? { methodVersion: carriedMethodVersion } : {},
|
|
10099
10352
|
...projectPage !== void 0 ? { projectPage } : {}
|
|
10100
10353
|
};
|
|
10101
|
-
const current = readFileOrNull(
|
|
10354
|
+
const current = readFileOrNull(join7(targetRepoRoot, BUNDLE_PIN_REL));
|
|
10102
10355
|
const text = inLineEndingOf(current, JSON.stringify(pin, null, 2) + "\n");
|
|
10103
10356
|
return {
|
|
10104
10357
|
rel: BUNDLE_PIN_REL,
|
|
@@ -10129,7 +10382,7 @@ function planBundlePinPreview(targetRepoRoot, version, home) {
|
|
|
10129
10382
|
return planBundlePin(targetRepoRoot, version, existing.engagementId, existing.engagementType, void 0, void 0);
|
|
10130
10383
|
}
|
|
10131
10384
|
function writeBundlePin(targetRepoRoot, planned, writtenPaths) {
|
|
10132
|
-
const pinPath =
|
|
10385
|
+
const pinPath = join7(targetRepoRoot, BUNDLE_PIN_REL);
|
|
10133
10386
|
writeAllowlisted(pinPath, targetRepoRoot, planned.text, writtenPaths);
|
|
10134
10387
|
}
|
|
10135
10388
|
function updatePinnedMethodVersion(repoRoot, engagementId, version) {
|
|
@@ -10141,7 +10394,7 @@ function updatePinnedMethodVersion(repoRoot, engagementId, version) {
|
|
|
10141
10394
|
}
|
|
10142
10395
|
if (pin === null || pin.engagementId !== engagementId)
|
|
10143
10396
|
return;
|
|
10144
|
-
const pinPath =
|
|
10397
|
+
const pinPath = join7(repoRoot, BUNDLE_PIN_REL);
|
|
10145
10398
|
const updated = { ...pin, methodVersion: version };
|
|
10146
10399
|
const pinText = inLineEndingOf(readFileOrNull(pinPath), JSON.stringify(updated, null, 2) + "\n");
|
|
10147
10400
|
try {
|
|
@@ -10161,15 +10414,15 @@ function renderCrewRoster() {
|
|
|
10161
10414
|
}
|
|
10162
10415
|
function writeCrewRoster(targetRepoRoot, report) {
|
|
10163
10416
|
const rendered = renderCrewRoster();
|
|
10164
|
-
const crewPath =
|
|
10417
|
+
const crewPath = join7(targetRepoRoot, CREW_ROSTER_REL);
|
|
10165
10418
|
return recordOwned(report, crewPath, targetRepoRoot, rendered, CREW_ROSTER_REL);
|
|
10166
10419
|
}
|
|
10167
10420
|
function pinCreatedByAccountId(pin) {
|
|
10168
10421
|
return nonEmpty(pin?.createdByAccountId) ?? nonEmpty(pin?.accountId);
|
|
10169
10422
|
}
|
|
10170
10423
|
function readBundlePin(targetRepoRoot) {
|
|
10171
|
-
const pinPath =
|
|
10172
|
-
if (!
|
|
10424
|
+
const pinPath = join7(targetRepoRoot, BUNDLE_PIN_REL);
|
|
10425
|
+
if (!existsSync5(pinPath))
|
|
10173
10426
|
return null;
|
|
10174
10427
|
return JSON.parse(readFileSync6(pinPath, "utf-8"));
|
|
10175
10428
|
}
|
|
@@ -10194,7 +10447,7 @@ function readPinnedEngagement(targetRepoRoot, home) {
|
|
|
10194
10447
|
}
|
|
10195
10448
|
function readLegacyEnvLocal(targetRepoRoot) {
|
|
10196
10449
|
try {
|
|
10197
|
-
return parseEnvText(readFileSync6(
|
|
10450
|
+
return parseEnvText(readFileSync6(join7(targetRepoRoot, LEGACY_ENV_LOCAL_REL), "utf-8"));
|
|
10198
10451
|
} catch {
|
|
10199
10452
|
return null;
|
|
10200
10453
|
}
|
|
@@ -10289,8 +10542,8 @@ function engagementCredentialValues(credential, engagementId) {
|
|
|
10289
10542
|
}
|
|
10290
10543
|
var LEGACY_ENV_LOCAL_REL = ".env.local";
|
|
10291
10544
|
function migrateLegacyEnvLocal(targetRepo, stored) {
|
|
10292
|
-
const path =
|
|
10293
|
-
if (!
|
|
10545
|
+
const path = join7(targetRepo, LEGACY_ENV_LOCAL_REL);
|
|
10546
|
+
if (!existsSync5(path))
|
|
10294
10547
|
return { outcome: "absent", unbacked: [], missingForAdoption: [] };
|
|
10295
10548
|
let raw;
|
|
10296
10549
|
try {
|
|
@@ -10320,7 +10573,7 @@ function migrateLegacyEnvLocal(targetRepo, stored) {
|
|
|
10320
10573
|
return { outcome: "stripped", unbacked: [], missingForAdoption: [], keptText: kept.join("\n") };
|
|
10321
10574
|
}
|
|
10322
10575
|
function applyLegacyMigration(targetRepo, decided) {
|
|
10323
|
-
const path =
|
|
10576
|
+
const path = join7(targetRepo, LEGACY_ENV_LOCAL_REL);
|
|
10324
10577
|
try {
|
|
10325
10578
|
if (decided.outcome === "removed") {
|
|
10326
10579
|
rmSync2(path);
|
|
@@ -10346,23 +10599,23 @@ function planCodexSkills(targetRepo, previous, writeSet) {
|
|
|
10346
10599
|
const manifest = readPluginManifest();
|
|
10347
10600
|
const recordedFiles = new Map((previous?.files ?? []).filter((file) => writeSet.files.has(file.path)).map((file) => [file.path, file]));
|
|
10348
10601
|
return (manifest.commands ?? []).map((cmd) => {
|
|
10349
|
-
const srcFile =
|
|
10350
|
-
if (!
|
|
10602
|
+
const srcFile = join7(BUNDLE_ROOT, ".claude-plugin", cmd.path);
|
|
10603
|
+
if (!existsSync5(srcFile)) {
|
|
10351
10604
|
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
10605
|
}
|
|
10353
10606
|
const rel = skillRel(cmd.name);
|
|
10354
10607
|
const content = codexSkillContent(cmd.name, readFileSync6(srcFile, "utf-8"));
|
|
10355
10608
|
const recorded = recordedFiles.get(rel);
|
|
10356
|
-
const planned = decideCollisionSafe(
|
|
10609
|
+
const planned = decideCollisionSafe(join7(targetRepo, rel), targetRepo, content, (current) => bytesAreOurs(rel, current, recorded, writeSet));
|
|
10357
10610
|
return { ...planned, editedCollided: planned.outcome === "collided" && recorded !== void 0 };
|
|
10358
10611
|
});
|
|
10359
10612
|
}
|
|
10360
10613
|
function planVendoredBinary(targetRepo) {
|
|
10361
|
-
return decideOwned(
|
|
10614
|
+
return decideOwned(join7(targetRepo, ".halfcycle", "bin", "bin.bundle.mjs"), targetRepo, readFileSync6(resolveVendoredBinary(), "utf-8"));
|
|
10362
10615
|
}
|
|
10363
10616
|
function planSettings(targetRepo, previous, home, env) {
|
|
10364
|
-
const settingsPath =
|
|
10365
|
-
const preexisted =
|
|
10617
|
+
const settingsPath = join7(targetRepo, SETTINGS_REL);
|
|
10618
|
+
const preexisted = existsSync5(settingsPath);
|
|
10366
10619
|
const generated = JSON.parse(generateSettingsJson());
|
|
10367
10620
|
let existing;
|
|
10368
10621
|
try {
|
|
@@ -10386,11 +10639,11 @@ function planSettings(targetRepo, previous, home, env) {
|
|
|
10386
10639
|
return { file, preexisted, existing, generated, merged, retiredOurs };
|
|
10387
10640
|
}
|
|
10388
10641
|
function planHookScripts(targetRepo) {
|
|
10389
|
-
return hookScriptsThisVersionWrites().map(([rel, content]) => decideOwnedGenerated(
|
|
10642
|
+
return hookScriptsThisVersionWrites().map(([rel, content]) => decideOwnedGenerated(join7(targetRepo, rel), targetRepo, content, OWNED_GENERATED_HEADERS[rel]));
|
|
10390
10643
|
}
|
|
10391
10644
|
function planMcpRegistration(targetRepo, credential) {
|
|
10392
|
-
const mcpPath =
|
|
10393
|
-
const existing =
|
|
10645
|
+
const mcpPath = join7(targetRepo, MCP_REGISTRATION_REL);
|
|
10646
|
+
const existing = existsSync5(mcpPath) ? readFileSync6(mcpPath, "utf-8") : null;
|
|
10394
10647
|
if (credential === void 0)
|
|
10395
10648
|
return { kind: "none", existing };
|
|
10396
10649
|
if (credential === "pending")
|
|
@@ -10402,11 +10655,11 @@ function planMcpRegistration(targetRepo, credential) {
|
|
|
10402
10655
|
function putPlannedMcp(targetRepo, planned) {
|
|
10403
10656
|
if (planned.kind !== "write" || planned.outcome === "skipped")
|
|
10404
10657
|
return;
|
|
10405
|
-
writeAllowlisted(
|
|
10658
|
+
writeAllowlisted(join7(targetRepo, MCP_REGISTRATION_REL), targetRepo, planned.text, []);
|
|
10406
10659
|
}
|
|
10407
10660
|
function fileInFolderPlace(root, rel) {
|
|
10408
10661
|
for (let dir = posix2.dirname(rel); dir !== "."; dir = posix2.dirname(dir)) {
|
|
10409
|
-
const found = lstatOrNull(
|
|
10662
|
+
const found = lstatOrNull(join7(root, dir));
|
|
10410
10663
|
if (found !== null && !found.isDirectory())
|
|
10411
10664
|
return true;
|
|
10412
10665
|
}
|
|
@@ -10415,7 +10668,7 @@ function fileInFolderPlace(root, rel) {
|
|
|
10415
10668
|
function symlinkOnPath(root, rel) {
|
|
10416
10669
|
let current = root;
|
|
10417
10670
|
for (const segment of rel.split("/")) {
|
|
10418
|
-
current =
|
|
10671
|
+
current = join7(current, segment);
|
|
10419
10672
|
try {
|
|
10420
10673
|
if (lstatSync(current).isSymbolicLink())
|
|
10421
10674
|
return true;
|
|
@@ -10436,10 +10689,10 @@ function planCodexHooks(targetRepo) {
|
|
|
10436
10689
|
});
|
|
10437
10690
|
if (symlinkOnPath(targetRepo, CODEX_HOOKS_REL2))
|
|
10438
10691
|
return leave("link", null);
|
|
10439
|
-
const
|
|
10440
|
-
if (
|
|
10692
|
+
const folder2 = lstatOrNull(join7(targetRepo, posix2.dirname(CODEX_HOOKS_REL2)));
|
|
10693
|
+
if (folder2 !== null && !folder2.isDirectory())
|
|
10441
10694
|
return leave("unreadable", null);
|
|
10442
|
-
const path =
|
|
10695
|
+
const path = join7(targetRepo, CODEX_HOOKS_REL2);
|
|
10443
10696
|
let before = null;
|
|
10444
10697
|
if (lstatOrNull(path) !== null) {
|
|
10445
10698
|
try {
|
|
@@ -10477,13 +10730,13 @@ function planCodexHooks(targetRepo) {
|
|
|
10477
10730
|
function putPlannedCodexHooks(targetRepo, planned) {
|
|
10478
10731
|
if (planned.outcome !== "written" || planned.text === null)
|
|
10479
10732
|
return;
|
|
10480
|
-
writeAllowlisted(
|
|
10733
|
+
writeAllowlisted(join7(targetRepo, CODEX_HOOKS_REL2), targetRepo, planned.text, []);
|
|
10481
10734
|
}
|
|
10482
10735
|
function readCodexConfig(targetRepo) {
|
|
10483
|
-
const
|
|
10484
|
-
if (
|
|
10736
|
+
const folder2 = lstatOrNull(join7(targetRepo, posix2.dirname(CODEX_CONFIG_REL2)));
|
|
10737
|
+
if (folder2 !== null && !folder2.isDirectory())
|
|
10485
10738
|
return "unreadable";
|
|
10486
|
-
const path =
|
|
10739
|
+
const path = join7(targetRepo, CODEX_CONFIG_REL2);
|
|
10487
10740
|
const stat = lstatOrNull(path);
|
|
10488
10741
|
if (stat === null)
|
|
10489
10742
|
return { text: null };
|
|
@@ -10537,10 +10790,10 @@ function planCodexConfig(targetRepo, credential) {
|
|
|
10537
10790
|
function putPlannedCodexConfig(targetRepo, planned) {
|
|
10538
10791
|
if (planned.outcome !== "written" || planned.text === null)
|
|
10539
10792
|
return;
|
|
10540
|
-
writeAllowlisted(
|
|
10793
|
+
writeAllowlisted(join7(targetRepo, CODEX_CONFIG_REL2), targetRepo, planned.text, []);
|
|
10541
10794
|
}
|
|
10542
10795
|
function planCrewRoster(targetRepo) {
|
|
10543
|
-
return decideOwned(
|
|
10796
|
+
return decideOwned(join7(targetRepo, CREW_ROSTER_REL), targetRepo, renderCrewRoster());
|
|
10544
10797
|
}
|
|
10545
10798
|
function planCrewProfiles(targetRepo, previous, writeSet) {
|
|
10546
10799
|
const recordedFiles = new Map((previous?.files ?? []).filter((file) => writeSet.files.has(file.path)).map((file) => [file.path, file]));
|
|
@@ -10555,7 +10808,7 @@ function planCrewProfiles(targetRepo, previous, writeSet) {
|
|
|
10555
10808
|
});
|
|
10556
10809
|
}
|
|
10557
10810
|
function planInstallRecord(targetRepo, text) {
|
|
10558
|
-
return decideOwned(
|
|
10811
|
+
return decideOwned(join7(targetRepo, INSTALL_MANIFEST_REL), targetRepo, text);
|
|
10559
10812
|
}
|
|
10560
10813
|
function accountInstall(steps, actual) {
|
|
10561
10814
|
const { targetRepo, writeSet, pin } = steps;
|
|
@@ -10783,7 +11036,7 @@ function mcpHasHalfcycleEntry(text) {
|
|
|
10783
11036
|
function writeMissingSelfBuildCrewProfiles(targetRepoRoot, report) {
|
|
10784
11037
|
for (const { rel, text } of renderCrewProfiles()) {
|
|
10785
11038
|
const target = crewProfileTarget(targetRepoRoot, rel);
|
|
10786
|
-
if (
|
|
11039
|
+
if (existsSync5(target))
|
|
10787
11040
|
continue;
|
|
10788
11041
|
writeAllowlisted(target, targetRepoRoot, text, report.writtenPaths);
|
|
10789
11042
|
}
|
|
@@ -10791,7 +11044,7 @@ function writeMissingSelfBuildCrewProfiles(targetRepoRoot, report) {
|
|
|
10791
11044
|
function installSelfBuild(options) {
|
|
10792
11045
|
const { targetRepo, engagementId, credential, freshlyMinted, home } = options;
|
|
10793
11046
|
assertEngagementId(engagementId);
|
|
10794
|
-
if (!
|
|
11047
|
+
if (!existsSync5(targetRepo)) {
|
|
10795
11048
|
throw new Error(`[halfcycle] Target repo does not exist: ${targetRepo}`);
|
|
10796
11049
|
}
|
|
10797
11050
|
const report = {
|
|
@@ -10802,7 +11055,7 @@ function installSelfBuild(options) {
|
|
|
10802
11055
|
editedCollidedPaths: [],
|
|
10803
11056
|
replacedPaths: []
|
|
10804
11057
|
};
|
|
10805
|
-
const at = (rel) =>
|
|
11058
|
+
const at = (rel) => join7(targetRepo, rel);
|
|
10806
11059
|
const version = readPluginManifest().version;
|
|
10807
11060
|
const existingMcp = readFileOrNull(at(MCP_REGISTRATION_REL));
|
|
10808
11061
|
const mcpText = existingMcp === null || !mcpHasHalfcycleEntry(existingMcp) ? inLineEndingOf(existingMcp, generateMcpRegistration(existingMcp, credential.mcpUrl)) : null;
|
|
@@ -10813,17 +11066,17 @@ function installSelfBuild(options) {
|
|
|
10813
11066
|
if (mcpText !== null) {
|
|
10814
11067
|
writeAllowlisted(at(MCP_REGISTRATION_REL), targetRepo, mcpText, report.writtenPaths);
|
|
10815
11068
|
}
|
|
10816
|
-
if (!
|
|
11069
|
+
if (!existsSync5(at(VENDORED_BIN_REL))) {
|
|
10817
11070
|
recordOwned(report, at(VENDORED_BIN_REL), targetRepo, readFileSync6(resolveVendoredBinary(), "utf-8"), VENDORED_BIN_REL);
|
|
10818
11071
|
}
|
|
10819
|
-
if (!
|
|
11072
|
+
if (!existsSync5(at(SELF_BUILD_GITIGNORE_REL))) {
|
|
10820
11073
|
writeAllowlisted(at(SELF_BUILD_GITIGNORE_REL), targetRepo, SELF_BUILD_GITIGNORE_TEXT, report.writtenPaths);
|
|
10821
11074
|
}
|
|
10822
|
-
if (!
|
|
11075
|
+
if (!existsSync5(at(CREW_ROSTER_REL))) {
|
|
10823
11076
|
writeCrewRoster(targetRepo, report);
|
|
10824
11077
|
}
|
|
10825
11078
|
writeMissingSelfBuildCrewProfiles(targetRepo, report);
|
|
10826
|
-
if (!
|
|
11079
|
+
if (!existsSync5(at(BUNDLE_PIN_REL))) {
|
|
10827
11080
|
writeBundlePin(targetRepo, planBundlePin(targetRepo, version, engagementId, "self-build", void 0, credential.methodVersion), report.writtenPaths);
|
|
10828
11081
|
}
|
|
10829
11082
|
return { writtenPaths: report.writtenPaths, credentialWritten };
|
|
@@ -11159,7 +11412,7 @@ function localDeadline(started) {
|
|
|
11159
11412
|
return new Date(started.expiresAt).getTime() + started.pollIntervalMs * DEADLINE_SLACK_INTERVALS;
|
|
11160
11413
|
}
|
|
11161
11414
|
async function pollForCredential(serviceUrl, started, deps = {}) {
|
|
11162
|
-
const
|
|
11415
|
+
const write2 = deps.write ?? defaultWrite;
|
|
11163
11416
|
const sleep = deps.sleep ?? defaultSleep;
|
|
11164
11417
|
const now = deps.now ?? Date.now;
|
|
11165
11418
|
const url = tokenUrlFor(serviceUrl);
|
|
@@ -11181,7 +11434,7 @@ async function pollForCredential(serviceUrl, started, deps = {}) {
|
|
|
11181
11434
|
}
|
|
11182
11435
|
if (now() - announcedAt >= (deps.stillWaitingMs ?? STILL_WAITING_MS)) {
|
|
11183
11436
|
announcedAt = now();
|
|
11184
|
-
|
|
11437
|
+
write2(stillWaitingLine(announcedAt - startedAt, started));
|
|
11185
11438
|
}
|
|
11186
11439
|
await sleep(interval);
|
|
11187
11440
|
}
|
|
@@ -11240,13 +11493,13 @@ async function pollBesideLoopback(serviceUrl, started, wake, deps) {
|
|
|
11240
11493
|
}
|
|
11241
11494
|
}
|
|
11242
11495
|
async function signIn(serviceUrl, deps = {}) {
|
|
11243
|
-
const
|
|
11496
|
+
const write2 = deps.write ?? defaultWrite;
|
|
11244
11497
|
const now = deps.now ?? Date.now;
|
|
11245
11498
|
const env = deps.env ?? process.env;
|
|
11246
11499
|
if (isNonInteractive(env)) {
|
|
11247
11500
|
throw new SignInRefused(SIGN_IN_TIMED_OUT, SIGN_IN_NON_INTERACTIVE_MESSAGE);
|
|
11248
11501
|
}
|
|
11249
|
-
|
|
11502
|
+
write2(`[halfcycle] Halfcycle needs an account before it can ${deps.reason ?? DEFAULT_SIGN_IN_REASON} \u2014 signing you in through your browser.
|
|
11250
11503
|
`);
|
|
11251
11504
|
const bind = await bindLoopback(env, deps.onwardEngagementId);
|
|
11252
11505
|
let closed = false;
|
|
@@ -11262,17 +11515,17 @@ async function signIn(serviceUrl, deps = {}) {
|
|
|
11262
11515
|
const url = loopbackVerificationUrl(started.verificationUrl, bind.target);
|
|
11263
11516
|
const attempt = await openBrowser(url, env);
|
|
11264
11517
|
if (attempt.opened) {
|
|
11265
|
-
|
|
11518
|
+
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
11519
|
`);
|
|
11267
11520
|
const startedAt = now();
|
|
11268
11521
|
const redirect = bind.waitForCallback({
|
|
11269
11522
|
timeoutMs: localDeadline(started) - startedAt,
|
|
11270
|
-
onStillWaiting: (elapsed) =>
|
|
11523
|
+
onStillWaiting: (elapsed) => write2(stillWaitingLine(elapsed, started)),
|
|
11271
11524
|
stillWaitingMs: deps.stillWaitingMs ?? STILL_WAITING_MS
|
|
11272
11525
|
});
|
|
11273
11526
|
const approved = await pollBesideLoopback(serviceUrl, started, redirect.then(() => void 0), deps);
|
|
11274
11527
|
await closeListener();
|
|
11275
|
-
|
|
11528
|
+
write2(`[halfcycle] Signed in \u2014 continuing.
|
|
11276
11529
|
`);
|
|
11277
11530
|
return {
|
|
11278
11531
|
accountId: approved.accountId,
|
|
@@ -11281,10 +11534,10 @@ async function signIn(serviceUrl, deps = {}) {
|
|
|
11281
11534
|
};
|
|
11282
11535
|
}
|
|
11283
11536
|
await closeListener();
|
|
11284
|
-
|
|
11537
|
+
write2(`[halfcycle] Falling back to the code-based sign-in: ${attempt.reason}.
|
|
11285
11538
|
`);
|
|
11286
11539
|
} else {
|
|
11287
|
-
|
|
11540
|
+
write2(`[halfcycle] Falling back to the code-based sign-in: ${bind.reason}.
|
|
11288
11541
|
`);
|
|
11289
11542
|
}
|
|
11290
11543
|
return await deviceCodeSignIn(serviceUrl, started, env, deps);
|
|
@@ -11293,21 +11546,21 @@ async function signIn(serviceUrl, deps = {}) {
|
|
|
11293
11546
|
}
|
|
11294
11547
|
}
|
|
11295
11548
|
async function deviceCodeSignIn(serviceUrl, started, env, deps) {
|
|
11296
|
-
const
|
|
11297
|
-
|
|
11549
|
+
const write2 = deps.write ?? defaultWrite;
|
|
11550
|
+
write2(`[halfcycle] Your sign-in code: ${started.userCode}
|
|
11298
11551
|
`);
|
|
11299
11552
|
const attempt = await openBrowser(started.verificationUrl, env);
|
|
11300
11553
|
if (attempt.opened) {
|
|
11301
|
-
|
|
11554
|
+
write2(`[halfcycle] Opened your browser at ${started.verificationUrl}
|
|
11302
11555
|
`);
|
|
11303
11556
|
} else {
|
|
11304
|
-
|
|
11557
|
+
write2(`[halfcycle] Open this address to sign in: ${started.verificationUrl}
|
|
11305
11558
|
`);
|
|
11306
11559
|
}
|
|
11307
|
-
|
|
11560
|
+
write2(`[halfcycle] Check the code on that page matches the one above, then sign in. This terminal is waiting.
|
|
11308
11561
|
`);
|
|
11309
11562
|
const approved = await pollForCredential(serviceUrl, started, deps);
|
|
11310
|
-
|
|
11563
|
+
write2(`[halfcycle] Signed in \u2014 continuing.
|
|
11311
11564
|
`);
|
|
11312
11565
|
return {
|
|
11313
11566
|
accountId: approved.accountId,
|
|
@@ -11947,40 +12200,40 @@ function identitySwitchRemedy(home, interactive) {
|
|
|
11947
12200
|
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
12201
|
}
|
|
11949
12202
|
async function confirmActingIdentity(serviceUrl, credential, act, deps = {}) {
|
|
11950
|
-
const
|
|
12203
|
+
const write2 = deps.write ?? ((text) => process.stdout.write(text));
|
|
11951
12204
|
const colour = deps.colour ?? decideColour({ isTTY: process.stdout.isTTY === true, env: deps.env ?? process.env });
|
|
11952
12205
|
const identity = await describeAccount(serviceUrl, credential.credential, deps.fetchImpl);
|
|
11953
12206
|
const plainLabel = accountLabel(identity, credential.accountId, deps.verbose === true);
|
|
11954
12207
|
const label = plainLabel !== void 0 ? ink("keyBold", plainLabel, colour) : "";
|
|
11955
12208
|
const acting = actingAs(credential, identity);
|
|
11956
12209
|
const script = armScript(credential, act, accountLine(identity, label), plainLabel, deps.home);
|
|
11957
|
-
|
|
12210
|
+
write2(script.preamble);
|
|
11958
12211
|
if (!script.asks)
|
|
11959
12212
|
return acting;
|
|
11960
12213
|
if (deps.assumeYes === true) {
|
|
11961
|
-
|
|
12214
|
+
write2(`[halfcycle] Continuing as that account without asking, because --yes was given.
|
|
11962
12215
|
`);
|
|
11963
12216
|
return acting;
|
|
11964
12217
|
}
|
|
11965
12218
|
const interactive = deps.interactive ?? isInteractiveRun(deps.env);
|
|
11966
12219
|
if (!interactive) {
|
|
11967
12220
|
if (script.noTerminalNote !== "")
|
|
11968
|
-
|
|
12221
|
+
write2(script.noTerminalNote);
|
|
11969
12222
|
return acting;
|
|
11970
12223
|
}
|
|
11971
12224
|
const ask = deps.ask ?? askLine;
|
|
11972
|
-
|
|
12225
|
+
write2("\n");
|
|
11973
12226
|
const decision = readDecision(await ask(question(colour)));
|
|
11974
12227
|
if (decision === "proceed")
|
|
11975
12228
|
return acting;
|
|
11976
12229
|
if (decision === "switch-account") {
|
|
11977
|
-
|
|
12230
|
+
write2(script.switchOpening);
|
|
11978
12231
|
const replacement = await switchAccount(serviceUrl, deps);
|
|
11979
12232
|
const replacementIdentity = await describeAccount(serviceUrl, replacement.credential, deps.fetchImpl);
|
|
11980
|
-
|
|
12233
|
+
write2(`[halfcycle] Signed in. This install will ${actClause(act)}.
|
|
11981
12234
|
`);
|
|
11982
12235
|
if (script.afterSwitch !== "")
|
|
11983
|
-
|
|
12236
|
+
write2(script.afterSwitch);
|
|
11984
12237
|
return actingAs(replacement, replacementIdentity);
|
|
11985
12238
|
}
|
|
11986
12239
|
throw new SignInRefused(IDENTITY_DECLINED, script.stopRemedy);
|
|
@@ -12091,8 +12344,8 @@ async function joinPinnedEngagement(serviceUrl, engagementId, targetRepo, deps =
|
|
|
12091
12344
|
|
|
12092
12345
|
// dist/pre-create-checks.js
|
|
12093
12346
|
import { spawnSync } from "node:child_process";
|
|
12094
|
-
import { existsSync as
|
|
12095
|
-
import { delimiter, dirname as dirname4, join as
|
|
12347
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7, realpathSync } from "node:fs";
|
|
12348
|
+
import { delimiter, dirname as dirname4, join as join8 } from "node:path";
|
|
12096
12349
|
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
12350
|
var PreCreateRefused = class extends Error {
|
|
12098
12351
|
constructor(message) {
|
|
@@ -12122,29 +12375,29 @@ var SYSTEM_FOLDERS = [
|
|
|
12122
12375
|
"C:\\Program Files",
|
|
12123
12376
|
"C:\\Program Files (x86)"
|
|
12124
12377
|
];
|
|
12125
|
-
function refuseSystemFolder(targetRepo,
|
|
12126
|
-
const key = (
|
|
12127
|
-
const real = realOrLiteral(
|
|
12128
|
-
return
|
|
12378
|
+
function refuseSystemFolder(targetRepo, platform6 = process.platform) {
|
|
12379
|
+
const key = (folder2) => {
|
|
12380
|
+
const real = realOrLiteral(folder2);
|
|
12381
|
+
return platform6 === "win32" ? real.toLowerCase() : real;
|
|
12129
12382
|
};
|
|
12130
12383
|
const target = key(targetRepo);
|
|
12131
|
-
if (SYSTEM_FOLDERS.some((
|
|
12384
|
+
if (SYSTEM_FOLDERS.some((folder2) => key(folder2) === target)) {
|
|
12132
12385
|
throw new PreCreateRefused(`${targetRepo} is a system folder \u2014 run "npx halfcycle" inside a project folder.`);
|
|
12133
12386
|
}
|
|
12134
12387
|
}
|
|
12135
12388
|
function refuseInsideProject(targetRepo) {
|
|
12136
|
-
let
|
|
12137
|
-
for (let parent = dirname4(
|
|
12138
|
-
if (
|
|
12389
|
+
let folder2 = realOrLiteral(targetRepo);
|
|
12390
|
+
for (let parent = dirname4(folder2); parent !== folder2; folder2 = parent, parent = dirname4(parent)) {
|
|
12391
|
+
if (existsSync6(join8(parent, BUNDLE_PIN_REL))) {
|
|
12139
12392
|
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
12393
|
}
|
|
12141
12394
|
}
|
|
12142
12395
|
}
|
|
12143
|
-
function realOrLiteral(
|
|
12396
|
+
function realOrLiteral(folder2) {
|
|
12144
12397
|
try {
|
|
12145
|
-
return realpathSync(
|
|
12398
|
+
return realpathSync(folder2);
|
|
12146
12399
|
} catch {
|
|
12147
|
-
return
|
|
12400
|
+
return folder2;
|
|
12148
12401
|
}
|
|
12149
12402
|
}
|
|
12150
12403
|
var GIT_INSTALL_ARGS = ["install", "--id", "Git.Git", "-e", "--source", "winget"];
|
|
@@ -12159,13 +12412,13 @@ async function offerGitForWindows() {
|
|
|
12159
12412
|
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
12413
|
}
|
|
12161
12414
|
function gitBashPath(env) {
|
|
12162
|
-
const onPath = (env["PATH"] ?? "").split(delimiter).filter((dir) => dir !== "" &&
|
|
12415
|
+
const onPath = (env["PATH"] ?? "").split(delimiter).filter((dir) => dir !== "" && existsSync6(join8(dir, "git.exe"))).map((dir) => join8(dir, "..", "bin", "bash.exe"));
|
|
12163
12416
|
const candidates = [
|
|
12164
12417
|
env["CLAUDE_CODE_GIT_BASH_PATH"],
|
|
12165
12418
|
...onPath,
|
|
12166
|
-
env["ProgramFiles"] === void 0 ? void 0 :
|
|
12419
|
+
env["ProgramFiles"] === void 0 ? void 0 : join8(env["ProgramFiles"], "Git", "bin", "bash.exe")
|
|
12167
12420
|
];
|
|
12168
|
-
return candidates.find((file) => file !== void 0 && file !== "" &&
|
|
12421
|
+
return candidates.find((file) => file !== void 0 && file !== "" && existsSync6(file));
|
|
12169
12422
|
}
|
|
12170
12423
|
function hasGitBash(env) {
|
|
12171
12424
|
return gitBashPath(env) !== void 0;
|
|
@@ -12174,7 +12427,7 @@ function runWingetInstall() {
|
|
|
12174
12427
|
return spawnSync("winget", [...GIT_INSTALL_ARGS], { stdio: "inherit" }).status === 0;
|
|
12175
12428
|
}
|
|
12176
12429
|
function unreadableProjectRefusal(targetRepo) {
|
|
12177
|
-
return `${
|
|
12430
|
+
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
12431
|
}
|
|
12179
12432
|
function cannotWriteRefusal(targetRepo) {
|
|
12180
12433
|
return `cannot write to ${targetRepo} \u2014 run "npx halfcycle" in a folder you can write to.`;
|
|
@@ -12194,7 +12447,7 @@ function claimFolderId(targetRepo) {
|
|
|
12194
12447
|
function readFolderId(targetRepo) {
|
|
12195
12448
|
let folderId;
|
|
12196
12449
|
try {
|
|
12197
|
-
const text = readFileSync7(
|
|
12450
|
+
const text = readFileSync7(join8(targetRepo, PROJECT_IDENTITY_REL), "utf-8");
|
|
12198
12451
|
folderId = JSON.parse(text)?.projectId;
|
|
12199
12452
|
} catch {
|
|
12200
12453
|
return void 0;
|
|
@@ -12212,7 +12465,7 @@ function checkSettingsParse(targetRepo) {
|
|
|
12212
12465
|
|
|
12213
12466
|
// dist/folder-refusals.js
|
|
12214
12467
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
12215
|
-
import { homedir as
|
|
12468
|
+
import { homedir as homedir4 } from "node:os";
|
|
12216
12469
|
function folderMissingRefusal(targetRepo) {
|
|
12217
12470
|
return `halfcycle: no such directory: ${targetRepo}`;
|
|
12218
12471
|
}
|
|
@@ -12232,7 +12485,7 @@ function homeFolderRefusal(targetRepo) {
|
|
|
12232
12485
|
function isHomeFolder(dir) {
|
|
12233
12486
|
try {
|
|
12234
12487
|
const target = realpathSync2(dir);
|
|
12235
|
-
const home = realpathSync2(
|
|
12488
|
+
const home = realpathSync2(homedir4());
|
|
12236
12489
|
return process.platform === "win32" ? target.toLowerCase() === home.toLowerCase() : target === home;
|
|
12237
12490
|
} catch {
|
|
12238
12491
|
return false;
|
|
@@ -12240,16 +12493,16 @@ function isHomeFolder(dir) {
|
|
|
12240
12493
|
}
|
|
12241
12494
|
|
|
12242
12495
|
// dist/preview.js
|
|
12243
|
-
import { accessSync, constants, existsSync as
|
|
12244
|
-
import { join as
|
|
12496
|
+
import { accessSync, constants, existsSync as existsSync7, readFileSync as readFileSync9, statSync } from "node:fs";
|
|
12497
|
+
import { join as join10 } from "node:path";
|
|
12245
12498
|
|
|
12246
12499
|
// dist/halfcycle-monorepo.js
|
|
12247
12500
|
import { readFileSync as readFileSync8 } from "node:fs";
|
|
12248
|
-
import { join as
|
|
12501
|
+
import { join as join9 } from "node:path";
|
|
12249
12502
|
var MONOREPO_PACKAGE_NAME = "halfcycle-monorepo";
|
|
12250
12503
|
function isHalfcycleMonorepo(dir) {
|
|
12251
12504
|
try {
|
|
12252
|
-
const pkg = JSON.parse(readFileSync8(
|
|
12505
|
+
const pkg = JSON.parse(readFileSync8(join9(dir, "package.json"), "utf-8"));
|
|
12253
12506
|
return pkg.name === MONOREPO_PACKAGE_NAME;
|
|
12254
12507
|
} catch {
|
|
12255
12508
|
return false;
|
|
@@ -12291,8 +12544,8 @@ function runPreview(targetRepo, options) {
|
|
|
12291
12544
|
};
|
|
12292
12545
|
}
|
|
12293
12546
|
function claudeMdLine(targetRepo) {
|
|
12294
|
-
const path =
|
|
12295
|
-
if (!
|
|
12547
|
+
const path = join10(targetRepo, CLAUDE_MD_REL);
|
|
12548
|
+
if (!existsSync7(path))
|
|
12296
12549
|
return void 0;
|
|
12297
12550
|
let text;
|
|
12298
12551
|
try {
|
|
@@ -12303,7 +12556,7 @@ function claudeMdLine(targetRepo) {
|
|
|
12303
12556
|
return text.includes(AGENTS_IMPORT_LINE) ? WORDS.claudeMdAlreadyPoints : WORDS.claudeMdWillAdd;
|
|
12304
12557
|
}
|
|
12305
12558
|
function previewRefusal(targetRepo, options) {
|
|
12306
|
-
if (!
|
|
12559
|
+
if (!existsSync7(targetRepo))
|
|
12307
12560
|
return folderMissingRefusal(targetRepo);
|
|
12308
12561
|
if (!statSync(targetRepo).isDirectory())
|
|
12309
12562
|
return folderIsFileRefusal(targetRepo);
|
|
@@ -12324,7 +12577,7 @@ function previewRefusal(targetRepo, options) {
|
|
|
12324
12577
|
} catch {
|
|
12325
12578
|
return cannotWriteRefusal(targetRepo);
|
|
12326
12579
|
}
|
|
12327
|
-
if (
|
|
12580
|
+
if (existsSync7(join10(targetRepo, PROJECT_IDENTITY_REL)) && readFolderId(targetRepo) === void 0) {
|
|
12328
12581
|
return unreadableProjectRefusal(targetRepo);
|
|
12329
12582
|
}
|
|
12330
12583
|
try {
|
|
@@ -12382,9 +12635,9 @@ function renderPreview(plan, missingGitForWindows, claudeMdWords) {
|
|
|
12382
12635
|
|
|
12383
12636
|
// dist/doctor.js
|
|
12384
12637
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
12385
|
-
import { existsSync as
|
|
12386
|
-
import { homedir as
|
|
12387
|
-
import { join as
|
|
12638
|
+
import { existsSync as existsSync8, readFileSync as readFileSync10, realpathSync as realpathSync3 } from "node:fs";
|
|
12639
|
+
import { homedir as homedir5 } from "node:os";
|
|
12640
|
+
import { join as join11 } from "node:path";
|
|
12388
12641
|
var GUARD_RUNNER_REL2 = ".claude/hooks/guard-runner.sh";
|
|
12389
12642
|
var CONNECTION_VERB = "check-connection";
|
|
12390
12643
|
var WRAPPER_TIMEOUT_MS = 3e4;
|
|
@@ -12406,16 +12659,17 @@ function runDoctor(root, options) {
|
|
|
12406
12659
|
const version = offline ? notChecked("Up to date", "Connected") : upToDate ? { ok: true, text: `Up to date: yes (${current}).` } : { ok: false, text: versionNoLine(installed, current) };
|
|
12407
12660
|
const lines = [setUp.line, connection.signedIn, connection.connected, checksRunning, version];
|
|
12408
12661
|
const failing = lines.filter((line) => !line.ok && !line.cascading).length;
|
|
12409
|
-
const
|
|
12410
|
-
const
|
|
12411
|
-
const
|
|
12662
|
+
const report = running.state === "answered" ? running.report : "none";
|
|
12663
|
+
const closing = failing === 0 ? closingWithNothingFailing(report) : `${failing} of these ${failing === 1 ? "needs" : "need"} fixing; each line above says what to do.`;
|
|
12664
|
+
const extras = [codexLine(root, options), reportsLine(report)].filter((line) => line !== void 0);
|
|
12665
|
+
const printed = [...lines.slice(0, 4), ...extras, ...lines.slice(4)];
|
|
12412
12666
|
return {
|
|
12413
12667
|
text: [...printed.map((line) => line.text), closing].join("\n"),
|
|
12414
12668
|
exitCode: failing === 0 ? 0 : 1
|
|
12415
12669
|
};
|
|
12416
12670
|
}
|
|
12417
12671
|
function setUpLine(root, upToDate) {
|
|
12418
|
-
if (!
|
|
12672
|
+
if (!existsSync8(join11(root, BUNDLE_PIN_REL))) {
|
|
12419
12673
|
return {
|
|
12420
12674
|
state: "no",
|
|
12421
12675
|
hooksMissing: false,
|
|
@@ -12429,9 +12683,9 @@ function setUpLine(root, upToDate) {
|
|
|
12429
12683
|
const missing = [];
|
|
12430
12684
|
if (!pinNamesProject(root))
|
|
12431
12685
|
missing.push("Halfcycle's project record");
|
|
12432
|
-
if (!
|
|
12686
|
+
if (!existsSync8(join11(root, INSTALL_MANIFEST_REL)))
|
|
12433
12687
|
missing.push("the install record");
|
|
12434
|
-
if (!
|
|
12688
|
+
if (!existsSync8(join11(root, VENDORED_BIN_REL)))
|
|
12435
12689
|
missing.push("Halfcycle's check program");
|
|
12436
12690
|
if (!mcpNamesServer(root))
|
|
12437
12691
|
missing.push("Halfcycle's server entry");
|
|
@@ -12457,25 +12711,25 @@ function pinNamesProject(root) {
|
|
|
12457
12711
|
}
|
|
12458
12712
|
}
|
|
12459
12713
|
function mcpNamesServer(root) {
|
|
12460
|
-
const parsed = readJson(
|
|
12461
|
-
if (!
|
|
12714
|
+
const parsed = readJson(join11(root, MCP_REGISTRATION_REL));
|
|
12715
|
+
if (!isObject2(parsed) || !isObject2(parsed["mcpServers"]))
|
|
12462
12716
|
return false;
|
|
12463
12717
|
const entry = parsed["mcpServers"][MCP_SERVER_KEY];
|
|
12464
|
-
return
|
|
12718
|
+
return isObject2(entry) && typeof entry["url"] === "string";
|
|
12465
12719
|
}
|
|
12466
12720
|
function settingsRegisterEveryCommand(root) {
|
|
12467
|
-
const parsed = readJson(
|
|
12468
|
-
if (!
|
|
12721
|
+
const parsed = readJson(join11(root, SETTINGS_REL));
|
|
12722
|
+
if (!isObject2(parsed) || !isObject2(parsed["hooks"]))
|
|
12469
12723
|
return false;
|
|
12470
12724
|
const registered = /* @__PURE__ */ new Set();
|
|
12471
12725
|
for (const groups of Object.values(parsed["hooks"])) {
|
|
12472
12726
|
if (!Array.isArray(groups))
|
|
12473
12727
|
continue;
|
|
12474
12728
|
for (const group of groups) {
|
|
12475
|
-
if (!
|
|
12729
|
+
if (!isObject2(group) || !Array.isArray(group["hooks"]))
|
|
12476
12730
|
continue;
|
|
12477
12731
|
for (const hook of group["hooks"]) {
|
|
12478
|
-
if (
|
|
12732
|
+
if (isObject2(hook) && typeof hook["command"] === "string")
|
|
12479
12733
|
registered.add(hook["command"]);
|
|
12480
12734
|
}
|
|
12481
12735
|
}
|
|
@@ -12495,8 +12749,8 @@ function startWrapper(root, options) {
|
|
|
12495
12749
|
}
|
|
12496
12750
|
shell = gitBash;
|
|
12497
12751
|
}
|
|
12498
|
-
const wrapper =
|
|
12499
|
-
if (!
|
|
12752
|
+
const wrapper = join11(root, GUARD_RUNNER_REL2);
|
|
12753
|
+
if (!existsSync8(wrapper))
|
|
12500
12754
|
return failed(repairSentence());
|
|
12501
12755
|
const run = spawnSync2(shell, [wrapper, CONNECTION_VERB], {
|
|
12502
12756
|
cwd: root,
|
|
@@ -12523,8 +12777,8 @@ function startWrapper(root, options) {
|
|
|
12523
12777
|
}
|
|
12524
12778
|
function settingsDisablingHooks(root) {
|
|
12525
12779
|
for (const rel of [SETTINGS_REL, LOCAL_SETTINGS_REL]) {
|
|
12526
|
-
const parsed = readJson(
|
|
12527
|
-
if (
|
|
12780
|
+
const parsed = readJson(join11(root, rel));
|
|
12781
|
+
if (isObject2(parsed) && parsed["disableAllHooks"] === true)
|
|
12528
12782
|
return rel;
|
|
12529
12783
|
}
|
|
12530
12784
|
return void 0;
|
|
@@ -12539,14 +12793,16 @@ function parseAnswer(stdout) {
|
|
|
12539
12793
|
} catch {
|
|
12540
12794
|
return void 0;
|
|
12541
12795
|
}
|
|
12542
|
-
if (!
|
|
12796
|
+
if (!isObject2(parsed) || typeof parsed["kind"] !== "string" || !KNOWN_KINDS.includes(parsed["kind"]))
|
|
12543
12797
|
return void 0;
|
|
12544
12798
|
const sentence = parsed["sentence"];
|
|
12545
12799
|
if (sentence !== null && typeof sentence !== "string")
|
|
12546
12800
|
return void 0;
|
|
12547
12801
|
if (parsed["kind"] !== "not-registered" && typeof sentence !== "string")
|
|
12548
12802
|
return void 0;
|
|
12549
|
-
|
|
12803
|
+
const said = parsed["report"];
|
|
12804
|
+
const report = parsed["kind"] === "connected" && (said === "no-report-yet" || said === "gone-quiet") ? said : "none";
|
|
12805
|
+
return { kind: parsed["kind"], sentence, report };
|
|
12550
12806
|
}
|
|
12551
12807
|
function checksRunningLine(running, hooksMissing) {
|
|
12552
12808
|
if (running.state === "not-run")
|
|
@@ -12587,6 +12843,17 @@ function connectionLines(running) {
|
|
|
12587
12843
|
connected: { ok: false, text: `Connected: no. ${sentence ?? ""}`.trimEnd() }
|
|
12588
12844
|
};
|
|
12589
12845
|
}
|
|
12846
|
+
function reportsLine(report) {
|
|
12847
|
+
if (report === "no-report-yet") {
|
|
12848
|
+
return { ok: true, text: `Reports: ${CHECKS_NO_REPORT_YET_STATE}. ${CHECKS_NO_REPORT_YET_WHEN} ${CHECKS_IF_STILL_UNREPORTED}` };
|
|
12849
|
+
}
|
|
12850
|
+
return report === "gone-quiet" ? { ok: true, text: `Reports: ${CHECKS_GONE_QUIET}` } : void 0;
|
|
12851
|
+
}
|
|
12852
|
+
function closingWithNothingFailing(report) {
|
|
12853
|
+
if (report === "no-report-yet")
|
|
12854
|
+
return `${CHECKS_NO_REPORT_YET_STATE}.`;
|
|
12855
|
+
return report === "gone-quiet" ? CHECKS_GONE_QUIET : "Everything is working.";
|
|
12856
|
+
}
|
|
12590
12857
|
function installedVersion(root) {
|
|
12591
12858
|
try {
|
|
12592
12859
|
return checkDrift(root).installed;
|
|
@@ -12610,8 +12877,8 @@ var CODEX_EVENT_SNAKE = {
|
|
|
12610
12877
|
SessionEnd: "session_end"
|
|
12611
12878
|
};
|
|
12612
12879
|
function codexLine(root, options) {
|
|
12613
|
-
const hooksPath =
|
|
12614
|
-
if (!
|
|
12880
|
+
const hooksPath = join11(root, CODEX_HOOKS_REL2);
|
|
12881
|
+
if (!existsSync8(hooksPath))
|
|
12615
12882
|
return void 0;
|
|
12616
12883
|
const positions = codexEntryPositions(hooksPath);
|
|
12617
12884
|
if (positions === void 0) {
|
|
@@ -12624,7 +12891,7 @@ function codexLine(root, options) {
|
|
|
12624
12891
|
}
|
|
12625
12892
|
function codexEntryPositions(hooksPath) {
|
|
12626
12893
|
const parsed = readJson(hooksPath);
|
|
12627
|
-
if (!
|
|
12894
|
+
if (!isObject2(parsed) || !isObject2(parsed["hooks"]))
|
|
12628
12895
|
return void 0;
|
|
12629
12896
|
const positions = [];
|
|
12630
12897
|
for (const entry of AGENT_PROFILES.codex.wiring) {
|
|
@@ -12634,11 +12901,11 @@ function codexEntryPositions(hooksPath) {
|
|
|
12634
12901
|
let found;
|
|
12635
12902
|
for (let g = 0; g < groups.length && found === void 0; g += 1) {
|
|
12636
12903
|
const group = groups[g];
|
|
12637
|
-
if (!
|
|
12904
|
+
if (!isObject2(group) || !Array.isArray(group["hooks"]))
|
|
12638
12905
|
continue;
|
|
12639
12906
|
for (let h = 0; h < group["hooks"].length; h += 1) {
|
|
12640
12907
|
const hook = group["hooks"][h];
|
|
12641
|
-
if (
|
|
12908
|
+
if (isObject2(hook) && hook["command"] === entry.command) {
|
|
12642
12909
|
found = { group: g, hook: h };
|
|
12643
12910
|
break;
|
|
12644
12911
|
}
|
|
@@ -12652,7 +12919,7 @@ function codexEntryPositions(hooksPath) {
|
|
|
12652
12919
|
}
|
|
12653
12920
|
function codexApprovalStatus(root, positions, options) {
|
|
12654
12921
|
const codexHome = options.env["CODEX_HOME"];
|
|
12655
|
-
const configPath =
|
|
12922
|
+
const configPath = join11(codexHome !== void 0 && codexHome !== "" ? codexHome : join11(homedir5(), ".codex"), "config.toml");
|
|
12656
12923
|
let text;
|
|
12657
12924
|
try {
|
|
12658
12925
|
text = readFileSync10(configPath, "utf-8");
|
|
@@ -12661,7 +12928,7 @@ function codexApprovalStatus(root, positions, options) {
|
|
|
12661
12928
|
}
|
|
12662
12929
|
let hooksPath;
|
|
12663
12930
|
try {
|
|
12664
|
-
hooksPath =
|
|
12931
|
+
hooksPath = join11(realpathSync3(root), CODEX_HOOKS_REL2);
|
|
12665
12932
|
} catch {
|
|
12666
12933
|
return "not-approved";
|
|
12667
12934
|
}
|
|
@@ -12691,7 +12958,7 @@ function naturalList(items) {
|
|
|
12691
12958
|
return `${items.slice(0, -1).join(", ")} and ${items[items.length - 1]}`;
|
|
12692
12959
|
}
|
|
12693
12960
|
function readJson(path) {
|
|
12694
|
-
if (!
|
|
12961
|
+
if (!existsSync8(path))
|
|
12695
12962
|
return void 0;
|
|
12696
12963
|
try {
|
|
12697
12964
|
return JSON.parse(readFileSync10(path, "utf-8"));
|
|
@@ -12699,7 +12966,7 @@ function readJson(path) {
|
|
|
12699
12966
|
return void 0;
|
|
12700
12967
|
}
|
|
12701
12968
|
}
|
|
12702
|
-
function
|
|
12969
|
+
function isObject2(value) {
|
|
12703
12970
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12704
12971
|
}
|
|
12705
12972
|
|
|
@@ -12757,13 +13024,13 @@ async function probeMcpOrigin(mcpUrl, token) {
|
|
|
12757
13024
|
|
|
12758
13025
|
// dist/claude-version-probe.js
|
|
12759
13026
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
12760
|
-
import { existsSync as
|
|
12761
|
-
import { delimiter as delimiter2, join as
|
|
13027
|
+
import { existsSync as existsSync9, realpathSync as realpathSync4 } from "node:fs";
|
|
13028
|
+
import { delimiter as delimiter2, join as join12 } from "node:path";
|
|
12762
13029
|
function execVersionCommand(command, timeoutMs, deps) {
|
|
12763
|
-
const
|
|
13030
|
+
const platform6 = deps.platform ?? process.platform;
|
|
12764
13031
|
const exec = deps.exec ?? execFileSync3;
|
|
12765
13032
|
try {
|
|
12766
|
-
return
|
|
13033
|
+
return platform6 === "win32" ? exec("cmd.exe", ["/d", "/s", "/c", `"${command} --version"`], {
|
|
12767
13034
|
encoding: "utf-8",
|
|
12768
13035
|
timeout: timeoutMs,
|
|
12769
13036
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -12800,8 +13067,8 @@ var INSTALL_METHOD_RULES = [
|
|
|
12800
13067
|
];
|
|
12801
13068
|
function probeClaudeCodeInstallMethod(deps = {}) {
|
|
12802
13069
|
const env = deps.env ?? process.env;
|
|
12803
|
-
const
|
|
12804
|
-
const exists = deps.exists ??
|
|
13070
|
+
const platform6 = deps.platform ?? process.platform;
|
|
13071
|
+
const exists = deps.exists ?? existsSync9;
|
|
12805
13072
|
const realpath = deps.realpath ?? ((file) => {
|
|
12806
13073
|
try {
|
|
12807
13074
|
return realpathSync4(file);
|
|
@@ -12809,11 +13076,11 @@ function probeClaudeCodeInstallMethod(deps = {}) {
|
|
|
12809
13076
|
return file;
|
|
12810
13077
|
}
|
|
12811
13078
|
});
|
|
12812
|
-
const names =
|
|
13079
|
+
const names = platform6 === "win32" ? ["claude.exe", "claude.cmd"] : ["claude"];
|
|
12813
13080
|
const dirs = (env["PATH"] ?? "").split(delimiter2).filter((dir) => dir !== "");
|
|
12814
13081
|
for (const dir of dirs) {
|
|
12815
13082
|
for (const name of names) {
|
|
12816
|
-
const file =
|
|
13083
|
+
const file = join12(dir, name);
|
|
12817
13084
|
if (!exists(file))
|
|
12818
13085
|
continue;
|
|
12819
13086
|
const real = realpath(file);
|
|
@@ -12848,8 +13115,8 @@ function codexInstallLine(codex) {
|
|
|
12848
13115
|
}
|
|
12849
13116
|
|
|
12850
13117
|
// dist/build-record/write.js
|
|
12851
|
-
import { existsSync as
|
|
12852
|
-
import { basename as basename2, join as
|
|
13118
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, rmSync as rmSync4 } from "node:fs";
|
|
13119
|
+
import { basename as basename2, join as join13, resolve as resolve3, relative as relative2, isAbsolute as isAbsolute3 } from "node:path";
|
|
12853
13120
|
|
|
12854
13121
|
// dist/build-record/render-v2.js
|
|
12855
13122
|
var HIDDEN = /[\p{Cc}\p{Zl}\p{Zp}\p{Default_Ignorable_Code_Point}]/u;
|
|
@@ -13498,7 +13765,7 @@ function replaceOne(io, tmp, finalPath, text) {
|
|
|
13498
13765
|
}
|
|
13499
13766
|
|
|
13500
13767
|
// dist/build-record/write.js
|
|
13501
|
-
var BUILD_RECORD_DIR =
|
|
13768
|
+
var BUILD_RECORD_DIR = join13("docs", "build-records");
|
|
13502
13769
|
function assertInsideOutDir(outDir, candidate, identity) {
|
|
13503
13770
|
const rel = relative2(outDir, resolve3(candidate));
|
|
13504
13771
|
const isPlainFilename = rel !== "" && rel !== ".." && !isAbsolute3(rel) && rel === basename2(rel);
|
|
@@ -13536,8 +13803,8 @@ function writeRecordPair(repoRoot, phase, rawRecord, record2, io = NODE_RECORD_P
|
|
|
13536
13803
|
}
|
|
13537
13804
|
const outDir = resolve3(repoRoot, BUILD_RECORD_DIR);
|
|
13538
13805
|
const segment = renderPhaseSegment(phase);
|
|
13539
|
-
const jsonPath =
|
|
13540
|
-
const mdPath =
|
|
13806
|
+
const jsonPath = join13(outDir, `${segment}.json`);
|
|
13807
|
+
const mdPath = join13(outDir, `${segment}.md`);
|
|
13541
13808
|
const jsonTmp = `${jsonPath}.tmp`;
|
|
13542
13809
|
const mdTmp = `${mdPath}.tmp`;
|
|
13543
13810
|
for (const candidate of [jsonPath, mdPath, jsonTmp, mdTmp]) {
|
|
@@ -13551,7 +13818,7 @@ function writeRecordPair(repoRoot, phase, rawRecord, record2, io = NODE_RECORD_P
|
|
|
13551
13818
|
}
|
|
13552
13819
|
const existingJson = readIfPresent(jsonPath);
|
|
13553
13820
|
if (existingJson === null) {
|
|
13554
|
-
return { kind: "in-the-way", path:
|
|
13821
|
+
return { kind: "in-the-way", path: existsSync10(jsonPath) ? jsonPath : mdPath };
|
|
13555
13822
|
}
|
|
13556
13823
|
if (existingJson === jsonText) {
|
|
13557
13824
|
replaceOne(io, mdTmp, mdPath, mdText);
|
|
@@ -13579,20 +13846,20 @@ import { relative as relative5 } from "node:path";
|
|
|
13579
13846
|
|
|
13580
13847
|
// dist/open-phase.js
|
|
13581
13848
|
import { readFileSync as readFileSync17 } from "node:fs";
|
|
13582
|
-
import { join as
|
|
13849
|
+
import { join as join21 } from "node:path";
|
|
13583
13850
|
|
|
13584
13851
|
// dist/close-phase.js
|
|
13585
13852
|
import { execFile, execFileSync as execFileSync5 } from "node:child_process";
|
|
13586
13853
|
import { readFileSync as readFileSync16 } from "node:fs";
|
|
13587
|
-
import { join as
|
|
13854
|
+
import { join as join20 } from "node:path";
|
|
13588
13855
|
|
|
13589
13856
|
// dist/plan.js
|
|
13590
|
-
import { existsSync as
|
|
13591
|
-
import { join as
|
|
13857
|
+
import { existsSync as existsSync12, readFileSync as readFileSync15 } from "node:fs";
|
|
13858
|
+
import { join as join19 } from "node:path";
|
|
13592
13859
|
|
|
13593
13860
|
// dist/draw/write.js
|
|
13594
13861
|
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
|
|
13862
|
+
import { dirname as dirname5, isAbsolute as isAbsolute4, join as join14, relative as relative3, resolve as resolve4, sep } from "node:path";
|
|
13596
13863
|
var DOCS_DIR = "docs";
|
|
13597
13864
|
var DIAGRAMS_DIR = "diagrams";
|
|
13598
13865
|
var DIAGRAM_DIR_REL = `${DOCS_DIR}/${DIAGRAMS_DIR}`;
|
|
@@ -13648,9 +13915,9 @@ function embedImageLink(base, doc) {
|
|
|
13648
13915
|
}
|
|
13649
13916
|
function diagramPaths(repoRoot, base) {
|
|
13650
13917
|
const root = resolve4(repoRoot);
|
|
13651
|
-
const dir =
|
|
13918
|
+
const dir = join14(root, DOCS_DIR, DIAGRAMS_DIR);
|
|
13652
13919
|
const names = fileNames(base);
|
|
13653
|
-
const file = Object.fromEntries(DIAGRAM_FILES.map((which) => [which,
|
|
13920
|
+
const file = Object.fromEntries(DIAGRAM_FILES.map((which) => [which, join14(dir, names[which])]));
|
|
13654
13921
|
return { repoRoot: root, dir, file };
|
|
13655
13922
|
}
|
|
13656
13923
|
function diagramRel(base, which) {
|
|
@@ -13923,10 +14190,10 @@ function isMissingRoute(method, status, body) {
|
|
|
13923
14190
|
}
|
|
13924
14191
|
|
|
13925
14192
|
// dist/change.js
|
|
13926
|
-
import { join as
|
|
14193
|
+
import { join as join17 } from "node:path";
|
|
13927
14194
|
|
|
13928
14195
|
// 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"];
|
|
14196
|
+
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
14197
|
var CONTRACTS = [
|
|
13931
14198
|
{ verb: "install", required: [], conditional: [] },
|
|
13932
14199
|
{ verb: "check-drift", required: [], conditional: [] },
|
|
@@ -13945,6 +14212,17 @@ var CONTRACTS = [
|
|
|
13945
14212
|
{ verb: "preview", required: [], conditional: [] },
|
|
13946
14213
|
{ verb: "doctor", required: [], conditional: [] },
|
|
13947
14214
|
{ verb: "restore", required: [], conditional: [] },
|
|
14215
|
+
{
|
|
14216
|
+
verb: "request-shapes",
|
|
14217
|
+
required: [],
|
|
14218
|
+
conditional: [],
|
|
14219
|
+
forms: [
|
|
14220
|
+
{ form: "on", required: [], conditional: [] },
|
|
14221
|
+
{ form: "off", required: [], conditional: [] },
|
|
14222
|
+
{ form: "show", required: [], conditional: [] },
|
|
14223
|
+
{ form: "clear", required: [], conditional: [] }
|
|
14224
|
+
]
|
|
14225
|
+
},
|
|
13948
14226
|
{
|
|
13949
14227
|
verb: "check-report",
|
|
13950
14228
|
required: [
|
|
@@ -14136,11 +14414,11 @@ function placeholderRefusal(verb, tokens) {
|
|
|
14136
14414
|
// dist/system-report.js
|
|
14137
14415
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
14138
14416
|
import { readFileSync as readFileSync13 } from "node:fs";
|
|
14139
|
-
import { join as
|
|
14417
|
+
import { join as join15 } from "node:path";
|
|
14140
14418
|
function readDescriptionFile(repoRoot) {
|
|
14141
14419
|
let text;
|
|
14142
14420
|
try {
|
|
14143
|
-
text = readFileSync13(
|
|
14421
|
+
text = readFileSync13(join15(repoRoot, SYSTEM_DESCRIPTION_REL), "utf-8");
|
|
14144
14422
|
} catch (err) {
|
|
14145
14423
|
const code = err.code;
|
|
14146
14424
|
return code === "ENOENT" || code === "ENOTDIR" ? { kind: "missing" } : { kind: "unreadable" };
|
|
@@ -14364,8 +14642,8 @@ async function runSystem(repoRoot, deps) {
|
|
|
14364
14642
|
}
|
|
14365
14643
|
|
|
14366
14644
|
// dist/build-record/write-change.js
|
|
14367
|
-
import { existsSync as
|
|
14368
|
-
import { basename as basename3, isAbsolute as isAbsolute5, join as
|
|
14645
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync8 } from "node:fs";
|
|
14646
|
+
import { basename as basename3, isAbsolute as isAbsolute5, join as join16, relative as relative4, resolve as resolve5 } from "node:path";
|
|
14369
14647
|
|
|
14370
14648
|
// dist/build-record/render-change.js
|
|
14371
14649
|
function counted2(n, one, many) {
|
|
@@ -14510,7 +14788,7 @@ function renderChangeRecordMarkdown(record2) {
|
|
|
14510
14788
|
}
|
|
14511
14789
|
|
|
14512
14790
|
// dist/build-record/write-change.js
|
|
14513
|
-
var CHANGE_RECORD_DIR =
|
|
14791
|
+
var CHANGE_RECORD_DIR = join16("docs", "build-records", "changes");
|
|
14514
14792
|
var InvalidChangeIdError = class extends Error {
|
|
14515
14793
|
id;
|
|
14516
14794
|
constructor(id) {
|
|
@@ -14530,8 +14808,8 @@ function writeChangeRecordPair(repoRoot, sortingId, rawRecord, record2, io = NOD
|
|
|
14530
14808
|
if (!checked.success)
|
|
14531
14809
|
throw new InvalidChangeIdError(sortingId);
|
|
14532
14810
|
const outDir = resolve5(repoRoot, CHANGE_RECORD_DIR);
|
|
14533
|
-
const jsonPath =
|
|
14534
|
-
const mdPath =
|
|
14811
|
+
const jsonPath = join16(outDir, `${checked.data}.json`);
|
|
14812
|
+
const mdPath = join16(outDir, `${checked.data}.md`);
|
|
14535
14813
|
const mdTmp = `${mdPath}.tmp`;
|
|
14536
14814
|
for (const candidate of [jsonPath, mdPath, mdTmp]) {
|
|
14537
14815
|
assertInsideChangeDir(outDir, candidate, sortingId);
|
|
@@ -14544,7 +14822,7 @@ function writeChangeRecordPair(repoRoot, sortingId, rawRecord, record2, io = NOD
|
|
|
14544
14822
|
}
|
|
14545
14823
|
const existingJson = readIfPresent(jsonPath);
|
|
14546
14824
|
if (existingJson === null) {
|
|
14547
|
-
return { kind: "in-the-way", path:
|
|
14825
|
+
return { kind: "in-the-way", path: existsSync11(jsonPath) ? jsonPath : mdPath };
|
|
14548
14826
|
}
|
|
14549
14827
|
if (existingJson !== jsonText)
|
|
14550
14828
|
return { kind: "in-the-way", path: jsonPath };
|
|
@@ -14807,7 +15085,7 @@ function writePairLines(deps, repoRoot, sortingId, found) {
|
|
|
14807
15085
|
};
|
|
14808
15086
|
}
|
|
14809
15087
|
return {
|
|
14810
|
-
stdout: `[halfcycle] Its record is in ${
|
|
15088
|
+
stdout: `[halfcycle] Its record is in ${join17(CHANGE_RECORD_DIR, `${sortingId}.md`)}.
|
|
14811
15089
|
`,
|
|
14812
15090
|
stderr: "",
|
|
14813
15091
|
ok: true
|
|
@@ -15048,12 +15326,12 @@ function runChange(repoRoot, args2, deps) {
|
|
|
15048
15326
|
// dist/shown-record.js
|
|
15049
15327
|
import { chmodSync as chmodSync3, mkdirSync as mkdirSync9, readFileSync as readFileSync14, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync6 } from "node:fs";
|
|
15050
15328
|
import { platform as platform4 } from "node:os";
|
|
15051
|
-
import { join as
|
|
15329
|
+
import { join as join18 } from "node:path";
|
|
15052
15330
|
function updateShownRecord(engagementId, update, home) {
|
|
15053
15331
|
let temp = null;
|
|
15054
15332
|
try {
|
|
15055
15333
|
const dir = engagementStateDir(engagementId, home);
|
|
15056
|
-
const path =
|
|
15334
|
+
const path = join18(dir, SHOWN_PLANS_FILENAME);
|
|
15057
15335
|
let current;
|
|
15058
15336
|
try {
|
|
15059
15337
|
current = parseShownRecord(readFileSync14(path, "utf-8"));
|
|
@@ -15063,7 +15341,7 @@ function updateShownRecord(engagementId, update, home) {
|
|
|
15063
15341
|
const next = update(current);
|
|
15064
15342
|
const body = serialiseShownRecord(next);
|
|
15065
15343
|
mkdirSync9(dir, { recursive: true, mode: 448 });
|
|
15066
|
-
temp =
|
|
15344
|
+
temp = join18(dir, `.${SHOWN_PLANS_FILENAME}.${process.pid}.tmp`);
|
|
15067
15345
|
writeFileSync6(temp, body, { mode: 384 });
|
|
15068
15346
|
if (platform4() !== "win32")
|
|
15069
15347
|
chmodSync3(temp, 384);
|
|
@@ -15140,7 +15418,7 @@ function readAndCheckPlan(repoRoot, phase, description, verb) {
|
|
|
15140
15418
|
const fileRel = planFileRel(phase);
|
|
15141
15419
|
let text;
|
|
15142
15420
|
try {
|
|
15143
|
-
text = readFileSync15(
|
|
15421
|
+
text = readFileSync15(join19(repoRoot, fileRel), "utf-8");
|
|
15144
15422
|
} catch (err) {
|
|
15145
15423
|
const code = err.code;
|
|
15146
15424
|
if (code === "ENOENT" || code === "ENOTDIR") {
|
|
@@ -15296,7 +15574,7 @@ function removalOwedLines(verb, parts) {
|
|
|
15296
15574
|
function redrawLine(repoRoot, phase) {
|
|
15297
15575
|
let exists;
|
|
15298
15576
|
try {
|
|
15299
|
-
exists =
|
|
15577
|
+
exists = existsSync12(join19(repoRoot, DIAGRAM_DIR_REL, embedImageName(phaseDiagramBase(phase))));
|
|
15300
15578
|
} catch {
|
|
15301
15579
|
return null;
|
|
15302
15580
|
}
|
|
@@ -15917,7 +16195,7 @@ function readTimeLine(stdout) {
|
|
|
15917
16195
|
}
|
|
15918
16196
|
}
|
|
15919
16197
|
function runTimeChild(binary, credential, repoRoot) {
|
|
15920
|
-
return runRunnerChild(binary, ["time"], credential, repoRoot, readTimeLine, TIME_UNREACHABLE);
|
|
16198
|
+
return runRunnerChild(binary, ["time", "--final"], credential, repoRoot, readTimeLine, TIME_UNREACHABLE);
|
|
15921
16199
|
}
|
|
15922
16200
|
function runProveQueueChild(binary, credential, repoRoot) {
|
|
15923
16201
|
return runRunnerChild(binary, ["prove", "--send-queued"], credential, repoRoot, () => void 0, void 0);
|
|
@@ -16243,7 +16521,7 @@ var PHASE_MODELS_FILENAME = "phase-models.json";
|
|
|
16243
16521
|
var PACING_FOLDER = "pacing";
|
|
16244
16522
|
function pacingFile(engagementId, name, home) {
|
|
16245
16523
|
try {
|
|
16246
|
-
return
|
|
16524
|
+
return join20(engagementStateDir(engagementId, home), PACING_FOLDER, name);
|
|
16247
16525
|
} catch {
|
|
16248
16526
|
return null;
|
|
16249
16527
|
}
|
|
@@ -16397,7 +16675,7 @@ function readRepoCredential(repoRoot, env = process.env, home) {
|
|
|
16397
16675
|
if (pinned !== null)
|
|
16398
16676
|
assertEngagementId(pinned);
|
|
16399
16677
|
const fromStore = pinned === null ? null : readEngagementEnv(pinned, home);
|
|
16400
|
-
const looked = pinned === null ? `${
|
|
16678
|
+
const looked = pinned === null ? `${join21(repoRoot, ".halfcycle", "bundle.json")} (which names no engagement)` : engagementEnvPath(pinned, home);
|
|
16401
16679
|
const values = {};
|
|
16402
16680
|
for (const key of OPEN_PHASE_ENV_KEYS) {
|
|
16403
16681
|
const value = (env[key] ?? fromStore?.[key] ?? "").trim();
|
|
@@ -16414,7 +16692,7 @@ function readRepoCredential(repoRoot, env = process.env, home) {
|
|
|
16414
16692
|
}
|
|
16415
16693
|
function readPinnedEngagementId(repoRoot) {
|
|
16416
16694
|
try {
|
|
16417
|
-
const pin = JSON.parse(readFileSync17(
|
|
16695
|
+
const pin = JSON.parse(readFileSync17(join21(repoRoot, ".halfcycle", "bundle.json"), "utf-8"));
|
|
16418
16696
|
const id = pin[PIN_ENGAGEMENT_ID_FIELD];
|
|
16419
16697
|
return typeof id === "string" && id !== "" ? id : null;
|
|
16420
16698
|
} catch {
|
|
@@ -16895,7 +17173,7 @@ async function writeDrawOutcome(repoRoot, deps, outcome, target, warnings, canno
|
|
|
16895
17173
|
|
|
16896
17174
|
// dist/check-tasks.js
|
|
16897
17175
|
import { readFileSync as readFileSync18 } from "node:fs";
|
|
16898
|
-
import { join as
|
|
17176
|
+
import { join as join22, resolve as resolve6 } from "node:path";
|
|
16899
17177
|
var CHECK_TASKS_USAGE = `${HALFCYCLE_COMMAND} check-tasks <phase> [--with-task-list] [--repo <root>]`;
|
|
16900
17178
|
function parseCheckTasksArgs(argv) {
|
|
16901
17179
|
const positionals = [];
|
|
@@ -16993,7 +17271,7 @@ function readPhasePlanParts(repoRoot, phase, description) {
|
|
|
16993
17271
|
const file = planFileRel(phase);
|
|
16994
17272
|
let text;
|
|
16995
17273
|
try {
|
|
16996
|
-
text = readFileSync18(
|
|
17274
|
+
text = readFileSync18(join22(repoRoot, file), "utf-8");
|
|
16997
17275
|
} catch (err) {
|
|
16998
17276
|
const code = err.code;
|
|
16999
17277
|
if (code === "ENOENT" || code === "ENOTDIR")
|
|
@@ -17026,7 +17304,7 @@ function readPlanSpecFiles(repoRoot, plan) {
|
|
|
17026
17304
|
const rel = `${SPEC_FOLDER}/${slug}.md`;
|
|
17027
17305
|
let text;
|
|
17028
17306
|
try {
|
|
17029
|
-
text = readFileSync18(
|
|
17307
|
+
text = readFileSync18(join22(repoRoot, rel), "utf-8");
|
|
17030
17308
|
} catch (err) {
|
|
17031
17309
|
const code = err.code;
|
|
17032
17310
|
if (code === "ENOENT" || code === "ENOTDIR") {
|
|
@@ -17417,8 +17695,8 @@ function runProveChild(binary, credential, repoRoot, words, deps = {}) {
|
|
|
17417
17695
|
|
|
17418
17696
|
// dist/uninstall.js
|
|
17419
17697
|
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
|
|
17698
|
+
import { homedir as homedir6 } from "node:os";
|
|
17699
|
+
import { dirname as dirname6, join as join23, relative as relative6, sep as sep2 } from "node:path";
|
|
17422
17700
|
var REMOVE_CREDENTIAL_FLAG = "--remove-credential";
|
|
17423
17701
|
function parseUninstallArgs(args2) {
|
|
17424
17702
|
let removeCredential = false;
|
|
@@ -17453,7 +17731,7 @@ function readMergedJson(targetRepo, rule) {
|
|
|
17453
17731
|
const { rel } = rule;
|
|
17454
17732
|
if (symlinkOnPath(targetRepo, rel))
|
|
17455
17733
|
return { state: "symlink" };
|
|
17456
|
-
const path =
|
|
17734
|
+
const path = join23(targetRepo, rel);
|
|
17457
17735
|
if (!lexists(path))
|
|
17458
17736
|
return { state: "absent" };
|
|
17459
17737
|
let text;
|
|
@@ -17476,8 +17754,8 @@ function readMergedJson(targetRepo, rule) {
|
|
|
17476
17754
|
}
|
|
17477
17755
|
function validate(options, rules) {
|
|
17478
17756
|
const { targetRepo, removeCredential, home } = options;
|
|
17479
|
-
const manifestPath =
|
|
17480
|
-
const pinPath =
|
|
17757
|
+
const manifestPath = join23(targetRepo, INSTALL_MANIFEST_REL);
|
|
17758
|
+
const pinPath = join23(targetRepo, BUNDLE_PIN_REL);
|
|
17481
17759
|
if (symlinkOnPath(targetRepo, INSTALL_MANIFEST_REL) || symlinkOnPath(targetRepo, BUNDLE_PIN_REL)) {
|
|
17482
17760
|
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
17761
|
}
|
|
@@ -17518,7 +17796,7 @@ function validate(options, rules) {
|
|
|
17518
17796
|
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
17797
|
}
|
|
17520
17798
|
if (engagementId !== void 0) {
|
|
17521
|
-
const expectedParent =
|
|
17799
|
+
const expectedParent = join23(home ?? homedir6(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME);
|
|
17522
17800
|
if (dirname6(engagementStateDir(engagementId, home)) !== expectedParent) {
|
|
17523
17801
|
throw new Refusal(`the project id in ${BUNDLE_PIN_REL} does not name a folder under ~/.halfcycle/engagements. Nothing was removed.`);
|
|
17524
17802
|
}
|
|
@@ -17544,7 +17822,7 @@ function mergedFileRules(writeSet) {
|
|
|
17544
17822
|
function writeBack(targetRepo, rel, current, next, lists, name) {
|
|
17545
17823
|
if (next === current)
|
|
17546
17824
|
return;
|
|
17547
|
-
const path =
|
|
17825
|
+
const path = join23(targetRepo, rel);
|
|
17548
17826
|
try {
|
|
17549
17827
|
if (next === null) {
|
|
17550
17828
|
rmSync6(path);
|
|
@@ -17561,7 +17839,7 @@ function statusLineClaimedFromTree(targetRepo, writeSet) {
|
|
|
17561
17839
|
try {
|
|
17562
17840
|
if (symlinkOnPath(targetRepo, SETTINGS_REL))
|
|
17563
17841
|
return {};
|
|
17564
|
-
const value = JSON.parse(readFileSync20(
|
|
17842
|
+
const value = JSON.parse(readFileSync20(join23(targetRepo, SETTINGS_REL), "utf-8"));
|
|
17565
17843
|
if (!isPlainObject3(value) || !("statusLine" in value))
|
|
17566
17844
|
return {};
|
|
17567
17845
|
const hash = canonicalSha256(value["statusLine"]);
|
|
@@ -17575,7 +17853,7 @@ function noRecord(targetRepo, writeSet) {
|
|
|
17575
17853
|
const claim = (rel, header, lines) => {
|
|
17576
17854
|
let text;
|
|
17577
17855
|
try {
|
|
17578
|
-
text = symlinkOnPath(targetRepo, rel) ? "" : readFileSync20(
|
|
17856
|
+
text = symlinkOnPath(targetRepo, rel) ? "" : readFileSync20(join23(targetRepo, rel), "utf-8");
|
|
17579
17857
|
} catch {
|
|
17580
17858
|
text = "";
|
|
17581
17859
|
}
|
|
@@ -17816,7 +18094,7 @@ function lastOccurrence(text, block) {
|
|
|
17816
18094
|
}
|
|
17817
18095
|
function coveredPathExists(targetRepo, line) {
|
|
17818
18096
|
const path = line.trim().replace(/^\//, "").replace(/\/$/, "");
|
|
17819
|
-
return path !== "" && lexists(
|
|
18097
|
+
return path !== "" && lexists(join23(targetRepo, path));
|
|
17820
18098
|
}
|
|
17821
18099
|
function selfBuildMergedFileRules() {
|
|
17822
18100
|
return {
|
|
@@ -17830,7 +18108,7 @@ function removeSelfBuildOwnedFile(targetRepo, rel, lists, name) {
|
|
|
17830
18108
|
name(lists.leftAlone, `${rel} (a symbolic link, or inside one \u2014 not followed)`, rel);
|
|
17831
18109
|
return;
|
|
17832
18110
|
}
|
|
17833
|
-
const abs =
|
|
18111
|
+
const abs = join23(targetRepo, rel);
|
|
17834
18112
|
let isFile;
|
|
17835
18113
|
try {
|
|
17836
18114
|
isFile = lstatSync3(abs).isFile();
|
|
@@ -17854,7 +18132,7 @@ function removeSelfBuildCrewRoster(targetRepo, lists, name) {
|
|
|
17854
18132
|
name(lists.leftAlone, `${rel} (a symbolic link, or inside one \u2014 not followed)`, rel);
|
|
17855
18133
|
return;
|
|
17856
18134
|
}
|
|
17857
|
-
const abs =
|
|
18135
|
+
const abs = join23(targetRepo, rel);
|
|
17858
18136
|
let bytes;
|
|
17859
18137
|
try {
|
|
17860
18138
|
bytes = readFileSync20(abs);
|
|
@@ -17881,7 +18159,7 @@ function removeSelfBuildCrewProfile(targetRepo, rel, text, lists, name) {
|
|
|
17881
18159
|
name(lists.leftAlone, `${rel} (a symbolic link, or inside one \u2014 not followed)`, rel);
|
|
17882
18160
|
return;
|
|
17883
18161
|
}
|
|
17884
|
-
const abs =
|
|
18162
|
+
const abs = join23(targetRepo, rel);
|
|
17885
18163
|
let bytes;
|
|
17886
18164
|
try {
|
|
17887
18165
|
bytes = readFileSync20(abs);
|
|
@@ -17906,7 +18184,7 @@ function removeSelfBuildCrewProfile(targetRepo, rel, text, lists, name) {
|
|
|
17906
18184
|
function removeSelfBuildEmptyDir(targetRepo, dir, lists, name) {
|
|
17907
18185
|
if (symlinkOnPath(targetRepo, dir))
|
|
17908
18186
|
return;
|
|
17909
|
-
const abs =
|
|
18187
|
+
const abs = join23(targetRepo, dir);
|
|
17910
18188
|
try {
|
|
17911
18189
|
if (!lstatSync3(abs).isDirectory() || readdirSync(abs).length > 0)
|
|
17912
18190
|
return;
|
|
@@ -17940,7 +18218,7 @@ function uninstallSelfBuild(options) {
|
|
|
17940
18218
|
throw err;
|
|
17941
18219
|
}
|
|
17942
18220
|
const { targetRepo } = options;
|
|
17943
|
-
const pinExisted = lexists(
|
|
18221
|
+
const pinExisted = lexists(join23(targetRepo, BUNDLE_PIN_REL));
|
|
17944
18222
|
const name = (list2, text, _path) => {
|
|
17945
18223
|
list2.push(text);
|
|
17946
18224
|
};
|
|
@@ -18056,7 +18334,7 @@ function uninstall(options) {
|
|
|
18056
18334
|
const gitattributes = manifest.gitattributes ?? { created: false, appended: [] };
|
|
18057
18335
|
trimAppendedFile(targetRepo, gitattributes, deleteWhenEmpty(gitattributes.created), gitattributesRules(writeSet), result, name);
|
|
18058
18336
|
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(
|
|
18337
|
+
if (checked.manifest === null && !lexists(join23(targetRepo, BUNDLE_PIN_REL)) && foundNothing) {
|
|
18060
18338
|
return { outcome: "not-installed", exitCode: 0, ...result, credential: nothingToSay };
|
|
18061
18339
|
}
|
|
18062
18340
|
const credential = handleCredential(options, checked);
|
|
@@ -18064,7 +18342,7 @@ function uninstall(options) {
|
|
|
18064
18342
|
if (result.failed.length === 0 && !credentialFailed) {
|
|
18065
18343
|
removeInstalledFile(targetRepo, BUNDLE_PIN_REL, recorded.get(BUNDLE_PIN_REL), writeSet, result, name);
|
|
18066
18344
|
try {
|
|
18067
|
-
rmSync6(
|
|
18345
|
+
rmSync6(join23(targetRepo, INSTALL_MANIFEST_REL), { force: true });
|
|
18068
18346
|
named2.add(INSTALL_MANIFEST_REL);
|
|
18069
18347
|
} catch {
|
|
18070
18348
|
name(result.failed, INSTALL_MANIFEST_REL, INSTALL_MANIFEST_REL);
|
|
@@ -18099,13 +18377,13 @@ function applyMergedJson(targetRepo, rule, file, deleteWhenEmpty, lists, name, e
|
|
|
18099
18377
|
return;
|
|
18100
18378
|
const empty = Object.keys(value).length === 0 && deleteWhenEmpty;
|
|
18101
18379
|
writeBack(targetRepo, rel, file.text, empty ? null : inLineEndingOf(file.text, serializeJson(value)), lists, name);
|
|
18102
|
-
if (adopted && lexists(
|
|
18380
|
+
if (adopted && lexists(join23(targetRepo, rel))) {
|
|
18103
18381
|
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
18382
|
}
|
|
18105
18383
|
}
|
|
18106
18384
|
function removeCodexConfigBlock(targetRepo, record2, deleteWhenEmpty, lists, name) {
|
|
18107
18385
|
const rel = CODEX_CONFIG_REL2;
|
|
18108
|
-
const path =
|
|
18386
|
+
const path = join23(targetRepo, rel);
|
|
18109
18387
|
if (symlinkOnPath(targetRepo, rel)) {
|
|
18110
18388
|
if (lexists(path))
|
|
18111
18389
|
name(lists.leftAlone, `${rel} (a symbolic link, or inside one \u2014 not followed)`, rel);
|
|
@@ -18150,7 +18428,7 @@ function removeInstalledFile(targetRepo, path, entry, writeSet, lists, name) {
|
|
|
18150
18428
|
name(lists.leftAlone, `${path} (a symbolic link, or inside one \u2014 not followed)`, path);
|
|
18151
18429
|
return;
|
|
18152
18430
|
}
|
|
18153
|
-
const abs =
|
|
18431
|
+
const abs = join23(targetRepo, path);
|
|
18154
18432
|
let isFile;
|
|
18155
18433
|
try {
|
|
18156
18434
|
isFile = lstatSync3(abs).isFile();
|
|
@@ -18184,7 +18462,7 @@ function removeInstalledFile(targetRepo, path, entry, writeSet, lists, name) {
|
|
|
18184
18462
|
function removeEmptyDir(targetRepo, dir, writeSet, lists, name) {
|
|
18185
18463
|
if (!writeSet.dirs.has(dir) || symlinkOnPath(targetRepo, dir))
|
|
18186
18464
|
return;
|
|
18187
|
-
const abs =
|
|
18465
|
+
const abs = join23(targetRepo, dir);
|
|
18188
18466
|
try {
|
|
18189
18467
|
if (!lstatSync3(abs).isDirectory() || readdirSync(abs).length > 0)
|
|
18190
18468
|
return;
|
|
@@ -18203,7 +18481,7 @@ function trimAppendedFile(targetRepo, rec, deleteWhenEmpty, rules, lists, name)
|
|
|
18203
18481
|
name(lists.leftAlone, `${rel} (a symbolic link \u2014 not followed)`, rel);
|
|
18204
18482
|
return [];
|
|
18205
18483
|
}
|
|
18206
|
-
const path =
|
|
18484
|
+
const path = join23(targetRepo, rel);
|
|
18207
18485
|
let before;
|
|
18208
18486
|
try {
|
|
18209
18487
|
before = readFileSync20(path, "utf-8");
|
|
@@ -18250,16 +18528,16 @@ function filesUnder(root, rel) {
|
|
|
18250
18528
|
const walk = (dirRel) => {
|
|
18251
18529
|
let names;
|
|
18252
18530
|
try {
|
|
18253
|
-
if (!lstatSync3(
|
|
18531
|
+
if (!lstatSync3(join23(root, dirRel)).isDirectory())
|
|
18254
18532
|
return;
|
|
18255
|
-
names = readdirSync(
|
|
18533
|
+
names = readdirSync(join23(root, dirRel));
|
|
18256
18534
|
} catch {
|
|
18257
18535
|
return;
|
|
18258
18536
|
}
|
|
18259
18537
|
for (const entry of names.sort()) {
|
|
18260
18538
|
const childRel = `${dirRel}/${entry}`;
|
|
18261
18539
|
try {
|
|
18262
|
-
if (lstatSync3(
|
|
18540
|
+
if (lstatSync3(join23(root, childRel)).isDirectory())
|
|
18263
18541
|
walk(childRel);
|
|
18264
18542
|
else
|
|
18265
18543
|
out.push(childRel);
|
|
@@ -18272,7 +18550,7 @@ function filesUnder(root, rel) {
|
|
|
18272
18550
|
return out;
|
|
18273
18551
|
}
|
|
18274
18552
|
function shownPath(path, home) {
|
|
18275
|
-
const base = home ??
|
|
18553
|
+
const base = home ?? homedir6();
|
|
18276
18554
|
const rel = relative6(base, path);
|
|
18277
18555
|
const under = rel !== "" && !rel.startsWith("..") && !rel.startsWith(sep2);
|
|
18278
18556
|
return `${under ? `~/${rel.split(sep2).join("/")}` : path}/`;
|
|
@@ -18332,7 +18610,7 @@ function renderUninstallReport(result, home) {
|
|
|
18332
18610
|
|
|
18333
18611
|
// dist/banner-facts.js
|
|
18334
18612
|
import { readFileSync as readFileSync21 } from "node:fs";
|
|
18335
|
-
import { basename as basename4, join as
|
|
18613
|
+
import { basename as basename4, join as join24, resolve as resolve7 } from "node:path";
|
|
18336
18614
|
var BRAND_URL = "halfcycle.ai";
|
|
18337
18615
|
var TAGLINE = [
|
|
18338
18616
|
"Fewer cycles.",
|
|
@@ -18345,7 +18623,7 @@ var TAGLINE_NARROW = [
|
|
|
18345
18623
|
];
|
|
18346
18624
|
function bundleVersion() {
|
|
18347
18625
|
try {
|
|
18348
|
-
const pkg = JSON.parse(readFileSync21(
|
|
18626
|
+
const pkg = JSON.parse(readFileSync21(join24(BUNDLE_ROOT, "package.json"), "utf-8"));
|
|
18349
18627
|
return typeof pkg.version === "string" ? pkg.version : void 0;
|
|
18350
18628
|
} catch {
|
|
18351
18629
|
return void 0;
|
|
@@ -18441,7 +18719,7 @@ function safeBasename(dir) {
|
|
|
18441
18719
|
// dist/pace.js
|
|
18442
18720
|
import { spawn as spawn3 } from "node:child_process";
|
|
18443
18721
|
import { readFileSync as readFileSync22 } from "node:fs";
|
|
18444
|
-
import { join as
|
|
18722
|
+
import { join as join25 } from "node:path";
|
|
18445
18723
|
var PACE_USAGE = `${HALFCYCLE_COMMAND} pace [--agent claude-code|codex] [--start <phase> [--agents <n>]] [--repo <root>]`;
|
|
18446
18724
|
function parsePaceArgs(argv) {
|
|
18447
18725
|
let repoRoot;
|
|
@@ -18555,7 +18833,7 @@ function notPacedLine(reason) {
|
|
|
18555
18833
|
}
|
|
18556
18834
|
function readPromptScript(repoRoot) {
|
|
18557
18835
|
try {
|
|
18558
|
-
return readFileSync22(
|
|
18836
|
+
return readFileSync22(join25(repoRoot, ".claude", "hooks", "user-prompt-scope.sh"), "utf-8");
|
|
18559
18837
|
} catch {
|
|
18560
18838
|
return null;
|
|
18561
18839
|
}
|
|
@@ -18593,14 +18871,17 @@ async function runPace(repoRoot, start, deps, agent = null) {
|
|
|
18593
18871
|
return { stdout: `${paceStartLinesOf(result.suggestion, behind).join("\n")}
|
|
18594
18872
|
`, stderr: "", exitCode: 0 };
|
|
18595
18873
|
}
|
|
18596
|
-
const
|
|
18874
|
+
const share = result.keepFreePercent ?? KEEP_FREE_DEFAULT;
|
|
18875
|
+
const lines = [paceBuildUpToOf(result.number), paceAllowedNowOf(result.allowedNow), share === 0 ? PACE_P5 : paceP3Of(share)];
|
|
18876
|
+
if (result.limit !== "reached" && result.usage !== void 0) {
|
|
18877
|
+
lines.push(limitUsedOf(result.usage.used, result.usage.limit));
|
|
18878
|
+
}
|
|
18597
18879
|
if (result.limit === "reached" && result.limitHeld !== void 0) {
|
|
18598
18880
|
const held = result.limitHeld;
|
|
18599
18881
|
lines.push(limitReachedLineOf(held.used, held.limit, held.working, held.raiseBy));
|
|
18600
18882
|
}
|
|
18601
18883
|
if (result.windowKind !== void 0 && result.until !== void 0) {
|
|
18602
18884
|
const until = new Date(result.until);
|
|
18603
|
-
const share = result.keepFreePercent ?? KEEP_FREE_DEFAULT;
|
|
18604
18885
|
if (result.pace === "slowing")
|
|
18605
18886
|
lines.push(paceS1Of(result.windowKind, result.allowedNow, until, deps.now(), share));
|
|
18606
18887
|
else if (result.pace === "holding")
|
|
@@ -18611,10 +18892,9 @@ async function runPace(repoRoot, start, deps, agent = null) {
|
|
|
18611
18892
|
}
|
|
18612
18893
|
|
|
18613
18894
|
// dist/route.js
|
|
18614
|
-
import {
|
|
18615
|
-
import {
|
|
18895
|
+
import { readFileSync as readFileSync23 } from "node:fs";
|
|
18896
|
+
import { join as join26 } from "node:path";
|
|
18616
18897
|
var ROUTE_USAGE = `${HALFCYCLE_COMMAND} route <phase> <task> [--agent claude-code|codex] [--repo <root>]`;
|
|
18617
|
-
var ROUTING_FILENAME = "routing.json";
|
|
18618
18898
|
var ROUTE_WORK_READ_DEADLINE_MS = 5e3;
|
|
18619
18899
|
function parseRouteArgs(argv) {
|
|
18620
18900
|
const positionals = [];
|
|
@@ -18656,7 +18936,7 @@ function parseRouteArgs(argv) {
|
|
|
18656
18936
|
return { ok: true, phase: positionals[0], task: positionals[1], agent: agent ?? "claude-code", repoRoot };
|
|
18657
18937
|
}
|
|
18658
18938
|
function routingPath(engagementId, home) {
|
|
18659
|
-
return
|
|
18939
|
+
return join26(engagementStateDir(engagementId, home), ROUTING_FILENAME);
|
|
18660
18940
|
}
|
|
18661
18941
|
function readCheapStarted(path, phase) {
|
|
18662
18942
|
try {
|
|
@@ -18668,19 +18948,11 @@ function readCheapStarted(path, phase) {
|
|
|
18668
18948
|
return [];
|
|
18669
18949
|
}
|
|
18670
18950
|
}
|
|
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) {
|
|
18951
|
+
function workOf(read, task) {
|
|
18681
18952
|
if (read.kind !== "work")
|
|
18682
18953
|
return null;
|
|
18683
|
-
|
|
18954
|
+
const entry = read.work.tasks.find((t) => t.task === task);
|
|
18955
|
+
return { attempts: entry?.attempts ?? [], passed: entry?.standing === "passed" };
|
|
18684
18956
|
}
|
|
18685
18957
|
function lastTierOf(attempts) {
|
|
18686
18958
|
return [...attempts].sort((a, b) => b.attempt - a.attempt)[0].builderTier;
|
|
@@ -18734,9 +19006,11 @@ async function runRoute(repoRoot, phase, taskId, agent, deps) {
|
|
|
18734
19006
|
} catch {
|
|
18735
19007
|
return answer(routeStartLineOf(holder, taskId, member.tier));
|
|
18736
19008
|
}
|
|
18737
|
-
const
|
|
18738
|
-
if (
|
|
18739
|
-
|
|
19009
|
+
const work = workOf(await readWorkInTime(deps, credential, phase), taskId);
|
|
19010
|
+
if (work !== null && work.attempts.length > 0) {
|
|
19011
|
+
if (work.passed)
|
|
19012
|
+
return answer(routePassedLineOf(taskId, lastTierOf(work.attempts)));
|
|
19013
|
+
return answer(routeStartLineOf(holder, taskId, tierAboveOf(lastTierOf(work.attempts))));
|
|
18740
19014
|
}
|
|
18741
19015
|
if (agent === "codex" && !hasCheaperRole(member))
|
|
18742
19016
|
return answer(routeStartLineOf(holder, taskId, member.tier));
|
|
@@ -18748,19 +19022,14 @@ async function runRoute(repoRoot, phase, taskId, agent, deps) {
|
|
|
18748
19022
|
}
|
|
18749
19023
|
if (readCheapStarted(path, phase).includes(taskId))
|
|
18750
19024
|
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
19025
|
return answer(agent === "codex" ? routeCheaperCodexLineOf(holder, taskId) : routeCheaperClaudeLineOf(holder, taskId, AGENT_PROFILES["claude-code"].tierModels.fast.model));
|
|
18757
19026
|
}
|
|
18758
19027
|
|
|
18759
19028
|
// dist/restore.js
|
|
18760
19029
|
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
|
|
19030
|
+
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";
|
|
19031
|
+
import { homedir as homedir7 } from "node:os";
|
|
19032
|
+
import { join as join27, resolve as resolve8 } from "node:path";
|
|
18764
19033
|
var RESTORE_USAGE = `${HALFCYCLE_COMMAND} restore [<id>]`;
|
|
18765
19034
|
function parseRestoreArgs(rest) {
|
|
18766
19035
|
if (rest.length > 1)
|
|
@@ -18782,28 +19051,28 @@ function runRestore(cwd, id, options = {}) {
|
|
|
18782
19051
|
if (engagementId === null) {
|
|
18783
19052
|
return refused2(`Halfcycle is not set up in this project, so it has saved nothing here. To set it up, run: ${HALFCYCLE_COMMAND}`);
|
|
18784
19053
|
}
|
|
18785
|
-
const
|
|
19054
|
+
const folder2 = join27(options.home ?? homedir7(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, SAVED_DIR_NAME);
|
|
18786
19055
|
const refs = savedRefs(root);
|
|
18787
19056
|
if (refs === null)
|
|
18788
19057
|
return refused2("Halfcycle could not read the saved copies in this project, so nothing was put back.");
|
|
18789
|
-
pruneAtRestore(root,
|
|
19058
|
+
pruneAtRestore(root, folder2, refs, now());
|
|
18790
19059
|
for (const [saveId, madeAt] of [...refs]) {
|
|
18791
19060
|
const answer = savedPruneAnswer({ refMadeAt: madeAt, folderChangedAt: 0, folderRepository: null }, { repository: null, now: now() });
|
|
18792
19061
|
if (answer !== "keep")
|
|
18793
19062
|
refs.delete(saveId);
|
|
18794
19063
|
}
|
|
18795
19064
|
if (id === void 0)
|
|
18796
|
-
return list(root,
|
|
18797
|
-
return restoreOne(root,
|
|
19065
|
+
return list(root, folder2, refs);
|
|
19066
|
+
return restoreOne(root, folder2, refs, id);
|
|
18798
19067
|
}
|
|
18799
19068
|
function refused2(line) {
|
|
18800
19069
|
return { stdout: "", stderr: `${line}
|
|
18801
19070
|
`, exitCode: 1 };
|
|
18802
19071
|
}
|
|
18803
|
-
function list(root,
|
|
19072
|
+
function list(root, folder2, refs) {
|
|
18804
19073
|
const rows = [];
|
|
18805
19074
|
for (const [id, at] of refs) {
|
|
18806
|
-
const saved = readSaved(
|
|
19075
|
+
const saved = readSaved(folder2, id);
|
|
18807
19076
|
if (saved !== null)
|
|
18808
19077
|
rows.push({ id, at, what: saved.what });
|
|
18809
19078
|
}
|
|
@@ -18833,14 +19102,14 @@ function whenOf(ms) {
|
|
|
18833
19102
|
function printable(text) {
|
|
18834
19103
|
return text.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/g, " ");
|
|
18835
19104
|
}
|
|
18836
|
-
function restoreOne(root,
|
|
19105
|
+
function restoreOne(root, folder2, refs, rawId) {
|
|
18837
19106
|
const valid = savedIdSchema.safeParse(rawId);
|
|
18838
19107
|
const named2 = valid.success ? `"${rawId}"` : "with that name";
|
|
18839
19108
|
if (!valid.success || !refs.has(rawId)) {
|
|
18840
19109
|
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
19110
|
}
|
|
18842
19111
|
const id = valid.data;
|
|
18843
|
-
const saved = readSaved(
|
|
19112
|
+
const saved = readSaved(folder2, id);
|
|
18844
19113
|
if (saved === null)
|
|
18845
19114
|
return refused2(`The saved copy "${id}" could not be read, so nothing was put back.`);
|
|
18846
19115
|
const ref = savedRefOf(id);
|
|
@@ -18858,7 +19127,7 @@ function restoreOne(root, folder, refs, rawId) {
|
|
|
18858
19127
|
if (p.keptIn === "commit")
|
|
18859
19128
|
fromCommit.push(p.path);
|
|
18860
19129
|
else
|
|
18861
|
-
restoreCopy(root,
|
|
19130
|
+
restoreCopy(root, join27(folder2, id), p.path, out);
|
|
18862
19131
|
}
|
|
18863
19132
|
if (fromCommit.length > 0)
|
|
18864
19133
|
restoreFromCommit(root, ref, fromCommit, out);
|
|
@@ -18886,12 +19155,12 @@ function makeFolders(root, rel) {
|
|
|
18886
19155
|
const segments = rel.split("/");
|
|
18887
19156
|
let at = root;
|
|
18888
19157
|
for (let i = 0; i < segments.length - 1; i++) {
|
|
18889
|
-
at =
|
|
19158
|
+
at = join27(at, segments[i]);
|
|
18890
19159
|
const shown2 = segments.slice(0, i + 1).join("/");
|
|
18891
19160
|
let st = lstatOrNull3(at);
|
|
18892
19161
|
if (st === null) {
|
|
18893
19162
|
try {
|
|
18894
|
-
|
|
19163
|
+
mkdirSync10(at);
|
|
18895
19164
|
} catch {
|
|
18896
19165
|
}
|
|
18897
19166
|
st = lstatOrNull3(at);
|
|
@@ -18906,7 +19175,7 @@ function makeFolders(root, rel) {
|
|
|
18906
19175
|
return null;
|
|
18907
19176
|
}
|
|
18908
19177
|
function place(root, rel, content, out) {
|
|
18909
|
-
const abs =
|
|
19178
|
+
const abs = join27(root, rel);
|
|
18910
19179
|
const there = lstatOrNull3(abs);
|
|
18911
19180
|
if (there !== null) {
|
|
18912
19181
|
if (sameAs(abs, there, content))
|
|
@@ -18954,14 +19223,14 @@ function restoreCopy(root, saveDir, rel, out) {
|
|
|
18954
19223
|
let at = saveDir;
|
|
18955
19224
|
const chain = [SAVED_COPIES_DIR_NAME, ...segments];
|
|
18956
19225
|
for (let i = 0; i < chain.length - 1; i++) {
|
|
18957
|
-
at =
|
|
19226
|
+
at = join27(at, chain[i]);
|
|
18958
19227
|
const st2 = lstatOrNull3(at);
|
|
18959
19228
|
if (st2 === null || st2.isSymbolicLink() || !st2.isDirectory()) {
|
|
18960
19229
|
out.left.push(`${rel}: the saved copy does not hold it`);
|
|
18961
19230
|
return;
|
|
18962
19231
|
}
|
|
18963
19232
|
}
|
|
18964
|
-
const src =
|
|
19233
|
+
const src = join27(at, chain[chain.length - 1]);
|
|
18965
19234
|
const st = lstatOrNull3(src);
|
|
18966
19235
|
try {
|
|
18967
19236
|
if (st !== null && st.isSymbolicLink())
|
|
@@ -19065,7 +19334,7 @@ function restoreLines(root, ref, saved, out) {
|
|
|
19065
19334
|
}
|
|
19066
19335
|
function pinnedEngagement(root) {
|
|
19067
19336
|
try {
|
|
19068
|
-
const pin = JSON.parse(readFileSync24(
|
|
19337
|
+
const pin = JSON.parse(readFileSync24(join27(root, BUNDLE_PIN_REL), "utf-8"));
|
|
19069
19338
|
return isEngagementId(pin.engagementId) ? pin.engagementId : null;
|
|
19070
19339
|
} catch {
|
|
19071
19340
|
return null;
|
|
@@ -19086,12 +19355,12 @@ function savedRefs(root) {
|
|
|
19086
19355
|
}
|
|
19087
19356
|
return refs;
|
|
19088
19357
|
}
|
|
19089
|
-
function readSaved(
|
|
19090
|
-
const dir =
|
|
19358
|
+
function readSaved(folder2, id) {
|
|
19359
|
+
const dir = join27(folder2, id);
|
|
19091
19360
|
const st = lstatOrNull3(dir);
|
|
19092
19361
|
if (st === null || !st.isDirectory())
|
|
19093
19362
|
return null;
|
|
19094
|
-
const file =
|
|
19363
|
+
const file = join27(dir, SAVED_FILE_NAME);
|
|
19095
19364
|
const fst = lstatOrNull3(file);
|
|
19096
19365
|
if (fst === null || !fst.isFile())
|
|
19097
19366
|
return null;
|
|
@@ -19102,13 +19371,13 @@ function readSaved(folder, id) {
|
|
|
19102
19371
|
return null;
|
|
19103
19372
|
}
|
|
19104
19373
|
}
|
|
19105
|
-
function pruneAtRestore(root,
|
|
19374
|
+
function pruneAtRestore(root, folder2, refs, now) {
|
|
19106
19375
|
try {
|
|
19107
19376
|
const common = git2(root, ["rev-parse", "--git-common-dir"]);
|
|
19108
19377
|
const repository = common.ok && common.text.trim() !== "" ? realOrSelf(resolve8(root, common.text.trim())) : null;
|
|
19109
19378
|
let names = [];
|
|
19110
19379
|
try {
|
|
19111
|
-
names = readdirSync2(
|
|
19380
|
+
names = readdirSync2(folder2);
|
|
19112
19381
|
} catch {
|
|
19113
19382
|
names = [];
|
|
19114
19383
|
}
|
|
@@ -19117,7 +19386,7 @@ function pruneAtRestore(root, folder, refs, now) {
|
|
|
19117
19386
|
if (!savedIdSchema.safeParse(id).success)
|
|
19118
19387
|
continue;
|
|
19119
19388
|
here.add(id);
|
|
19120
|
-
const dir =
|
|
19389
|
+
const dir = join27(folder2, id);
|
|
19121
19390
|
const st = lstatOrNull3(dir);
|
|
19122
19391
|
const answer = savedPruneAnswer({
|
|
19123
19392
|
refMadeAt: refs.get(id) ?? null,
|
|
@@ -19145,7 +19414,7 @@ function pruneAtRestore(root, folder, refs, now) {
|
|
|
19145
19414
|
}
|
|
19146
19415
|
function markedRepository(dir) {
|
|
19147
19416
|
try {
|
|
19148
|
-
const text = readFileSync24(
|
|
19417
|
+
const text = readFileSync24(join27(dir, SAVED_REPOSITORY_FILE_NAME), "utf-8").trim();
|
|
19149
19418
|
return text === "" ? null : text;
|
|
19150
19419
|
} catch {
|
|
19151
19420
|
return null;
|
|
@@ -19174,7 +19443,110 @@ function realOrSelf(p) {
|
|
|
19174
19443
|
}
|
|
19175
19444
|
}
|
|
19176
19445
|
|
|
19446
|
+
// dist/request-shapes-verb.js
|
|
19447
|
+
import { chmodSync as chmodSync4, existsSync as existsSync13, mkdirSync as mkdirSync11, readFileSync as readFileSync25, rmSync as rmSync8, writeFileSync as writeFileSync8 } from "node:fs";
|
|
19448
|
+
import { homedir as homedir8, platform as platform5 } from "node:os";
|
|
19449
|
+
import { join as join28 } from "node:path";
|
|
19450
|
+
var REQUEST_SHAPES_USAGE = `${HALFCYCLE_COMMAND} request-shapes on|off|show|clear`;
|
|
19451
|
+
var REQUEST_SHAPES_FORMS = ["on", "off", "show", "clear"];
|
|
19452
|
+
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.";
|
|
19453
|
+
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.";
|
|
19454
|
+
var REQUEST_SHAPES_EMPTY_LINE = "No requests have been written down.";
|
|
19455
|
+
var REQUEST_SHAPES_CLEARED_LINE = "Halfcycle has removed the requests it wrote down.";
|
|
19456
|
+
function parseRequestShapesArgs(rest) {
|
|
19457
|
+
if (rest.length !== 1)
|
|
19458
|
+
return null;
|
|
19459
|
+
const form = rest[0];
|
|
19460
|
+
return REQUEST_SHAPES_FORMS.includes(form ?? "") ? form : null;
|
|
19461
|
+
}
|
|
19462
|
+
function asLoggedLine(text) {
|
|
19463
|
+
let value;
|
|
19464
|
+
try {
|
|
19465
|
+
value = JSON.parse(text);
|
|
19466
|
+
} catch {
|
|
19467
|
+
return null;
|
|
19468
|
+
}
|
|
19469
|
+
if (typeof value !== "object" || value === null)
|
|
19470
|
+
return null;
|
|
19471
|
+
const v = value;
|
|
19472
|
+
const str = (k) => typeof v[k] === "string" ? v[k] : "";
|
|
19473
|
+
return {
|
|
19474
|
+
at: str("at"),
|
|
19475
|
+
process: str("process"),
|
|
19476
|
+
method: str("method"),
|
|
19477
|
+
host: str("host"),
|
|
19478
|
+
path: str("path"),
|
|
19479
|
+
query: Array.isArray(v["query"]) ? v["query"].filter((q) => typeof q === "string") : [],
|
|
19480
|
+
shape: typeof v["shape"] === "string" ? v["shape"] : null,
|
|
19481
|
+
status: typeof v["status"] === "number" || typeof v["status"] === "string" ? v["status"] : ""
|
|
19482
|
+
};
|
|
19483
|
+
}
|
|
19484
|
+
function formatLine(line) {
|
|
19485
|
+
const at = line.at.replace(/\.\d+Z$/, "Z");
|
|
19486
|
+
const query = line.query.length > 0 ? `?${line.query.join("&")}` : "";
|
|
19487
|
+
const head = `${at} ${line.process.padEnd(22)} ${line.method.padEnd(4)} ${line.host} ${line.path}${query} -> ${String(line.status)}`;
|
|
19488
|
+
return line.shape === null ? head : `${head}
|
|
19489
|
+
${line.shape}`;
|
|
19490
|
+
}
|
|
19491
|
+
function showLog(logPath2) {
|
|
19492
|
+
let text = "";
|
|
19493
|
+
try {
|
|
19494
|
+
text = existsSync13(logPath2) ? readFileSync25(logPath2, "utf-8") : "";
|
|
19495
|
+
} catch {
|
|
19496
|
+
text = "";
|
|
19497
|
+
}
|
|
19498
|
+
const lines = text.split("\n").filter((l) => l.trim() !== "").map(asLoggedLine).filter((l) => l !== null);
|
|
19499
|
+
if (lines.length === 0)
|
|
19500
|
+
return `${REQUEST_SHAPES_EMPTY_LINE}
|
|
19501
|
+
`;
|
|
19502
|
+
const count = lines.length === 1 ? "1 request." : `${lines.length} requests.`;
|
|
19503
|
+
return `${lines.map(formatLine).join("\n")}
|
|
19504
|
+
${count}
|
|
19505
|
+
`;
|
|
19506
|
+
}
|
|
19507
|
+
function ownerOnly(path, mode) {
|
|
19508
|
+
if (platform5() !== "win32")
|
|
19509
|
+
chmodSync4(path, mode);
|
|
19510
|
+
}
|
|
19511
|
+
function runRequestShapes(form, home = homedir8()) {
|
|
19512
|
+
const folder2 = join28(home, HALFCYCLE_DIR_NAME, REQUEST_SHAPES_DIR_NAME);
|
|
19513
|
+
const switchPath = join28(folder2, REQUEST_SHAPES_SWITCH_FILENAME);
|
|
19514
|
+
const logPath2 = join28(folder2, REQUEST_SHAPES_LOG_FILENAME);
|
|
19515
|
+
try {
|
|
19516
|
+
switch (form) {
|
|
19517
|
+
case "on":
|
|
19518
|
+
mkdirSync11(folder2, { recursive: true, mode: 448 });
|
|
19519
|
+
ownerOnly(folder2, 448);
|
|
19520
|
+
writeFileSync8(switchPath, "", { mode: 384 });
|
|
19521
|
+
ownerOnly(switchPath, 384);
|
|
19522
|
+
return { stdout: `${REQUEST_SHAPES_ON_LINE}
|
|
19523
|
+
`, stderr: "", exitCode: 0 };
|
|
19524
|
+
case "off":
|
|
19525
|
+
rmSync8(switchPath, { force: true });
|
|
19526
|
+
return { stdout: `${REQUEST_SHAPES_OFF_LINE}
|
|
19527
|
+
`, stderr: "", exitCode: 0 };
|
|
19528
|
+
case "show":
|
|
19529
|
+
return { stdout: showLog(logPath2), stderr: "", exitCode: 0 };
|
|
19530
|
+
case "clear":
|
|
19531
|
+
if (existsSync13(logPath2)) {
|
|
19532
|
+
writeFileSync8(logPath2, "", { mode: 384 });
|
|
19533
|
+
ownerOnly(logPath2, 384);
|
|
19534
|
+
}
|
|
19535
|
+
return { stdout: `${REQUEST_SHAPES_CLEARED_LINE}
|
|
19536
|
+
`, stderr: "", exitCode: 0 };
|
|
19537
|
+
}
|
|
19538
|
+
} catch {
|
|
19539
|
+
return {
|
|
19540
|
+
stdout: "",
|
|
19541
|
+
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.
|
|
19542
|
+
`,
|
|
19543
|
+
exitCode: 1
|
|
19544
|
+
};
|
|
19545
|
+
}
|
|
19546
|
+
}
|
|
19547
|
+
|
|
19177
19548
|
// dist/bin.js
|
|
19549
|
+
knowRequestShapeVerbs(CLI_VERBS);
|
|
19178
19550
|
var ASSUME_YES_FLAGS = /* @__PURE__ */ new Set(["--yes", "-y"]);
|
|
19179
19551
|
var QUIET_FLAGS = /* @__PURE__ */ new Set(["--quiet", "-q"]);
|
|
19180
19552
|
var VERBOSE_FLAGS = /* @__PURE__ */ new Set(["--verbose", "-v"]);
|
|
@@ -19267,6 +19639,9 @@ var USAGE = ` ${HALFCYCLE_COMMAND} [install] [target-repo] [engagement-id] [sel
|
|
|
19267
19639
|
show what setting up this folder would add, change, remove or leave alone; writes nothing
|
|
19268
19640
|
${HALFCYCLE_COMMAND} doctor
|
|
19269
19641
|
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
|
|
19642
|
+
${REQUEST_SHAPES_USAGE}
|
|
19643
|
+
write down the shape of every request this computer sends to Halfcycle, never what is in
|
|
19644
|
+
them; show what was written; empty it; stop
|
|
19270
19645
|
${RESTORE_USAGE}
|
|
19271
19646
|
list the copies Halfcycle saved before a change it let through, or bring one back; never
|
|
19272
19647
|
replaces a file or a line of work that exists now
|
|
@@ -19396,6 +19771,22 @@ ${USAGE}`);
|
|
|
19396
19771
|
process.exitCode = report.exitCode;
|
|
19397
19772
|
return;
|
|
19398
19773
|
}
|
|
19774
|
+
if (cmd === "request-shapes") {
|
|
19775
|
+
const form = parseRequestShapesArgs(rest);
|
|
19776
|
+
if (form === null) {
|
|
19777
|
+
process.stderr.write(`halfcycle request-shapes: usage: ${REQUEST_SHAPES_USAGE}
|
|
19778
|
+
`);
|
|
19779
|
+
process.exitCode = 2;
|
|
19780
|
+
return;
|
|
19781
|
+
}
|
|
19782
|
+
const run = runRequestShapes(form);
|
|
19783
|
+
if (run.stdout !== "")
|
|
19784
|
+
process.stdout.write(run.stdout);
|
|
19785
|
+
if (run.stderr !== "")
|
|
19786
|
+
process.stderr.write(run.stderr);
|
|
19787
|
+
process.exitCode = run.exitCode;
|
|
19788
|
+
return;
|
|
19789
|
+
}
|
|
19399
19790
|
if (cmd === "restore") {
|
|
19400
19791
|
const parsed = parseRestoreArgs(rest);
|
|
19401
19792
|
if (!parsed.ok) {
|
|
@@ -19415,7 +19806,7 @@ ${USAGE}`);
|
|
|
19415
19806
|
if (installArm) {
|
|
19416
19807
|
const engagementIdArg = positionals[1];
|
|
19417
19808
|
const engagementTypeRaw = positionals[2] ?? "client";
|
|
19418
|
-
if (!
|
|
19809
|
+
if (!existsSync14(targetRepo)) {
|
|
19419
19810
|
process.stderr.write(`${folderMissingRefusal(targetRepo)}
|
|
19420
19811
|
`);
|
|
19421
19812
|
process.exit(1);
|