halfcycle 0.5.3 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/bin/bin.bundle.mjs +3505 -2162
- package/bin/pre-tool-use-filter.sh +6 -2
- package/dist/bin.js +0 -0
- package/dist/check-criteria.d.ts +57 -0
- package/dist/check-criteria.d.ts.map +1 -0
- package/dist/check-report.d.ts +58 -0
- package/dist/check-report.d.ts.map +1 -0
- package/dist/check-tasks.d.ts +148 -5
- package/dist/check-tasks.d.ts.map +1 -1
- package/dist/cli-contract.d.ts +1 -1
- package/dist/cli-contract.d.ts.map +1 -1
- package/dist/cli.js +2321 -395
- package/dist/cli.js.map +3 -3
- package/dist/close-phase.d.ts +39 -1
- package/dist/close-phase.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +221 -25
- package/dist/index.js.map +3 -3
- package/dist/install.d.ts +7 -2
- package/dist/install.d.ts.map +1 -1
- package/dist/pace.d.ts +19 -4
- package/dist/pace.d.ts.map +1 -1
- package/dist/ready.d.ts +106 -0
- package/dist/ready.d.ts.map +1 -0
- package/dist/route.d.ts +95 -0
- package/dist/route.d.ts.map +1 -0
- package/package.json +11 -11
- package/dist/claude-plan.d.ts +0 -133
- package/dist/claude-plan.d.ts.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -510,9 +510,19 @@ var HOOK_REMEDY = `Run ${SIGN_IN_COMMAND} in the project folder to sign in.`;
|
|
|
510
510
|
import { z as z2 } from "zod";
|
|
511
511
|
var TASK_PLAN_FILE = "docs/build-records/task-plan.json";
|
|
512
512
|
var TASK_LIST_FILE = "docs/build-records/build-tasks.json";
|
|
513
|
+
var CRITERIA_RECORD_FILE = "docs/build-records/criteria-check.json";
|
|
514
|
+
var AGENT_NAME_MAX = 32;
|
|
513
515
|
var TASK_PLAN_TASKS_MAX = 500;
|
|
514
516
|
var TASK_PLAN_ID_MAX = 64;
|
|
515
517
|
var TASK_PLAN_BUILDS_NONE = "none";
|
|
518
|
+
var TASK_PLAN_CRITERIA_NONE = "none";
|
|
519
|
+
var TASK_PLAN_SPECS_NONE = "none";
|
|
520
|
+
var EXAMPLE_SPEC = "order-intake";
|
|
521
|
+
var SPEC_NAME = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
522
|
+
var CITATION = /^([a-z0-9][a-z0-9-]{0,63}) AC((?:0|[1-9][0-9]*)[a-z]?)$/;
|
|
523
|
+
function isCriterionCitation(text) {
|
|
524
|
+
return CITATION.test(text);
|
|
525
|
+
}
|
|
516
526
|
var TASK_PLAN_KINDS = ["build", "review", "spec", "walk", "check"];
|
|
517
527
|
var TASK_PLAN_INTERFACE_KINDS = [
|
|
518
528
|
"route",
|
|
@@ -526,6 +536,7 @@ var TASK_PLAN_INTERFACE_KINDS = [
|
|
|
526
536
|
var CONTROL_CHARACTER = /[\u0000-\u001f\u007f-\u009f]/;
|
|
527
537
|
var planText = z2.string().refine((text) => !CONTROL_CHARACTER.test(text), { message: "holds a control character" });
|
|
528
538
|
var taskIdSchema = z2.string().min(1).max(TASK_PLAN_ID_MAX).refine((text) => !CONTROL_CHARACTER.test(text), { message: "holds a control character" });
|
|
539
|
+
var agentNameText = z2.string().min(1).max(AGENT_NAME_MAX).refine((text) => !CONTROL_CHARACTER.test(text), { message: "holds a control character" });
|
|
529
540
|
var taskPlanTaskSchema = z2.object({
|
|
530
541
|
id: taskIdSchema,
|
|
531
542
|
kind: z2.enum(TASK_PLAN_KINDS),
|
|
@@ -538,8 +549,27 @@ var taskPlanTaskSchema = z2.object({
|
|
|
538
549
|
}).strict().optional(),
|
|
539
550
|
wholeProject: z2.literal(true).optional(),
|
|
540
551
|
ships: z2.enum(["deploy", "release"]).optional(),
|
|
541
|
-
builds: z2.union([z2.literal(TASK_PLAN_BUILDS_NONE), z2.array(planText)]).optional()
|
|
552
|
+
builds: z2.union([z2.literal(TASK_PLAN_BUILDS_NONE), z2.array(planText)]).optional(),
|
|
553
|
+
criteria: z2.union([z2.literal(TASK_PLAN_CRITERIA_NONE), z2.array(planText)]).optional(),
|
|
554
|
+
whyOutsidePlan: planText.optional(),
|
|
555
|
+
agent: agentNameText.optional(),
|
|
556
|
+
cheapFirst: z2.literal(true).optional(),
|
|
557
|
+
criteriaCheck: z2.literal(true).optional()
|
|
542
558
|
}).strict().superRefine((task, ctx) => {
|
|
559
|
+
if (task.criteriaCheck !== void 0 && (task.kind !== "check" || task.wholeProject !== void 0)) {
|
|
560
|
+
ctx.addIssue({
|
|
561
|
+
code: "custom",
|
|
562
|
+
path: ["criteriaCheck"],
|
|
563
|
+
message: "only a check task that is not a whole-project check is the check against the acceptance criteria"
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
if (task.agent !== void 0 && task.heldBy !== void 0) {
|
|
567
|
+
ctx.addIssue({
|
|
568
|
+
code: "custom",
|
|
569
|
+
path: ["agent"],
|
|
570
|
+
message: "a task a person holds names no crew member"
|
|
571
|
+
});
|
|
572
|
+
}
|
|
543
573
|
if (task.reviews !== void 0 && task.kind !== "review") {
|
|
544
574
|
ctx.addIssue({
|
|
545
575
|
code: "custom",
|
|
@@ -564,6 +594,7 @@ var taskPlanTaskSchema = z2.object({
|
|
|
564
594
|
});
|
|
565
595
|
var taskPlanSchema = z2.object({
|
|
566
596
|
phase: z2.string().min(1).refine((text) => !CONTROL_CHARACTER.test(text), { message: "holds a control character" }),
|
|
597
|
+
specs: z2.union([z2.literal(TASK_PLAN_SPECS_NONE), z2.array(planText)]).optional(),
|
|
567
598
|
tasks: z2.array(taskPlanTaskSchema).max(TASK_PLAN_TASKS_MAX)
|
|
568
599
|
}).strict();
|
|
569
600
|
var taskListRowSchema = z2.object({
|
|
@@ -574,6 +605,7 @@ var taskListFileSchema = z2.union([
|
|
|
574
605
|
z2.object({ phase: planText, tasks: z2.array(taskListRowSchema) }).loose(),
|
|
575
606
|
z2.array(z2.unknown())
|
|
576
607
|
]);
|
|
608
|
+
var PHASE_PLAN_PROBLEMS_SHOWN = 3;
|
|
577
609
|
var taskPlanLineText = {
|
|
578
610
|
O1: (a, b, x) => `"${a}" and "${b}" could run at the same time and both change ${x}. Make one wait for the other: add "${a}" to the dependsOn of "${b}", or the other way round.`,
|
|
579
611
|
O2: (a) => `"${a}" does not say what it touches. List the files and interfaces it changes.`,
|
|
@@ -586,6 +618,22 @@ var taskPlanLineText = {
|
|
|
586
618
|
N1: (asked) => `The task list in ${TASK_LIST_FILE} is not for "${asked}", so it was not compared.`,
|
|
587
619
|
O6: (phase, asked) => `The plan is for "${phase}", not "${asked}".`,
|
|
588
620
|
O7: (a, file, partIds) => `"${a}" does not say which parts of the system it builds. Add "builds" with the ids of the parts it builds, from ${file}: ${partIds.join(", ")}. If it builds none of them, write "builds": "${TASK_PLAN_BUILDS_NONE}".`,
|
|
621
|
+
O8: (a, parts, file) => parts.length === 1 ? `"${a}" builds "${parts[0]}", which is not in plan: ${file} does not change it. Copy "builds" from that plan's changes (a release, publish or pin task names the part it ships, or "${TASK_PLAN_BUILDS_NONE}"), or say why in "whyOutsidePlan".` : `"${a}" builds ${parts.map((x) => `"${x}"`).join(", ")}, which are not in plan: ${file} changes none of them. Copy "builds" from that plan's changes (a release, publish or pin task names the part it ships, or "${TASK_PLAN_BUILDS_NONE}"), or say why in "whyOutsidePlan".`,
|
|
622
|
+
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
|
+
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
|
+
C1Form: (a, x, first) => `"${a}" names "${x}", which is not a criterion. Write it as <spec name> AC<number>, for example "${first} AC1".`,
|
|
625
|
+
C1Number: (a, x, folder, slug, n) => `"${a}" names "${x}", but ${folder}/${slug}.md has no AC${n}.`,
|
|
626
|
+
C2: (a, x) => `"${a}" names "${x}", which was retired. Remove it, or name the criterion that replaced it.`,
|
|
627
|
+
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
|
+
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
|
+
C5: (a, x, slug) => `"${a}" names "${x}", but "${slug}" is not in the plan's specs. Add it to "specs" if it belongs to this phase, or remove the citation.`,
|
|
630
|
+
C6None: (folder) => `The plan does not say which feature designs this phase has. Add "specs" with the name of each file in ${folder}/ this phase builds, without ".md". If it has none, write "specs": "${TASK_PLAN_SPECS_NONE}".`,
|
|
631
|
+
C6Malformed: (x, folder) => `The plan's specs name "${x}", which is not a spec name. Use the name of a file in ${folder}/, without ".md".`,
|
|
632
|
+
C6Missing: (slug, folder) => `The plan's specs name "${slug}", but there is no ${folder}/${slug}.md.`,
|
|
633
|
+
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
|
+
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
|
+
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.`,
|
|
636
|
+
N3: (s) => `"${s}" has no Boundary section, so what it owns was not checked.`,
|
|
589
637
|
K1: (a) => `"${a}" is in no batch: no whole-project check waits for it. Add it to the dependsOn of the first whole-project check that it does not already wait for and that waits for every build task it waits for, or add a whole-project check after it.`,
|
|
590
638
|
K2: (a, b) => `"${a}" and "${b}" are both whole-project checks and neither waits for the other. Make the later one wait for the earlier.`,
|
|
591
639
|
K3: (b, a, c) => `"${b}" builds on "${a}" before "${c}" has checked it. Add "${c}" to the dependsOn of "${b}".`,
|
|
@@ -593,6 +641,19 @@ var taskPlanLineText = {
|
|
|
593
641
|
K4: (a, b) => `"${a}" is a hand check but waits for "${b}", so it would run late. A hand check waits only for the work it checks.`,
|
|
594
642
|
K5: (a) => `"${a}" releases to users and no walk comes before it. Add a walk that waits for every deploy before it, and make "${a}" wait for that walk.`,
|
|
595
643
|
K6: (a, b, c) => `"${b}" is the walk before "${a}" but does not wait for the deploy "${c}". Make "${b}" wait for "${c}".`,
|
|
644
|
+
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
|
+
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
|
+
V3Unordered: (c, b) => `"${c}" checks the finished phase, but "${b}" could still be running while it does. Add "${b}" to the dependsOn of "${c}", or, if "${b}" fixes what "${c}" found, name it in ${CRITERIA_RECORD_FILE} and make it wait for "${c}".`,
|
|
647
|
+
V3Ships: (b, c) => `"${b}" ships to users and waits for "${c}", the check of the finished phase. A deploy or a release comes before that check, so the check sees what is live: add "${b}" to the dependsOn of "${c}" and stop "${b}" waiting for "${c}".`,
|
|
648
|
+
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
|
+
V4: (x) => `"${x}" does not say which crew member holds it. Add "agent" with that member's callsign.`,
|
|
650
|
+
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.`,
|
|
651
|
+
V6Walk: (c, n, ids, w) => `${CRITERIA_RECORD_FILE} says "${c}" turned ${n} criteria into fix tasks: ${ids.join(", ")}. "${w}" must wait for a new check that waits for each of them: add a task of kind "check" with "criteriaCheck": true that waits for ${ids.join(", ")}, and add it to the dependsOn of "${w}".`,
|
|
652
|
+
V6Missing: (x) => `${CRITERIA_RECORD_FILE} names "${x}" as a fix task, and the plan has no build task "${x}". Add it, citing the criterion it fixes.`,
|
|
653
|
+
E1: (crit) => `${crit} is an edge case, and no task writes its test before it is built. Add a build task, held by a crew member who builds none of ${crit}, that writes the test and names ${crit} in its criteria, and make each task that builds ${crit} wait for it.`,
|
|
654
|
+
E2: (t, crit, m, b) => `"${t}" writes the test for ${crit}, an edge case, but ${m} also holds "${b}", which builds it. Give "${t}" to a crew member who builds none of ${crit}.`,
|
|
655
|
+
J1: (a) => `"${a}" says "cheapFirst", but it is not a build task. Remove "cheapFirst".`,
|
|
656
|
+
J2: (a, m) => `"${a}" says "cheapFirst", but ${m} is not on the standard tier. Only a standard-tier task can start on the cheaper tier. Remove "cheapFirst" or give "${a}" to a standard-tier crew member.`,
|
|
596
657
|
P: () => "The plan is clear: no two tasks that could run at once change the same thing, and every check is in place."
|
|
597
658
|
};
|
|
598
659
|
function isMalformedPlanPath(path) {
|
|
@@ -610,10 +671,10 @@ function planInterfaceKey(text) {
|
|
|
610
671
|
return null;
|
|
611
672
|
return `${kind}: ${name}`.toLowerCase();
|
|
612
673
|
}
|
|
613
|
-
function
|
|
674
|
+
function pathItemsOf(paths) {
|
|
614
675
|
const files = [];
|
|
615
676
|
const folders = [];
|
|
616
|
-
for (const path of
|
|
677
|
+
for (const path of paths) {
|
|
617
678
|
if (isMalformedPlanPath(path))
|
|
618
679
|
continue;
|
|
619
680
|
const item = { shown: path, key: path.toLowerCase() };
|
|
@@ -621,6 +682,10 @@ function comparableItemsOf(task) {
|
|
|
621
682
|
if (item.key.endsWith("/"))
|
|
622
683
|
folders.push(item);
|
|
623
684
|
}
|
|
685
|
+
return { files, folders };
|
|
686
|
+
}
|
|
687
|
+
function comparableItemsOf(task) {
|
|
688
|
+
const { files, folders } = pathItemsOf(task.touches?.files ?? []);
|
|
624
689
|
const interfaces = [];
|
|
625
690
|
for (const text of task.touches?.interfaces ?? []) {
|
|
626
691
|
const key = planInterfaceKey(text);
|
|
@@ -657,6 +722,31 @@ function firstSharedItem(a, b) {
|
|
|
657
722
|
}
|
|
658
723
|
return null;
|
|
659
724
|
}
|
|
725
|
+
function sharedPaths(a, b) {
|
|
726
|
+
const out = [];
|
|
727
|
+
const seen = /* @__PURE__ */ new Set();
|
|
728
|
+
const add = (item) => {
|
|
729
|
+
if (seen.has(item.key))
|
|
730
|
+
return;
|
|
731
|
+
seen.add(item.key);
|
|
732
|
+
out.push(item.shown);
|
|
733
|
+
};
|
|
734
|
+
for (const file of a.files) {
|
|
735
|
+
if (b.fileKeys.has(file.key))
|
|
736
|
+
add(file);
|
|
737
|
+
if (file.key.endsWith("/")) {
|
|
738
|
+
for (const other of b.files) {
|
|
739
|
+
if (other.key.startsWith(file.key))
|
|
740
|
+
add(other);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
for (const folder of b.folders) {
|
|
744
|
+
if (file.key.startsWith(folder.key))
|
|
745
|
+
add(file);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
return out;
|
|
749
|
+
}
|
|
660
750
|
function waitsFor(tasks) {
|
|
661
751
|
const n = tasks.length;
|
|
662
752
|
const positionsOf = /* @__PURE__ */ new Map();
|
|
@@ -794,10 +884,10 @@ function firstDifference(plan, list2) {
|
|
|
794
884
|
const listIds = new Set(list2.map((task) => task.id));
|
|
795
885
|
for (const task of plan)
|
|
796
886
|
if (!listIds.has(task.id))
|
|
797
|
-
return `"${task.id}"`;
|
|
887
|
+
return `"${task.id}" is in the plan but not in the task list`;
|
|
798
888
|
for (const task of list2)
|
|
799
889
|
if (!planIds.has(task.id))
|
|
800
|
-
return `"${task.id}"`;
|
|
890
|
+
return `"${task.id}" is in the task list but not in the plan`;
|
|
801
891
|
const listDeps = /* @__PURE__ */ new Map();
|
|
802
892
|
for (const task of list2) {
|
|
803
893
|
const deps = listDeps.get(task.id) ?? /* @__PURE__ */ new Set();
|
|
@@ -814,18 +904,23 @@ function firstDifference(plan, list2) {
|
|
|
814
904
|
}
|
|
815
905
|
for (const task of plan) {
|
|
816
906
|
const theirs = listDeps.get(task.id);
|
|
817
|
-
for (const dep of task.dependsOn)
|
|
907
|
+
for (const dep of task.dependsOn) {
|
|
818
908
|
if (!theirs.has(dep))
|
|
819
|
-
return `"${task.id}"
|
|
909
|
+
return `in the plan "${task.id}" waits for "${dep}", and in the task list it does not`;
|
|
910
|
+
}
|
|
820
911
|
}
|
|
821
912
|
for (const task of list2) {
|
|
822
913
|
const ours = planDeps.get(task.id);
|
|
823
|
-
for (const dep of task.dependsOn)
|
|
914
|
+
for (const dep of task.dependsOn) {
|
|
824
915
|
if (!ours.has(dep))
|
|
825
|
-
return `"${task.id}"
|
|
916
|
+
return `in the task list "${task.id}" waits for "${dep}", and in the plan it does not`;
|
|
917
|
+
}
|
|
826
918
|
}
|
|
827
919
|
return null;
|
|
828
920
|
}
|
|
921
|
+
function note(code, text) {
|
|
922
|
+
return { code, text, refuses: false };
|
|
923
|
+
}
|
|
829
924
|
function refusal(code, text) {
|
|
830
925
|
return { code, text, refuses: true };
|
|
831
926
|
}
|
|
@@ -838,6 +933,249 @@ function mustNameParts(task) {
|
|
|
838
933
|
function namesNoPart(task) {
|
|
839
934
|
return task.builds === void 0 || Array.isArray(task.builds) && task.builds.length === 0;
|
|
840
935
|
}
|
|
936
|
+
function partsNotInPlan(tasks, planPartIds) {
|
|
937
|
+
const planned = new Set(planPartIds);
|
|
938
|
+
const out = [];
|
|
939
|
+
for (const task of tasks) {
|
|
940
|
+
if (task.reviews !== void 0 || task.kind === "review")
|
|
941
|
+
continue;
|
|
942
|
+
if (!Array.isArray(task.builds))
|
|
943
|
+
continue;
|
|
944
|
+
if (task.whyOutsidePlan !== void 0 && task.whyOutsidePlan.trim() !== "")
|
|
945
|
+
continue;
|
|
946
|
+
const parts = [...new Set(task.builds.filter((part) => !planned.has(part)))];
|
|
947
|
+
if (parts.length > 0)
|
|
948
|
+
out.push({ id: task.id, parts });
|
|
949
|
+
}
|
|
950
|
+
return out;
|
|
951
|
+
}
|
|
952
|
+
function namesNoCriterion(task) {
|
|
953
|
+
return task.criteria === void 0 || Array.isArray(task.criteria) && task.criteria.length === 0;
|
|
954
|
+
}
|
|
955
|
+
function isPlanSpecName(text) {
|
|
956
|
+
return SPEC_NAME.test(text);
|
|
957
|
+
}
|
|
958
|
+
function planSpecNamesToRead(plan) {
|
|
959
|
+
if (!Array.isArray(plan.specs))
|
|
960
|
+
return [];
|
|
961
|
+
return distinct(plan.specs.filter(isPlanSpecName));
|
|
962
|
+
}
|
|
963
|
+
var CRITERIA_HEADING = /^##\s+Acceptance criteria/;
|
|
964
|
+
var SECTION_HEADING = /^##\s/;
|
|
965
|
+
var ANY_HEADING = /^#{1,6}\s/;
|
|
966
|
+
var CHECKLIST_ITEM = /^- \[[ x]\] /;
|
|
967
|
+
var EXPLICIT_NUMBER = /^\*\*AC((?:0|[1-9][0-9]*)[a-z]?)( \(edge case\))?(?: —|\.|:)/;
|
|
968
|
+
var RETIRED_LEAD = /^\*\*AC(?:0|[1-9][0-9]*)[a-z]?(?: \(edge case\))? — retired \d{4}-\d{2}-\d{2}/;
|
|
969
|
+
function readSpecCriteria(markdown) {
|
|
970
|
+
const lines = markdown.split(/\r?\n/);
|
|
971
|
+
const start = lines.findIndex((line) => CRITERIA_HEADING.test(line));
|
|
972
|
+
if (start < 0)
|
|
973
|
+
return [];
|
|
974
|
+
const items = [];
|
|
975
|
+
let current = null;
|
|
976
|
+
const close = () => {
|
|
977
|
+
if (current !== null)
|
|
978
|
+
items.push(current);
|
|
979
|
+
current = null;
|
|
980
|
+
};
|
|
981
|
+
for (let i = start + 1; i < lines.length; i += 1) {
|
|
982
|
+
const line = lines[i];
|
|
983
|
+
if (SECTION_HEADING.test(line))
|
|
984
|
+
break;
|
|
985
|
+
if (CHECKLIST_ITEM.test(line)) {
|
|
986
|
+
close();
|
|
987
|
+
current = line.slice("- [ ] ".length);
|
|
988
|
+
continue;
|
|
989
|
+
}
|
|
990
|
+
if (line.trim() === "" || ANY_HEADING.test(line)) {
|
|
991
|
+
close();
|
|
992
|
+
continue;
|
|
993
|
+
}
|
|
994
|
+
if (current !== null)
|
|
995
|
+
current = `${current} ${line.trim()}`;
|
|
996
|
+
}
|
|
997
|
+
close();
|
|
998
|
+
return items.map((item, i) => {
|
|
999
|
+
const explicit = EXPLICIT_NUMBER.exec(item);
|
|
1000
|
+
return {
|
|
1001
|
+
number: explicit ? explicit[1] : String(i + 1),
|
|
1002
|
+
retired: RETIRED_LEAD.test(item),
|
|
1003
|
+
kind: explicit?.[2] !== void 0 ? "edge" : "normal"
|
|
1004
|
+
};
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
function liveCriterionNumbers(criteria) {
|
|
1008
|
+
const live = new Set(criteria.filter((c) => !c.retired).map((c) => c.number));
|
|
1009
|
+
return distinct(criteria.map((c) => c.number)).filter((n) => live.has(n));
|
|
1010
|
+
}
|
|
1011
|
+
function liveEdgeCriterionNumbers(criteria) {
|
|
1012
|
+
const edge = new Set(criteria.filter((c) => !c.retired && c.kind === "edge").map((c) => c.number));
|
|
1013
|
+
return liveCriterionNumbers(criteria).filter((n) => edge.has(n));
|
|
1014
|
+
}
|
|
1015
|
+
function specLines(plan, specFiles) {
|
|
1016
|
+
const folder = specFiles.folder;
|
|
1017
|
+
if (plan.specs === TASK_PLAN_SPECS_NONE)
|
|
1018
|
+
return [];
|
|
1019
|
+
if (plan.specs === void 0 || plan.specs.length === 0) {
|
|
1020
|
+
return [refusal("C6", taskPlanLineText.C6None(folder))];
|
|
1021
|
+
}
|
|
1022
|
+
const criteriaOf = specCriteriaBySlug(specFiles);
|
|
1023
|
+
const lines = [];
|
|
1024
|
+
for (const name of distinct(plan.specs)) {
|
|
1025
|
+
if (!isPlanSpecName(name)) {
|
|
1026
|
+
lines.push(refusal("C6", taskPlanLineText.C6Malformed(name, folder)));
|
|
1027
|
+
} else if ((criteriaOf.get(name) ?? null) === null) {
|
|
1028
|
+
lines.push(refusal("C6", taskPlanLineText.C6Missing(name, folder)));
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
const covered = /* @__PURE__ */ new Set();
|
|
1032
|
+
for (const task of plan.tasks) {
|
|
1033
|
+
if (task.kind !== "build" || !Array.isArray(task.criteria))
|
|
1034
|
+
continue;
|
|
1035
|
+
for (const citation of task.criteria)
|
|
1036
|
+
covered.add(citation);
|
|
1037
|
+
}
|
|
1038
|
+
for (const slug of planSpecNamesToRead(plan)) {
|
|
1039
|
+
const criteria = criteriaOf.get(slug) ?? null;
|
|
1040
|
+
if (criteria === null)
|
|
1041
|
+
continue;
|
|
1042
|
+
for (const n of liveCriterionNumbers(criteria)) {
|
|
1043
|
+
if (!covered.has(`${slug} AC${n}`))
|
|
1044
|
+
lines.push(refusal("C3", taskPlanLineText.C3(slug, n)));
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
return lines;
|
|
1048
|
+
}
|
|
1049
|
+
function specCriteriaBySlug(specFiles) {
|
|
1050
|
+
const bySlug = /* @__PURE__ */ new Map();
|
|
1051
|
+
for (const entry of specFiles.entries) {
|
|
1052
|
+
if (!bySlug.has(entry.slug))
|
|
1053
|
+
bySlug.set(entry.slug, entry.criteria);
|
|
1054
|
+
}
|
|
1055
|
+
return bySlug;
|
|
1056
|
+
}
|
|
1057
|
+
function citationLine(taskId, citation, plan, specFiles, criteriaOf) {
|
|
1058
|
+
const match = CITATION.exec(citation);
|
|
1059
|
+
if (match === null) {
|
|
1060
|
+
const first = Array.isArray(plan.specs) ? plan.specs.find(isPlanSpecName) : void 0;
|
|
1061
|
+
return refusal("C1", taskPlanLineText.C1Form(taskId, citation, first ?? EXAMPLE_SPEC));
|
|
1062
|
+
}
|
|
1063
|
+
if (plan.specs === void 0 || plan.specs.length === 0)
|
|
1064
|
+
return null;
|
|
1065
|
+
const slug = match[1];
|
|
1066
|
+
const n = match[2];
|
|
1067
|
+
if (plan.specs === TASK_PLAN_SPECS_NONE || !plan.specs.includes(slug)) {
|
|
1068
|
+
return refusal("C5", taskPlanLineText.C5(taskId, citation, slug));
|
|
1069
|
+
}
|
|
1070
|
+
const criteria = criteriaOf.get(slug) ?? null;
|
|
1071
|
+
if (criteria === null)
|
|
1072
|
+
return null;
|
|
1073
|
+
const items = criteria.filter((c) => c.number === n);
|
|
1074
|
+
if (items.length === 0) {
|
|
1075
|
+
return refusal("C1", taskPlanLineText.C1Number(taskId, citation, specFiles.folder, slug, n));
|
|
1076
|
+
}
|
|
1077
|
+
if (items.every((c) => c.retired))
|
|
1078
|
+
return refusal("C2", taskPlanLineText.C2(taskId, citation));
|
|
1079
|
+
return null;
|
|
1080
|
+
}
|
|
1081
|
+
function shownFileText(text) {
|
|
1082
|
+
return text.replace(/[\u0000-\u001f\u007f-\u009f]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
1083
|
+
}
|
|
1084
|
+
function ownedPathsBySpec(plan, specFiles) {
|
|
1085
|
+
const boundaryOf = boundaryBySlug(specFiles);
|
|
1086
|
+
const owned = /* @__PURE__ */ new Map();
|
|
1087
|
+
for (const slug of planSpecNamesToRead(plan)) {
|
|
1088
|
+
const boundary = boundaryOf.get(slug) ?? null;
|
|
1089
|
+
if (boundary === null || boundary.kind !== "section")
|
|
1090
|
+
continue;
|
|
1091
|
+
const paths = [];
|
|
1092
|
+
for (const item of boundary.owns) {
|
|
1093
|
+
if (item.kind === "path" && !isMalformedOwnsPath(item.path))
|
|
1094
|
+
paths.push(item.path);
|
|
1095
|
+
}
|
|
1096
|
+
owned.set(slug, paths);
|
|
1097
|
+
}
|
|
1098
|
+
return owned;
|
|
1099
|
+
}
|
|
1100
|
+
function isMalformedOwnsPath(path) {
|
|
1101
|
+
return isMalformedPlanPath(path) || CONTROL_CHARACTER.test(path);
|
|
1102
|
+
}
|
|
1103
|
+
function boundaryBySlug(specFiles) {
|
|
1104
|
+
const bySlug = /* @__PURE__ */ new Map();
|
|
1105
|
+
for (const entry of specFiles.entries) {
|
|
1106
|
+
if (!bySlug.has(entry.slug))
|
|
1107
|
+
bySlug.set(entry.slug, entry.boundary);
|
|
1108
|
+
}
|
|
1109
|
+
return bySlug;
|
|
1110
|
+
}
|
|
1111
|
+
function ownershipLines(plan, specFiles) {
|
|
1112
|
+
const lines = [];
|
|
1113
|
+
const boundaryOf = boundaryBySlug(specFiles);
|
|
1114
|
+
for (const slug of planSpecNamesToRead(plan)) {
|
|
1115
|
+
const boundary = boundaryOf.get(slug) ?? null;
|
|
1116
|
+
if (boundary === null || boundary.kind !== "section")
|
|
1117
|
+
continue;
|
|
1118
|
+
for (const item of boundary.owns) {
|
|
1119
|
+
if (item.kind === "bad") {
|
|
1120
|
+
lines.push(refusal("B2", taskPlanLineText.B2(slug, shownFileText(item.text))));
|
|
1121
|
+
} else if (isMalformedOwnsPath(item.path)) {
|
|
1122
|
+
lines.push(refusal("B2", taskPlanLineText.B2(slug, shownFileText(item.path))));
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
const owned = [...ownedPathsBySpec(plan, specFiles)].map(([slug, paths]) => {
|
|
1127
|
+
const { files, folders } = pathItemsOf(paths);
|
|
1128
|
+
const items = {
|
|
1129
|
+
files,
|
|
1130
|
+
fileKeys: new Set(files.map((f) => f.key)),
|
|
1131
|
+
folders,
|
|
1132
|
+
interfaces: [],
|
|
1133
|
+
interfaceKeys: /* @__PURE__ */ new Set()
|
|
1134
|
+
};
|
|
1135
|
+
return { slug, items };
|
|
1136
|
+
});
|
|
1137
|
+
for (let i = 0; i < owned.length; i += 1) {
|
|
1138
|
+
for (let j = i + 1; j < owned.length; j += 1) {
|
|
1139
|
+
const shared = sharedPaths(owned[i].items, owned[j].items);
|
|
1140
|
+
if (shared.length > 0) {
|
|
1141
|
+
lines.push(refusal("B1", taskPlanLineText.B1(owned[i].slug, owned[j].slug, shared[0], shared.length - 1)));
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
return lines;
|
|
1146
|
+
}
|
|
1147
|
+
function ownershipNotes(plan, specFiles) {
|
|
1148
|
+
const lines = [];
|
|
1149
|
+
const boundaryOf = boundaryBySlug(specFiles);
|
|
1150
|
+
for (const slug of planSpecNamesToRead(plan)) {
|
|
1151
|
+
if (boundaryOf.get(slug)?.kind === "none")
|
|
1152
|
+
lines.push(note("N3", taskPlanLineText.N3(slug)));
|
|
1153
|
+
}
|
|
1154
|
+
const owned = ownedPathsBySpec(plan, specFiles);
|
|
1155
|
+
const listed = new Set(planSpecNamesToRead(plan));
|
|
1156
|
+
for (const task of plan.tasks) {
|
|
1157
|
+
if (task.kind !== "build" || !Array.isArray(task.criteria))
|
|
1158
|
+
continue;
|
|
1159
|
+
const ownPaths = [];
|
|
1160
|
+
let anySection = false;
|
|
1161
|
+
for (const slug of distinct(task.criteria.map((c) => CITATION.exec(c)?.[1] ?? ""))) {
|
|
1162
|
+
const paths = listed.has(slug) ? owned.get(slug) : void 0;
|
|
1163
|
+
if (paths === void 0)
|
|
1164
|
+
continue;
|
|
1165
|
+
anySection = true;
|
|
1166
|
+
ownPaths.push(...paths);
|
|
1167
|
+
}
|
|
1168
|
+
if (!anySection)
|
|
1169
|
+
continue;
|
|
1170
|
+
const { files: ownedFiles, folders: ownedFolders } = pathItemsOf(ownPaths);
|
|
1171
|
+
const ownedKeys = new Set(ownedFiles.map((f) => f.key));
|
|
1172
|
+
const unowned = pathItemsOf(task.touches?.files ?? []).files.filter((touched) => !ownedKeys.has(touched.key) && !ownedFolders.some((folder) => touched.key.startsWith(folder.key)));
|
|
1173
|
+
if (unowned.length > 0) {
|
|
1174
|
+
lines.push(note("N2", taskPlanLineText.N2(task.id, distinct(unowned.map((f) => f.shown)))));
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
return lines;
|
|
1178
|
+
}
|
|
841
1179
|
function isWholeProjectCheck(task) {
|
|
842
1180
|
return task.kind === "check" && task.wholeProject === true;
|
|
843
1181
|
}
|
|
@@ -966,6 +1304,173 @@ function placementLines(tasks, before) {
|
|
|
966
1304
|
});
|
|
967
1305
|
return lines;
|
|
968
1306
|
}
|
|
1307
|
+
function isCriteriaCheck(task) {
|
|
1308
|
+
return task.kind === "check" && task.criteriaCheck === true;
|
|
1309
|
+
}
|
|
1310
|
+
function lacksCriteriaCheck(tasks) {
|
|
1311
|
+
return tasks.some((task) => task.kind === "build") && !tasks.some(isCriteriaCheck);
|
|
1312
|
+
}
|
|
1313
|
+
function mustNameHolder(task) {
|
|
1314
|
+
return mustNameParts(task) || isCriteriaCheck(task);
|
|
1315
|
+
}
|
|
1316
|
+
function criteriaCheckLines(tasks, before, record2) {
|
|
1317
|
+
const lines = [];
|
|
1318
|
+
const waits = (x, y) => hasBit(before[x], y);
|
|
1319
|
+
const positions = (keep) => {
|
|
1320
|
+
const out = [];
|
|
1321
|
+
tasks.forEach((task, i) => {
|
|
1322
|
+
if (keep(task))
|
|
1323
|
+
out.push(i);
|
|
1324
|
+
});
|
|
1325
|
+
return out;
|
|
1326
|
+
};
|
|
1327
|
+
const builds = positions((task) => task.kind === "build");
|
|
1328
|
+
const checks = positions(isCriteriaCheck);
|
|
1329
|
+
const walks = positions((task) => task.kind === "walk");
|
|
1330
|
+
const releases = positions((task) => task.ships === "release");
|
|
1331
|
+
const exempt = (w) => releases.some((r) => waits(r, w));
|
|
1332
|
+
if (lacksCriteriaCheck(tasks)) {
|
|
1333
|
+
lines.push(refusal("V1", taskPlanLineText.V1()));
|
|
1334
|
+
} else {
|
|
1335
|
+
const last = [...checks].reverse().find((c) => !checks.some((other) => other !== c && waits(other, c)));
|
|
1336
|
+
const named2 = last ?? checks[checks.length - 1];
|
|
1337
|
+
for (const w of walks) {
|
|
1338
|
+
if (exempt(w) || checks.some((c) => waits(w, c)) || named2 === void 0)
|
|
1339
|
+
continue;
|
|
1340
|
+
lines.push(refusal("V2", taskPlanLineText.V2(tasks[w].id, tasks[named2].id)));
|
|
1341
|
+
}
|
|
1342
|
+
const fixes = new Set(record2?.fixTasks ?? []);
|
|
1343
|
+
for (const c of checks) {
|
|
1344
|
+
const cid = tasks[c].id;
|
|
1345
|
+
for (const b of builds) {
|
|
1346
|
+
const bid = tasks[b].id;
|
|
1347
|
+
const checkWaits = waits(c, b);
|
|
1348
|
+
const taskWaits = waits(b, c);
|
|
1349
|
+
if (!checkWaits && !taskWaits) {
|
|
1350
|
+
lines.push(refusal("V3", taskPlanLineText.V3Unordered(cid, bid)));
|
|
1351
|
+
} else if (taskWaits && !fixes.has(bid)) {
|
|
1352
|
+
if (tasks[b].ships !== void 0) {
|
|
1353
|
+
lines.push(refusal("V3", taskPlanLineText.V3Ships(bid, cid)));
|
|
1354
|
+
} else if (!checks.some((other) => other !== c && waits(other, b))) {
|
|
1355
|
+
lines.push(refusal("V3", taskPlanLineText.V3Unchecked(bid, cid)));
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
for (const c of checks) {
|
|
1361
|
+
const holder = tasks[c].agent;
|
|
1362
|
+
if (holder === void 0)
|
|
1363
|
+
continue;
|
|
1364
|
+
const b = builds.find((p) => tasks[p].agent === holder);
|
|
1365
|
+
if (b !== void 0)
|
|
1366
|
+
lines.push(refusal("V5", taskPlanLineText.V5(tasks[c].id, holder, tasks[b].id)));
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
if (record2 !== void 0) {
|
|
1370
|
+
const buildOf = /* @__PURE__ */ new Map();
|
|
1371
|
+
for (const b of builds)
|
|
1372
|
+
if (!buildOf.has(tasks[b].id))
|
|
1373
|
+
buildOf.set(tasks[b].id, b);
|
|
1374
|
+
const ids = distinct(record2.fixTasks);
|
|
1375
|
+
for (const id of ids) {
|
|
1376
|
+
if (!buildOf.has(id))
|
|
1377
|
+
lines.push(refusal("V6", taskPlanLineText.V6Missing(id)));
|
|
1378
|
+
}
|
|
1379
|
+
if (record2.fixTasks.length > 0) {
|
|
1380
|
+
const heldBehindFixes = (c) => tasks[c].id !== record2.check && ids.every((id) => {
|
|
1381
|
+
const f = buildOf.get(id);
|
|
1382
|
+
return f !== void 0 && waits(c, f);
|
|
1383
|
+
});
|
|
1384
|
+
for (const w of walks) {
|
|
1385
|
+
if (exempt(w) || checks.some((c) => waits(w, c) && heldBehindFixes(c)))
|
|
1386
|
+
continue;
|
|
1387
|
+
lines.push(refusal("V6", taskPlanLineText.V6Walk(record2.check, record2.fixTasks.length, ids, tasks[w].id)));
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
return lines;
|
|
1392
|
+
}
|
|
1393
|
+
function edgeCriteriaAt(plan, specFiles, before) {
|
|
1394
|
+
const criteriaOf = specCriteriaBySlug(specFiles);
|
|
1395
|
+
const out = [];
|
|
1396
|
+
for (const slug of planSpecNamesToRead(plan)) {
|
|
1397
|
+
const criteria = criteriaOf.get(slug) ?? null;
|
|
1398
|
+
if (criteria === null)
|
|
1399
|
+
continue;
|
|
1400
|
+
for (const n of liveEdgeCriterionNumbers(criteria)) {
|
|
1401
|
+
const criterion = `${slug} AC${n}`;
|
|
1402
|
+
const builders = [];
|
|
1403
|
+
plan.tasks.forEach((task, i) => {
|
|
1404
|
+
if (task.kind === "build" && Array.isArray(task.criteria) && task.criteria.includes(criterion)) {
|
|
1405
|
+
builders.push(i);
|
|
1406
|
+
}
|
|
1407
|
+
});
|
|
1408
|
+
const testTask = builders.length < 2 ? void 0 : builders.find((t) => builders.every((b) => b === t || hasBit(before[b], t)));
|
|
1409
|
+
out.push({ criterion, slug, builders, testTask: testTask ?? null });
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
return out;
|
|
1413
|
+
}
|
|
1414
|
+
function edgeCriterionTests(plan, specFiles) {
|
|
1415
|
+
const { before } = waitsFor(plan.tasks);
|
|
1416
|
+
return edgeCriteriaAt(plan, specFiles, before).map((edge) => ({
|
|
1417
|
+
criterion: edge.criterion,
|
|
1418
|
+
slug: edge.slug,
|
|
1419
|
+
builders: edge.builders.map((i) => plan.tasks[i].id),
|
|
1420
|
+
testTask: edge.testTask === null ? null : plan.tasks[edge.testTask].id
|
|
1421
|
+
}));
|
|
1422
|
+
}
|
|
1423
|
+
function edgeCaseLines(plan, specFiles, before) {
|
|
1424
|
+
const tasks = plan.tasks;
|
|
1425
|
+
const lines = [];
|
|
1426
|
+
for (const edge of edgeCriteriaAt(plan, specFiles, before)) {
|
|
1427
|
+
if (edge.builders.length === 0)
|
|
1428
|
+
continue;
|
|
1429
|
+
if (edge.testTask === null) {
|
|
1430
|
+
lines.push(refusal("E1", taskPlanLineText.E1(edge.criterion)));
|
|
1431
|
+
continue;
|
|
1432
|
+
}
|
|
1433
|
+
const t = edge.testTask;
|
|
1434
|
+
const holder = tasks[t].agent;
|
|
1435
|
+
if (holder === void 0)
|
|
1436
|
+
continue;
|
|
1437
|
+
const b = edge.builders.find((p) => p !== t && tasks[p].agent === holder);
|
|
1438
|
+
if (b !== void 0) {
|
|
1439
|
+
lines.push(refusal("E2", taskPlanLineText.E2(tasks[t].id, edge.criterion, holder, tasks[b].id)));
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
return lines;
|
|
1443
|
+
}
|
|
1444
|
+
function phasePlanUnusableWhy(unusable, asked) {
|
|
1445
|
+
switch (unusable.kind) {
|
|
1446
|
+
case "unreadable":
|
|
1447
|
+
return "it could not be opened";
|
|
1448
|
+
case "not-json":
|
|
1449
|
+
return shownFileText(`it is not valid JSON (${unusable.error})`);
|
|
1450
|
+
case "other-phase":
|
|
1451
|
+
return shownFileText(`it is the plan for "${unusable.phase}", not "${asked}"`);
|
|
1452
|
+
case "problems": {
|
|
1453
|
+
const shown2 = unusable.problems.slice(0, PHASE_PLAN_PROBLEMS_SHOWN).map((problem) => `${problem.path === "" ? "the plan" : problem.path} ${problem.message}`);
|
|
1454
|
+
const more = unusable.problems.length - shown2.length;
|
|
1455
|
+
if (shown2.length === 0)
|
|
1456
|
+
return "it does not pass the plan's own check";
|
|
1457
|
+
return shownFileText(more > 0 ? `${shown2.join("; ")}; and ${more} more` : shown2.join("; "));
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
function cheapFirstLine(task, crew) {
|
|
1462
|
+
if (task.cheapFirst !== true)
|
|
1463
|
+
return null;
|
|
1464
|
+
if (task.kind !== "build")
|
|
1465
|
+
return refusal("J1", taskPlanLineText.J1(task.id));
|
|
1466
|
+
if (crew === void 0 || task.agent === void 0)
|
|
1467
|
+
return null;
|
|
1468
|
+
const holder = task.agent;
|
|
1469
|
+
const member = crew.find((m) => m.callsign === holder);
|
|
1470
|
+
if (member !== void 0 && member.tier === "standard")
|
|
1471
|
+
return null;
|
|
1472
|
+
return refusal("J2", taskPlanLineText.J2(task.id, holder));
|
|
1473
|
+
}
|
|
969
1474
|
function checkTaskPlan(plan, request) {
|
|
970
1475
|
if (plan.phase !== request.phase) {
|
|
971
1476
|
return { lines: [refusal("O6", taskPlanLineText.O6(plan.phase, request.phase))], refused: true };
|
|
@@ -973,6 +1478,12 @@ function checkTaskPlan(plan, request) {
|
|
|
973
1478
|
const tasks = plan.tasks;
|
|
974
1479
|
const lines = [];
|
|
975
1480
|
const system = request.system;
|
|
1481
|
+
const criteriaOf = specCriteriaBySlug(request.specFiles);
|
|
1482
|
+
const phasePlan = request.phasePlan;
|
|
1483
|
+
const noCriteriaCheck = lacksCriteriaCheck(tasks);
|
|
1484
|
+
if (phasePlan !== void 0 && "unusable" in phasePlan) {
|
|
1485
|
+
lines.push(refusal("O8", taskPlanLineText.O8Unusable(phasePlan.file, phasePlanUnusableWhy(phasePlan.unusable, request.phase))));
|
|
1486
|
+
}
|
|
976
1487
|
const ids = new Set(tasks.map((task) => task.id));
|
|
977
1488
|
const seen = /* @__PURE__ */ new Set();
|
|
978
1489
|
const reportedTwice = /* @__PURE__ */ new Set();
|
|
@@ -1013,7 +1524,36 @@ function checkTaskPlan(plan, request) {
|
|
|
1013
1524
|
if (system !== void 0 && system.partIds.length > 0 && mustNameParts(task) && namesNoPart(task)) {
|
|
1014
1525
|
lines.push(refusal("O7", taskPlanLineText.O7(task.id, system.file, system.partIds)));
|
|
1015
1526
|
}
|
|
1527
|
+
if (phasePlan !== void 0 && "partIds" in phasePlan) {
|
|
1528
|
+
for (const found of partsNotInPlan([task], phasePlan.partIds)) {
|
|
1529
|
+
const known = new Set(system?.partIds ?? found.parts);
|
|
1530
|
+
const noSuchPart = found.parts.filter((part) => !known.has(part));
|
|
1531
|
+
const outside = found.parts.filter((part) => known.has(part));
|
|
1532
|
+
if (system !== void 0 && noSuchPart.length > 0) {
|
|
1533
|
+
lines.push(refusal("O8", taskPlanLineText.O8NoSuchPart(found.id, noSuchPart, system.file, system.partIds)));
|
|
1534
|
+
}
|
|
1535
|
+
if (outside.length > 0)
|
|
1536
|
+
lines.push(refusal("O8", taskPlanLineText.O8(found.id, outside, phasePlan.file)));
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
if (mustNameParts(task) && namesNoCriterion(task)) {
|
|
1540
|
+
lines.push(refusal("C4", taskPlanLineText.C4(task.id)));
|
|
1541
|
+
}
|
|
1542
|
+
if (Array.isArray(task.criteria)) {
|
|
1543
|
+
for (const citation of task.criteria) {
|
|
1544
|
+
const line = citationLine(task.id, citation, plan, request.specFiles, criteriaOf);
|
|
1545
|
+
if (line !== null)
|
|
1546
|
+
lines.push(line);
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
if (!noCriteriaCheck && mustNameHolder(task) && task.agent === void 0) {
|
|
1550
|
+
lines.push(refusal("V4", taskPlanLineText.V4(task.id)));
|
|
1551
|
+
}
|
|
1552
|
+
const cheap = cheapFirstLine(task, request.crew);
|
|
1553
|
+
if (cheap !== null)
|
|
1554
|
+
lines.push(cheap);
|
|
1016
1555
|
}
|
|
1556
|
+
lines.push(...specLines(plan, request.specFiles));
|
|
1017
1557
|
const { before, cycles } = waitsFor(tasks);
|
|
1018
1558
|
for (const path of cycles)
|
|
1019
1559
|
lines.push(refusal("O4", taskPlanLineText.O4Cycle(path)));
|
|
@@ -1034,6 +1574,7 @@ function checkTaskPlan(plan, request) {
|
|
|
1034
1574
|
}
|
|
1035
1575
|
}
|
|
1036
1576
|
}
|
|
1577
|
+
lines.push(...ownershipLines(plan, request.specFiles));
|
|
1037
1578
|
if (request.taskList !== void 0) {
|
|
1038
1579
|
const file = request.taskList.file;
|
|
1039
1580
|
if (file !== null && !Array.isArray(file) && file.phase === request.phase) {
|
|
@@ -1044,13 +1585,269 @@ function checkTaskPlan(plan, request) {
|
|
|
1044
1585
|
lines.push({ code: "N1", text: taskPlanLineText.N1(request.phase), refuses: false });
|
|
1045
1586
|
}
|
|
1046
1587
|
}
|
|
1588
|
+
lines.push(...ownershipNotes(plan, request.specFiles));
|
|
1047
1589
|
lines.push(...placementLines(tasks, before));
|
|
1590
|
+
lines.push(...criteriaCheckLines(tasks, before, request.criteriaRecord));
|
|
1591
|
+
lines.push(...edgeCaseLines(plan, request.specFiles, before));
|
|
1048
1592
|
const refused3 = lines.some((line) => line.refuses);
|
|
1049
1593
|
if (!refused3)
|
|
1050
1594
|
lines.push({ code: "P", text: taskPlanLineText.P(), refuses: false });
|
|
1051
1595
|
return { lines, refused: refused3 };
|
|
1052
1596
|
}
|
|
1053
1597
|
|
|
1598
|
+
// ../core/dist/criteria-record.js
|
|
1599
|
+
var criteriaRecordLineText = {
|
|
1600
|
+
M1: (file, p, q) => `${file} is for "${p}", but the plan is for "${q}". Write the record for this phase.`,
|
|
1601
|
+
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
|
+
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
|
+
M4: (crit) => `${crit} has no row. Check it, and add it as met with what shows it, or as a fix with the gap and the fix task.`,
|
|
1604
|
+
M5: (file, x, folder) => `${file} has a row for "${x}", which is not a live criterion of this phase's features in ${folder}. Remove it, or correct it.`,
|
|
1605
|
+
M6: (crit) => `${crit} has more than one row. Keep one.`,
|
|
1606
|
+
M7: (crit, x) => `${crit} is fixed by "${x}", but the plan has no build task "${x}" that names ${crit} in its criteria. Add the task, or name it there.`,
|
|
1607
|
+
M8Unmarked: (crit, folder, slug) => `${crit} is not marked an edge case in ${folder}/${slug}.md, so its row does not carry "edge".`,
|
|
1608
|
+
M8Marked: (crit, folder, slug) => `${crit} is marked an edge case in ${folder}/${slug}.md, so its row carries "edge": true.`,
|
|
1609
|
+
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
|
+
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
|
+
P: (met, fix) => `The record covers every criterion of this phase's features: ${met} met, ${fix} to fix.`
|
|
1612
|
+
};
|
|
1613
|
+
function criteriaRecordMismatch(record2, plan) {
|
|
1614
|
+
if (record2.subject.phase !== plan.phase)
|
|
1615
|
+
return "phase";
|
|
1616
|
+
if (!plan.tasks.some((task) => task.id === record2.check && isCriteriaCheck(task)))
|
|
1617
|
+
return "check";
|
|
1618
|
+
return null;
|
|
1619
|
+
}
|
|
1620
|
+
function liveCriteria(specFiles) {
|
|
1621
|
+
const out = [];
|
|
1622
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1623
|
+
for (const entry of specFiles.entries) {
|
|
1624
|
+
if (entry.criteria === null || seen.has(entry.slug))
|
|
1625
|
+
continue;
|
|
1626
|
+
seen.add(entry.slug);
|
|
1627
|
+
for (const n of liveCriterionNumbers(entry.criteria))
|
|
1628
|
+
out.push(`${entry.slug} AC${n}`);
|
|
1629
|
+
}
|
|
1630
|
+
return out;
|
|
1631
|
+
}
|
|
1632
|
+
function liveEdgeCriteria(specFiles) {
|
|
1633
|
+
const out = /* @__PURE__ */ new Set();
|
|
1634
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1635
|
+
for (const entry of specFiles.entries) {
|
|
1636
|
+
if (entry.criteria === null || seen.has(entry.slug))
|
|
1637
|
+
continue;
|
|
1638
|
+
seen.add(entry.slug);
|
|
1639
|
+
for (const n of liveEdgeCriterionNumbers(entry.criteria))
|
|
1640
|
+
out.add(`${entry.slug} AC${n}`);
|
|
1641
|
+
}
|
|
1642
|
+
return out;
|
|
1643
|
+
}
|
|
1644
|
+
function slugOf(criterion) {
|
|
1645
|
+
return criterion.slice(0, criterion.lastIndexOf(" AC"));
|
|
1646
|
+
}
|
|
1647
|
+
function refusal2(code, text) {
|
|
1648
|
+
return { code, text, refuses: true };
|
|
1649
|
+
}
|
|
1650
|
+
function edgeRowLines(row, isEdge, testTask, plan, folder) {
|
|
1651
|
+
const crit = row.criterion;
|
|
1652
|
+
const lines = [];
|
|
1653
|
+
const marked = row.edge === true;
|
|
1654
|
+
if (marked && !isEdge)
|
|
1655
|
+
lines.push(refusal2("M8", criteriaRecordLineText.M8Unmarked(crit, folder, slugOf(crit))));
|
|
1656
|
+
if (!marked && isEdge)
|
|
1657
|
+
lines.push(refusal2("M8", criteriaRecordLineText.M8Marked(crit, folder, slugOf(crit))));
|
|
1658
|
+
if (!isEdge || row.status !== "met")
|
|
1659
|
+
return lines;
|
|
1660
|
+
if (testTask === null) {
|
|
1661
|
+
lines.push(refusal2("M9", criteriaRecordLineText.M9NoTestTask(crit)));
|
|
1662
|
+
return lines;
|
|
1663
|
+
}
|
|
1664
|
+
const evidence = row.evidence;
|
|
1665
|
+
const builtBy = (id) => id !== testTask && plan.tasks.some((task) => task.id === id && task.kind === "build" && Array.isArray(task.criteria) && task.criteria.includes(crit));
|
|
1666
|
+
const held = evidence.kind === "test" && evidence.writtenIn === testTask && builtBy(evidence.task);
|
|
1667
|
+
if (!held)
|
|
1668
|
+
lines.push(refusal2("M9", criteriaRecordLineText.M9Evidence(crit, testTask)));
|
|
1669
|
+
return lines;
|
|
1670
|
+
}
|
|
1671
|
+
function checkCriteriaRecord(record2, plan, request) {
|
|
1672
|
+
const { file, specFiles } = request;
|
|
1673
|
+
let met = 0;
|
|
1674
|
+
let fix = 0;
|
|
1675
|
+
for (const row of record2.rows) {
|
|
1676
|
+
if (row.status === "met")
|
|
1677
|
+
met += 1;
|
|
1678
|
+
else
|
|
1679
|
+
fix += 1;
|
|
1680
|
+
}
|
|
1681
|
+
const answer = (lines2) => {
|
|
1682
|
+
const refused3 = lines2.some((line) => line.refuses);
|
|
1683
|
+
return {
|
|
1684
|
+
lines: refused3 ? lines2 : [{ code: "P", text: criteriaRecordLineText.P(met, fix), refuses: false }],
|
|
1685
|
+
refused: refused3,
|
|
1686
|
+
met,
|
|
1687
|
+
fix
|
|
1688
|
+
};
|
|
1689
|
+
};
|
|
1690
|
+
const mismatch = criteriaRecordMismatch(record2, plan);
|
|
1691
|
+
if (mismatch === "phase") {
|
|
1692
|
+
return answer([refusal2("M1", criteriaRecordLineText.M1(file, record2.subject.phase, plan.phase))]);
|
|
1693
|
+
}
|
|
1694
|
+
const lines = [];
|
|
1695
|
+
if (mismatch === "check") {
|
|
1696
|
+
lines.push(refusal2("M2", criteriaRecordLineText.M2(file, record2.check)));
|
|
1697
|
+
} else {
|
|
1698
|
+
const holder = plan.tasks.find((task) => task.id === record2.check)?.agent;
|
|
1699
|
+
if (holder !== void 0 && holder !== record2.by) {
|
|
1700
|
+
lines.push(refusal2("M3", criteriaRecordLineText.M3(file, record2.by, record2.check, holder)));
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
const live = liveCriteria(specFiles);
|
|
1704
|
+
const liveSet = new Set(live);
|
|
1705
|
+
const edges = liveEdgeCriteria(specFiles);
|
|
1706
|
+
const testTaskOf = /* @__PURE__ */ new Map();
|
|
1707
|
+
for (const edge of edgeCriterionTests(plan, specFiles)) {
|
|
1708
|
+
if (!testTaskOf.has(edge.criterion))
|
|
1709
|
+
testTaskOf.set(edge.criterion, edge.testTask);
|
|
1710
|
+
}
|
|
1711
|
+
const named2 = /* @__PURE__ */ new Set();
|
|
1712
|
+
const told = /* @__PURE__ */ new Set();
|
|
1713
|
+
for (const row of record2.rows) {
|
|
1714
|
+
const crit = row.criterion;
|
|
1715
|
+
if (!liveSet.has(crit)) {
|
|
1716
|
+
if (!told.has(crit))
|
|
1717
|
+
lines.push(refusal2("M5", criteriaRecordLineText.M5(file, crit, specFiles.folder)));
|
|
1718
|
+
told.add(crit);
|
|
1719
|
+
named2.add(crit);
|
|
1720
|
+
continue;
|
|
1721
|
+
}
|
|
1722
|
+
if (named2.has(crit)) {
|
|
1723
|
+
if (!told.has(crit))
|
|
1724
|
+
lines.push(refusal2("M6", criteriaRecordLineText.M6(crit)));
|
|
1725
|
+
told.add(crit);
|
|
1726
|
+
}
|
|
1727
|
+
named2.add(crit);
|
|
1728
|
+
lines.push(...edgeRowLines(row, edges.has(crit), testTaskOf.get(crit) ?? null, plan, specFiles.folder));
|
|
1729
|
+
}
|
|
1730
|
+
for (const row of record2.rows) {
|
|
1731
|
+
if (row.status !== "fix" || !liveSet.has(row.criterion))
|
|
1732
|
+
continue;
|
|
1733
|
+
const fixes = plan.tasks.some((task) => task.id === row.task && task.kind === "build" && Array.isArray(task.criteria) && task.criteria.includes(row.criterion));
|
|
1734
|
+
if (!fixes)
|
|
1735
|
+
lines.push(refusal2("M7", criteriaRecordLineText.M7(row.criterion, row.task)));
|
|
1736
|
+
}
|
|
1737
|
+
for (const crit of live) {
|
|
1738
|
+
if (!named2.has(crit))
|
|
1739
|
+
lines.push(refusal2("M4", criteriaRecordLineText.M4(crit)));
|
|
1740
|
+
}
|
|
1741
|
+
return answer(lines);
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
// ../core/dist/spec-boundary.js
|
|
1745
|
+
var SPEC_BOUNDARY_OWNS_LABEL = "**Owns.**";
|
|
1746
|
+
var SPEC_BOUNDARY_LABELS = [
|
|
1747
|
+
SPEC_BOUNDARY_OWNS_LABEL,
|
|
1748
|
+
"**Out of boundary.**",
|
|
1749
|
+
"**Allowed dependencies.**",
|
|
1750
|
+
"**Revalidation triggers.**"
|
|
1751
|
+
];
|
|
1752
|
+
var REPEATED_OWNS_TEXT = "a second **Owns.** label: only the first list is read, so keep every path under one **Owns.** label";
|
|
1753
|
+
var BOUNDARY_HEADING = /^## Boundary[ \t]*$/;
|
|
1754
|
+
var SECTION_HEADING2 = /^## /;
|
|
1755
|
+
var FENCE = /^ {0,3}(`{3,}|~{3,})/;
|
|
1756
|
+
var BULLET = /^- /;
|
|
1757
|
+
var INDENTED = /^[ \t]/;
|
|
1758
|
+
var ONE_SPAN = /^`([^`]+)`$/;
|
|
1759
|
+
function fenceTracker() {
|
|
1760
|
+
let open = null;
|
|
1761
|
+
return (line) => {
|
|
1762
|
+
const match = FENCE.exec(line);
|
|
1763
|
+
if (open === null) {
|
|
1764
|
+
if (match === null)
|
|
1765
|
+
return true;
|
|
1766
|
+
open = match[1];
|
|
1767
|
+
return false;
|
|
1768
|
+
}
|
|
1769
|
+
if (match !== null && match[1][0] === open[0] && match[1].length >= open.length) {
|
|
1770
|
+
const rest = line.slice(line.indexOf(match[1]) + match[1].length);
|
|
1771
|
+
if (rest.trim() === "")
|
|
1772
|
+
open = null;
|
|
1773
|
+
}
|
|
1774
|
+
return false;
|
|
1775
|
+
};
|
|
1776
|
+
}
|
|
1777
|
+
function itemOf(first, continued) {
|
|
1778
|
+
const head = first.trim();
|
|
1779
|
+
if (continued.length > 0)
|
|
1780
|
+
return { kind: "bad", text: [head, ...continued].join(" ") };
|
|
1781
|
+
const span = ONE_SPAN.exec(head);
|
|
1782
|
+
if (span === null)
|
|
1783
|
+
return { kind: "bad", text: head };
|
|
1784
|
+
return { kind: "path", path: span[1] };
|
|
1785
|
+
}
|
|
1786
|
+
function readSpecBoundary(markdown) {
|
|
1787
|
+
const lines = markdown.split(/\r?\n/);
|
|
1788
|
+
const outside = fenceTracker();
|
|
1789
|
+
let start = -1;
|
|
1790
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
1791
|
+
if (outside(lines[i]) && BOUNDARY_HEADING.test(lines[i])) {
|
|
1792
|
+
start = i;
|
|
1793
|
+
break;
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
if (start < 0)
|
|
1797
|
+
return { kind: "none" };
|
|
1798
|
+
const owns = [];
|
|
1799
|
+
let ownsSeen = false;
|
|
1800
|
+
let listOpen = false;
|
|
1801
|
+
let first = null;
|
|
1802
|
+
let continued = [];
|
|
1803
|
+
const closeBullet = () => {
|
|
1804
|
+
if (first !== null)
|
|
1805
|
+
owns.push(itemOf(first, continued));
|
|
1806
|
+
first = null;
|
|
1807
|
+
continued = [];
|
|
1808
|
+
};
|
|
1809
|
+
const closeList = () => {
|
|
1810
|
+
closeBullet();
|
|
1811
|
+
listOpen = false;
|
|
1812
|
+
};
|
|
1813
|
+
for (let i = start + 1; i < lines.length; i += 1) {
|
|
1814
|
+
const line = lines[i];
|
|
1815
|
+
if (!outside(line)) {
|
|
1816
|
+
closeList();
|
|
1817
|
+
continue;
|
|
1818
|
+
}
|
|
1819
|
+
if (SECTION_HEADING2.test(line))
|
|
1820
|
+
break;
|
|
1821
|
+
const trimmed = line.trimEnd();
|
|
1822
|
+
if (SPEC_BOUNDARY_LABELS.includes(trimmed)) {
|
|
1823
|
+
closeList();
|
|
1824
|
+
if (trimmed === SPEC_BOUNDARY_OWNS_LABEL) {
|
|
1825
|
+
if (ownsSeen) {
|
|
1826
|
+
owns.push({ kind: "bad", text: REPEATED_OWNS_TEXT });
|
|
1827
|
+
} else {
|
|
1828
|
+
ownsSeen = true;
|
|
1829
|
+
listOpen = true;
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
continue;
|
|
1833
|
+
}
|
|
1834
|
+
if (!listOpen)
|
|
1835
|
+
continue;
|
|
1836
|
+
if (BULLET.test(line)) {
|
|
1837
|
+
closeBullet();
|
|
1838
|
+
first = line.slice(2);
|
|
1839
|
+
continue;
|
|
1840
|
+
}
|
|
1841
|
+
if (INDENTED.test(line) && line.trim() !== "" && first !== null) {
|
|
1842
|
+
continued.push(line.trim());
|
|
1843
|
+
continue;
|
|
1844
|
+
}
|
|
1845
|
+
closeList();
|
|
1846
|
+
}
|
|
1847
|
+
closeList();
|
|
1848
|
+
return { kind: "section", owns };
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1054
1851
|
// ../core/dist/agent-profile.js
|
|
1055
1852
|
var AGENT_IDS = ["claude-code", "codex"];
|
|
1056
1853
|
var CLAUDE_CODE_PROFILE = {
|
|
@@ -1148,6 +1945,222 @@ var AGENT_PROFILES = {
|
|
|
1148
1945
|
};
|
|
1149
1946
|
var ALL_HOOK_COMMANDS = new Set(AGENT_IDS.flatMap((id) => [...AGENT_PROFILES[id].commands]));
|
|
1150
1947
|
|
|
1948
|
+
// ../core/dist/worker-report.js
|
|
1949
|
+
var WORKER_REPORT_STATUSES = Object.freeze([
|
|
1950
|
+
"done",
|
|
1951
|
+
"needs-human-verification",
|
|
1952
|
+
"blocked-on-human",
|
|
1953
|
+
"failed"
|
|
1954
|
+
]);
|
|
1955
|
+
var WORKER_REPORT_RULE_FIELDS = Object.freeze({
|
|
1956
|
+
W1: "Worker report",
|
|
1957
|
+
W2: "Status",
|
|
1958
|
+
W3: "Head",
|
|
1959
|
+
W4: "Head",
|
|
1960
|
+
W5: "Verification",
|
|
1961
|
+
W6: "Verification",
|
|
1962
|
+
W7: "Verification",
|
|
1963
|
+
W8: "Unverified"
|
|
1964
|
+
});
|
|
1965
|
+
var WORKER_REPORT_LINES = Object.freeze({
|
|
1966
|
+
noBlock: () => 'There is no "## Worker report" block in this report.',
|
|
1967
|
+
statusMissing: (statuses) => `The report has no Status line. Use one of: ${statuses.join(", ")}.`,
|
|
1968
|
+
statusUnknown: (found, statuses) => `The report's Status is "${found}". Use one of: ${statuses.join(", ")}.`,
|
|
1969
|
+
headMissing: () => "The report has no Head line. Give the full commit id the report is about.",
|
|
1970
|
+
headMalformed: (found) => `The report's Head is "${found}", which is not a full commit id.`,
|
|
1971
|
+
headDiffers: (found, head) => `The report is about commit ${found}, but the work's head is ${head}. Ask for a report on the current head.`,
|
|
1972
|
+
noVerification: () => "The report does not say what was run. List each check under Verification with its exit code.",
|
|
1973
|
+
noExitCode: (entry) => `"${entry}" under Verification does not give an exit code.`,
|
|
1974
|
+
nonZeroOnDone: (entry) => `"${entry}" exited with a code other than 0, so the work is not done.`,
|
|
1975
|
+
noUnverified: () => 'The report has no Unverified line. Say what was not checked, or "nothing".',
|
|
1976
|
+
clear: () => "The report is complete and names the commit it is about."
|
|
1977
|
+
});
|
|
1978
|
+
var FIELD_LINE = /^([A-Z][A-Za-z0-9 ]*):(.*)$/;
|
|
1979
|
+
var FULL_COMMIT = /^[0-9a-f]{40}$/;
|
|
1980
|
+
var EXIT_CODE = /(?:→|\bexit(?:ed)?(?:\s*code)?)\s*(\d+)/giu;
|
|
1981
|
+
function extractWorkerReportBlock(body) {
|
|
1982
|
+
const lines = String(body ?? "").replace(/\r\n/g, "\n").split("\n");
|
|
1983
|
+
const start = lines.findIndex((l) => /^##\s+Worker report\s*$/.test(l.trim()));
|
|
1984
|
+
if (start === -1)
|
|
1985
|
+
return null;
|
|
1986
|
+
const out = [];
|
|
1987
|
+
for (const line of lines.slice(start + 1)) {
|
|
1988
|
+
if (/^#{1,3}\s/.test(line.trim()))
|
|
1989
|
+
break;
|
|
1990
|
+
if (/^```/.test(line.trim()))
|
|
1991
|
+
continue;
|
|
1992
|
+
out.push(line);
|
|
1993
|
+
}
|
|
1994
|
+
return out;
|
|
1995
|
+
}
|
|
1996
|
+
function parseWorkerReportFields(blockLines) {
|
|
1997
|
+
const fields = /* @__PURE__ */ new Map();
|
|
1998
|
+
let current = null;
|
|
1999
|
+
for (const raw of blockLines) {
|
|
2000
|
+
const m = FIELD_LINE.exec(raw);
|
|
2001
|
+
if (m) {
|
|
2002
|
+
const name = m[1];
|
|
2003
|
+
if (fields.has(name)) {
|
|
2004
|
+
current = null;
|
|
2005
|
+
continue;
|
|
2006
|
+
}
|
|
2007
|
+
current = { inline: m[2].trim(), continuation: [] };
|
|
2008
|
+
fields.set(name, current);
|
|
2009
|
+
continue;
|
|
2010
|
+
}
|
|
2011
|
+
if (current)
|
|
2012
|
+
current.continuation.push(raw);
|
|
2013
|
+
}
|
|
2014
|
+
return fields;
|
|
2015
|
+
}
|
|
2016
|
+
function splitOutsideBackticks(text) {
|
|
2017
|
+
const parts = [];
|
|
2018
|
+
let buf = "";
|
|
2019
|
+
let inTick = false;
|
|
2020
|
+
for (let i = 0; i < text.length; i++) {
|
|
2021
|
+
const ch = text[i];
|
|
2022
|
+
if (ch === "`")
|
|
2023
|
+
inTick = !inTick;
|
|
2024
|
+
if (!inTick && ch === ";") {
|
|
2025
|
+
parts.push(buf);
|
|
2026
|
+
buf = "";
|
|
2027
|
+
continue;
|
|
2028
|
+
}
|
|
2029
|
+
if (!inTick && text.startsWith(" \xB7 ", i)) {
|
|
2030
|
+
parts.push(buf);
|
|
2031
|
+
buf = "";
|
|
2032
|
+
i += 2;
|
|
2033
|
+
continue;
|
|
2034
|
+
}
|
|
2035
|
+
buf += ch;
|
|
2036
|
+
}
|
|
2037
|
+
parts.push(buf);
|
|
2038
|
+
return parts.map((p) => p.trim()).filter(Boolean);
|
|
2039
|
+
}
|
|
2040
|
+
function verificationEntries(field) {
|
|
2041
|
+
if (!field)
|
|
2042
|
+
return [];
|
|
2043
|
+
const entries = field.inline ? splitOutsideBackticks(field.inline) : [];
|
|
2044
|
+
for (const line of field.continuation) {
|
|
2045
|
+
const m = /^\s*[-*]\s+(.*)$/.exec(line);
|
|
2046
|
+
if (m && m[1].trim())
|
|
2047
|
+
entries.push(m[1].trim());
|
|
2048
|
+
}
|
|
2049
|
+
return entries;
|
|
2050
|
+
}
|
|
2051
|
+
function verificationExitCode(entry) {
|
|
2052
|
+
let code = null;
|
|
2053
|
+
for (const m of entry.matchAll(EXIT_CODE))
|
|
2054
|
+
code = Number(m[1]);
|
|
2055
|
+
return code;
|
|
2056
|
+
}
|
|
2057
|
+
function checkWorkerReport(body, options = {}) {
|
|
2058
|
+
const statuses = options.statuses ?? WORKER_REPORT_STATUSES;
|
|
2059
|
+
const given = options.head ? options.head : null;
|
|
2060
|
+
const problems = [];
|
|
2061
|
+
const add = (id, line) => {
|
|
2062
|
+
problems.push({ id, field: WORKER_REPORT_RULE_FIELDS[id], line });
|
|
2063
|
+
};
|
|
2064
|
+
const block = extractWorkerReportBlock(body);
|
|
2065
|
+
if (block === null) {
|
|
2066
|
+
add("W1", WORKER_REPORT_LINES.noBlock());
|
|
2067
|
+
return { problems, headChecked: false, fields: {} };
|
|
2068
|
+
}
|
|
2069
|
+
const fields = parseWorkerReportFields(block);
|
|
2070
|
+
const inline = (name) => fields.get(name)?.inline;
|
|
2071
|
+
const status = inline("Status");
|
|
2072
|
+
if (status === void 0)
|
|
2073
|
+
add("W2", WORKER_REPORT_LINES.statusMissing(statuses));
|
|
2074
|
+
else if (!statuses.includes(status))
|
|
2075
|
+
add("W2", WORKER_REPORT_LINES.statusUnknown(status, statuses));
|
|
2076
|
+
const head = inline("Head");
|
|
2077
|
+
let headChecked = false;
|
|
2078
|
+
if (head === void 0)
|
|
2079
|
+
add("W3", WORKER_REPORT_LINES.headMissing());
|
|
2080
|
+
else if (!FULL_COMMIT.test(head))
|
|
2081
|
+
add("W3", WORKER_REPORT_LINES.headMalformed(head));
|
|
2082
|
+
else if (given !== null) {
|
|
2083
|
+
headChecked = true;
|
|
2084
|
+
if (head !== given)
|
|
2085
|
+
add("W4", WORKER_REPORT_LINES.headDiffers(head, given));
|
|
2086
|
+
}
|
|
2087
|
+
const entries = verificationEntries(fields.get("Verification"));
|
|
2088
|
+
if (entries.length === 0)
|
|
2089
|
+
add("W5", WORKER_REPORT_LINES.noVerification());
|
|
2090
|
+
const codes = entries.map((entry) => ({ entry, code: verificationExitCode(entry) }));
|
|
2091
|
+
for (const { entry, code } of codes) {
|
|
2092
|
+
if (code === null)
|
|
2093
|
+
add("W6", WORKER_REPORT_LINES.noExitCode(entry));
|
|
2094
|
+
}
|
|
2095
|
+
if (status === "done") {
|
|
2096
|
+
for (const { entry, code } of codes) {
|
|
2097
|
+
if (code !== null && code !== 0)
|
|
2098
|
+
add("W7", WORKER_REPORT_LINES.nonZeroOnDone(entry));
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
if (!fields.has("Unverified"))
|
|
2102
|
+
add("W8", WORKER_REPORT_LINES.noUnverified());
|
|
2103
|
+
const flat = {};
|
|
2104
|
+
for (const [name, field] of fields)
|
|
2105
|
+
flat[name] = field.inline;
|
|
2106
|
+
return { problems, headChecked, fields: flat };
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
// ../core/dist/task-ready.js
|
|
2110
|
+
var READY_DEFAULT_MINUTES = 60;
|
|
2111
|
+
function minutesOf(row) {
|
|
2112
|
+
return typeof row.minutes === "number" ? row.minutes : READY_DEFAULT_MINUTES;
|
|
2113
|
+
}
|
|
2114
|
+
function readyTasks(rows) {
|
|
2115
|
+
const byId = /* @__PURE__ */ new Map();
|
|
2116
|
+
for (const row of rows) {
|
|
2117
|
+
if (!byId.has(row.id))
|
|
2118
|
+
byId.set(row.id, row);
|
|
2119
|
+
}
|
|
2120
|
+
const children = /* @__PURE__ */ new Map();
|
|
2121
|
+
for (const id of byId.keys())
|
|
2122
|
+
children.set(id, []);
|
|
2123
|
+
for (const row of byId.values()) {
|
|
2124
|
+
for (const dep of row.dependsOn)
|
|
2125
|
+
children.get(dep)?.push(row.id);
|
|
2126
|
+
}
|
|
2127
|
+
const memo = /* @__PURE__ */ new Map();
|
|
2128
|
+
const pathFrom = (id) => {
|
|
2129
|
+
const known = memo.get(id);
|
|
2130
|
+
if (known !== void 0)
|
|
2131
|
+
return known;
|
|
2132
|
+
memo.set(id, 0);
|
|
2133
|
+
let best = 0;
|
|
2134
|
+
for (const childId of children.get(id) ?? []) {
|
|
2135
|
+
const child = byId.get(childId);
|
|
2136
|
+
if (child === void 0 || child.progress === "completed")
|
|
2137
|
+
continue;
|
|
2138
|
+
best = Math.max(best, pathFrom(childId));
|
|
2139
|
+
}
|
|
2140
|
+
const total = minutesOf(byId.get(id)) + best;
|
|
2141
|
+
memo.set(id, total);
|
|
2142
|
+
return total;
|
|
2143
|
+
};
|
|
2144
|
+
const startable = [];
|
|
2145
|
+
const person = [];
|
|
2146
|
+
for (const row of byId.values()) {
|
|
2147
|
+
if (row.progress !== "pending")
|
|
2148
|
+
continue;
|
|
2149
|
+
if (!row.dependsOn.every((dep) => byId.get(dep)?.progress === "completed"))
|
|
2150
|
+
continue;
|
|
2151
|
+
if (row.person) {
|
|
2152
|
+
person.push(row.id);
|
|
2153
|
+
continue;
|
|
2154
|
+
}
|
|
2155
|
+
startable.push({ id: row.id, path: 0, tieRank: row.tieRank });
|
|
2156
|
+
}
|
|
2157
|
+
for (const entry of startable)
|
|
2158
|
+
entry.path = pathFrom(entry.id);
|
|
2159
|
+
startable.sort((a, b) => b.path - a.path || a.tieRank - b.tieRank || a.id.localeCompare(b.id));
|
|
2160
|
+
person.sort((a, b) => a.localeCompare(b));
|
|
2161
|
+
return { ready: startable.map((entry) => entry.id), person };
|
|
2162
|
+
}
|
|
2163
|
+
|
|
1151
2164
|
// ../events/dist/result.js
|
|
1152
2165
|
import { z as z3 } from "zod";
|
|
1153
2166
|
var firedGuardSchema = z3.object({
|
|
@@ -1219,8 +2232,8 @@ import { z as z5 } from "zod";
|
|
|
1219
2232
|
// ../events/dist/human-agent.js
|
|
1220
2233
|
import { z as z4 } from "zod";
|
|
1221
2234
|
var HUMAN_AGENT = "Human";
|
|
1222
|
-
var
|
|
1223
|
-
var agentNameSchema = z4.string().min(1).max(
|
|
2235
|
+
var AGENT_NAME_MAX2 = AGENT_NAME_MAX;
|
|
2236
|
+
var agentNameSchema = z4.string().min(1).max(AGENT_NAME_MAX2);
|
|
1224
2237
|
|
|
1225
2238
|
// ../events/dist/state.js
|
|
1226
2239
|
var utcIso8601 = z5.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/, "must be a UTC ISO-8601 timestamp ending in Z");
|
|
@@ -1719,7 +2732,7 @@ function isMissingKey(raw, path) {
|
|
|
1719
2732
|
}
|
|
1720
2733
|
return isRecord(parent) && !(String(path[path.length - 1]) in parent);
|
|
1721
2734
|
}
|
|
1722
|
-
function shapeProblemsOf(schema, raw, undeclared) {
|
|
2735
|
+
function shapeProblemsOf(schema, raw, undeclared, required = {}) {
|
|
1723
2736
|
const parsed = schema.safeParse(raw);
|
|
1724
2737
|
if (parsed.success)
|
|
1725
2738
|
return [];
|
|
@@ -1730,7 +2743,11 @@ function shapeProblemsOf(schema, raw, undeclared) {
|
|
|
1730
2743
|
problems.push({ path: dotted([...issue.path, key]), message: undeclared });
|
|
1731
2744
|
}
|
|
1732
2745
|
} else if (isMissingKey(raw, issue.path)) {
|
|
1733
|
-
|
|
2746
|
+
const path = dotted(issue.path);
|
|
2747
|
+
if (Object.hasOwn(required, path))
|
|
2748
|
+
problems.push({ path, message: required[path] });
|
|
2749
|
+
else
|
|
2750
|
+
problems.push({ path: dotted(issue.path), message: "is required" });
|
|
1734
2751
|
} else {
|
|
1735
2752
|
problems.push({ path: dotted(issue.path), message: issue.message });
|
|
1736
2753
|
}
|
|
@@ -1813,8 +2830,11 @@ var systemDescriptionProblemSchema = z7.object({
|
|
|
1813
2830
|
path: z7.string(),
|
|
1814
2831
|
message: z7.string()
|
|
1815
2832
|
}).strict();
|
|
2833
|
+
var CONNECTIONS_REQUIRED = "is required; an empty list, [], is fine when no part connects to another";
|
|
1816
2834
|
function shapeProblems(raw) {
|
|
1817
|
-
return shapeProblemsOf(systemDescriptionSchema, raw, "is not a field a description has"
|
|
2835
|
+
return shapeProblemsOf(systemDescriptionSchema, raw, "is not a field a description has", {
|
|
2836
|
+
connections: CONNECTIONS_REQUIRED
|
|
2837
|
+
});
|
|
1818
2838
|
}
|
|
1819
2839
|
function quoted(value) {
|
|
1820
2840
|
return typeof value === "string" ? shown(value) : "another part";
|
|
@@ -2293,16 +3313,16 @@ function planCrossFieldProblems(raw, description) {
|
|
|
2293
3313
|
if (!isRecord(raw) || !Array.isArray(raw.changes))
|
|
2294
3314
|
return problems;
|
|
2295
3315
|
const known = new Set(description.parts.map((p) => p.id));
|
|
2296
|
-
const
|
|
3316
|
+
const named2 = /* @__PURE__ */ new Map();
|
|
2297
3317
|
raw.changes.forEach((change, i) => {
|
|
2298
3318
|
if (!isRecord(change) || typeof change.part !== "string")
|
|
2299
3319
|
return;
|
|
2300
3320
|
if (!known.has(change.part)) {
|
|
2301
3321
|
problems.push({ path: `changes.${i}.part`, message: `names no part in the system description: ${shown(change.part)}` });
|
|
2302
3322
|
}
|
|
2303
|
-
const earlier =
|
|
3323
|
+
const earlier = named2.get(change.part);
|
|
2304
3324
|
if (earlier === void 0) {
|
|
2305
|
-
|
|
3325
|
+
named2.set(change.part, i);
|
|
2306
3326
|
} else {
|
|
2307
3327
|
problems.push({ path: `changes.${i}.part`, message: `${shown(change.part)} is already listed at changes.${earlier}` });
|
|
2308
3328
|
}
|
|
@@ -3285,9 +4305,54 @@ var workCallRecordSchema = z15.object({
|
|
|
3285
4305
|
message: "says how to undo it exactly when it can be undone",
|
|
3286
4306
|
path: ["how"]
|
|
3287
4307
|
});
|
|
4308
|
+
var WORK_CRITERIA_ROWS_MAX = 500;
|
|
4309
|
+
var workCriterionSchema = agentWorkText.refine((t) => isCriterionCitation(t), {
|
|
4310
|
+
message: "must name a criterion as its spec name, a space, then AC and its number, such as order-intake AC1"
|
|
4311
|
+
});
|
|
4312
|
+
var workCriteriaTestEvidenceSchema = z15.object({
|
|
4313
|
+
kind: z15.literal("test"),
|
|
4314
|
+
task: buildTaskIdSchema,
|
|
4315
|
+
label: agentWorkText,
|
|
4316
|
+
writtenIn: buildTaskIdSchema.optional()
|
|
4317
|
+
}).strict();
|
|
4318
|
+
var workCriteriaPathEvidenceSchema = z15.object({
|
|
4319
|
+
kind: z15.literal("path"),
|
|
4320
|
+
path: agentWorkText
|
|
4321
|
+
}).strict();
|
|
4322
|
+
var workCriteriaWalkEvidenceSchema = z15.object({
|
|
4323
|
+
kind: z15.literal("walk"),
|
|
4324
|
+
leg: agentWorkText
|
|
4325
|
+
}).strict();
|
|
4326
|
+
var workCriteriaEvidenceSchema = z15.discriminatedUnion("kind", [
|
|
4327
|
+
workCriteriaTestEvidenceSchema,
|
|
4328
|
+
workCriteriaPathEvidenceSchema,
|
|
4329
|
+
workCriteriaWalkEvidenceSchema
|
|
4330
|
+
]);
|
|
4331
|
+
var workCriteriaMetRowSchema = z15.object({
|
|
4332
|
+
criterion: workCriterionSchema,
|
|
4333
|
+
edge: z15.literal(true).optional(),
|
|
4334
|
+
status: z15.literal("met"),
|
|
4335
|
+
evidence: workCriteriaEvidenceSchema
|
|
4336
|
+
}).strict();
|
|
4337
|
+
var workCriteriaFixRowSchema = z15.object({
|
|
4338
|
+
criterion: workCriterionSchema,
|
|
4339
|
+
edge: z15.literal(true).optional(),
|
|
4340
|
+
status: z15.literal("fix"),
|
|
4341
|
+
gap: agentWorkText,
|
|
4342
|
+
task: buildTaskIdSchema
|
|
4343
|
+
}).strict();
|
|
4344
|
+
var workCriteriaRowSchema = z15.discriminatedUnion("status", [workCriteriaMetRowSchema, workCriteriaFixRowSchema]);
|
|
4345
|
+
var workCriteriaRecordSchema = z15.object({
|
|
4346
|
+
record: z15.literal("criteria"),
|
|
4347
|
+
subject: workPhaseSubjectSchema,
|
|
4348
|
+
check: buildTaskIdSchema,
|
|
4349
|
+
by: agentNameSchema,
|
|
4350
|
+
rows: z15.array(workCriteriaRowSchema).min(1, { message: "must carry at least one criterion" }).max(WORK_CRITERIA_ROWS_MAX, { message: `can carry at most ${WORK_CRITERIA_ROWS_MAX} criteria` })
|
|
4351
|
+
}).strict();
|
|
3288
4352
|
var workRecordSchema = z15.discriminatedUnion("record", [
|
|
3289
4353
|
workReviewRecordSchema,
|
|
3290
4354
|
workOutcomesRecordSchema,
|
|
4355
|
+
workCriteriaRecordSchema,
|
|
3291
4356
|
workClaimsRecordSchema,
|
|
3292
4357
|
workDiagnosisRecordSchema,
|
|
3293
4358
|
workCallRecordSchema
|
|
@@ -3334,7 +4399,8 @@ var WORK_REFUSAL_STATUS = Object.freeze({
|
|
|
3334
4399
|
held: 409,
|
|
3335
4400
|
"attempt-limit": 409,
|
|
3336
4401
|
"same-attempt": 409,
|
|
3337
|
-
"call-not-recorded": 409
|
|
4402
|
+
"call-not-recorded": 409,
|
|
4403
|
+
"other-version": 409
|
|
3338
4404
|
});
|
|
3339
4405
|
var WORK_REFUSALS = Object.freeze(Object.keys(WORK_REFUSAL_STATUS));
|
|
3340
4406
|
var workRefusalSchema = z15.enum(WORK_REFUSALS);
|
|
@@ -3600,8 +4666,8 @@ function contractProblems(raw, description) {
|
|
|
3600
4666
|
if (!isRecord(contract) || typeof contract.from !== "string" || typeof contract.to !== "string")
|
|
3601
4667
|
return;
|
|
3602
4668
|
const { from, to } = contract;
|
|
3603
|
-
const
|
|
3604
|
-
if (!
|
|
4669
|
+
const named2 = description.connections.some((c) => c.from === from && c.to === to || c.from === to && c.to === from);
|
|
4670
|
+
if (!named2) {
|
|
3605
4671
|
problems.push({
|
|
3606
4672
|
path: `contracts.${i}`,
|
|
3607
4673
|
message: `names no connection in the system description: ${shown(from)} to ${shown(to)}`
|
|
@@ -5199,58 +6265,78 @@ var ownerWaitChatResultSchema = z32.discriminatedUnion("outcome", [
|
|
|
5199
6265
|
|
|
5200
6266
|
// ../events/dist/pacing.js
|
|
5201
6267
|
import { z as z33 } from "zod";
|
|
5202
|
-
var
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
Object.freeze({ id: "max-20x", words: "Max (20\xD7)", startingAgents: 8 }),
|
|
5206
|
-
Object.freeze({ id: "team", words: "Team", startingAgents: 3 }),
|
|
5207
|
-
Object.freeze({ id: "enterprise", words: "Enterprise", startingAgents: 3 }),
|
|
5208
|
-
Object.freeze({ id: "api-key", words: "I pay for what I use, with an API key", startingAgents: 8 }),
|
|
5209
|
-
Object.freeze({ id: "not-sure", words: "Not sure", startingAgents: 2 })
|
|
5210
|
-
]);
|
|
5211
|
-
var CLAUDE_PLAN_IDS = Object.freeze(CLAUDE_PLANS.map((plan) => plan.id));
|
|
5212
|
-
var claudePlanSchema = z33.enum(CLAUDE_PLAN_IDS);
|
|
5213
|
-
var CLAUDE_PLAN_UNKNOWN = "not-sure";
|
|
5214
|
-
function claudePlanOf(id) {
|
|
5215
|
-
return CLAUDE_PLANS.find((plan) => plan.id === id) ?? CLAUDE_PLANS.find((plan) => plan.id === CLAUDE_PLAN_UNKNOWN) ?? CLAUDE_PLANS[0];
|
|
5216
|
-
}
|
|
5217
|
-
function claudePlanKnown(id) {
|
|
5218
|
-
return id !== null && id !== CLAUDE_PLAN_UNKNOWN;
|
|
5219
|
-
}
|
|
5220
|
-
var CLAUDE_PLAN_SET_IN = ["setup", "console"];
|
|
5221
|
-
var claudePlanSetInSchema = z33.enum(CLAUDE_PLAN_SET_IN);
|
|
5222
|
-
var KEEP_FREE_PERCENTS = [0, 20];
|
|
5223
|
-
var keepFreePercentSchema = z33.union([z33.literal(KEEP_FREE_PERCENTS[0]), z33.literal(KEEP_FREE_PERCENTS[1])]);
|
|
6268
|
+
var KEEP_FREE_MAX = 90;
|
|
6269
|
+
var keepFreePercentSchema = z33.number().int().min(0).max(KEEP_FREE_MAX);
|
|
6270
|
+
var KEEP_FREE_DEFAULT = 20;
|
|
5224
6271
|
var PACE_AGENTS_MAX = 20;
|
|
5225
|
-
var
|
|
5226
|
-
var
|
|
5227
|
-
var
|
|
5228
|
-
|
|
5229
|
-
|
|
6272
|
+
var FIVE_HOUR_WINDOW_MINUTES = 300;
|
|
6273
|
+
var SEVEN_DAY_WINDOW_MINUTES = 10080;
|
|
6274
|
+
var PACE_WINDOW_MINUTES_MAX = 525600;
|
|
6275
|
+
var MINUTE_KEY = /^([1-9][0-9]{0,6})-minute$/;
|
|
6276
|
+
function isPaceWindowKind(value) {
|
|
6277
|
+
if (value === "five-hour" || value === "seven-day")
|
|
6278
|
+
return true;
|
|
6279
|
+
if (typeof value !== "string")
|
|
6280
|
+
return false;
|
|
6281
|
+
const match = MINUTE_KEY.exec(value);
|
|
6282
|
+
if (match === null)
|
|
6283
|
+
return false;
|
|
6284
|
+
const minutes = Number(match[1]);
|
|
6285
|
+
return minutes <= PACE_WINDOW_MINUTES_MAX && minutes !== FIVE_HOUR_WINDOW_MINUTES && minutes !== SEVEN_DAY_WINDOW_MINUTES;
|
|
6286
|
+
}
|
|
6287
|
+
var paceWindowKindSchema = z33.custom((value) => isPaceWindowKind(value), {
|
|
6288
|
+
message: "not a usage window key"
|
|
5230
6289
|
});
|
|
6290
|
+
function paceWindowMinutesOf(kind) {
|
|
6291
|
+
if (kind === "five-hour")
|
|
6292
|
+
return FIVE_HOUR_WINDOW_MINUTES;
|
|
6293
|
+
if (kind === "seven-day")
|
|
6294
|
+
return SEVEN_DAY_WINDOW_MINUTES;
|
|
6295
|
+
return Number(MINUTE_KEY.exec(kind)?.[1] ?? 0);
|
|
6296
|
+
}
|
|
6297
|
+
function paceWindowWordsOf(kind) {
|
|
6298
|
+
const minutes = paceWindowMinutesOf(kind);
|
|
6299
|
+
if (minutes === FIVE_HOUR_WINDOW_MINUTES)
|
|
6300
|
+
return "5-hour window";
|
|
6301
|
+
if (minutes === SEVEN_DAY_WINDOW_MINUTES)
|
|
6302
|
+
return "weekly window";
|
|
6303
|
+
if (minutes % 1440 === 0)
|
|
6304
|
+
return `${String(minutes / 1440)}-day window`;
|
|
6305
|
+
if (minutes % 60 === 0)
|
|
6306
|
+
return `${String(minutes / 60)}-hour window`;
|
|
6307
|
+
return `${String(minutes)}-minute window`;
|
|
6308
|
+
}
|
|
6309
|
+
function paceShareWordsOf(keepFreePercent) {
|
|
6310
|
+
return keepFreePercent === 20 ? "a fifth" : `${String(keepFreePercent)}%`;
|
|
6311
|
+
}
|
|
5231
6312
|
var PACE_STATES = ["full", "slowing", "holding"];
|
|
5232
6313
|
var paceStateSchema = z33.enum(PACE_STATES);
|
|
5233
|
-
var
|
|
5234
|
-
var
|
|
5235
|
-
var
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
})
|
|
6314
|
+
var PACE_SHARE_GIVEN_IN = ["chat", "agent", "relayed"];
|
|
6315
|
+
var paceShareGivenInSchema = z33.enum(PACE_SHARE_GIVEN_IN);
|
|
6316
|
+
var paceShareRequestSchema = z33.object({ keepFreePercent: keepFreePercentSchema, givenIn: paceShareGivenInSchema }).strict();
|
|
6317
|
+
var paceShareResultSchema = z33.discriminatedUnion("outcome", [
|
|
6318
|
+
z33.object({
|
|
6319
|
+
outcome: z33.literal("recorded"),
|
|
6320
|
+
keepFreePercent: keepFreePercentSchema,
|
|
6321
|
+
moreAllowance: z33.boolean().optional()
|
|
6322
|
+
}).strict(),
|
|
6323
|
+
z33.object({ outcome: z33.literal("needs-the-person"), keepFreePercent: keepFreePercentSchema }).strict(),
|
|
6324
|
+
z33.object({ outcome: z33.literal("not-owner") }).strict(),
|
|
6325
|
+
z33.object({ outcome: z33.literal("no-owner") }).strict(),
|
|
6326
|
+
z33.object({ outcome: z33.literal("unavailable") }).strict()
|
|
6327
|
+
]);
|
|
5243
6328
|
var pacingBasisTaskSchema = z33.object({
|
|
5244
6329
|
id: buildTaskIdSchema,
|
|
5245
6330
|
dependsOn: z33.array(buildTaskIdSchema).max(BUILD_SNAPSHOT_TASK_LIMIT),
|
|
5246
6331
|
budgetMinutes: z33.number().int().min(1).max(BUILD_TASK_BUDGET_MAX_MINUTES).optional(),
|
|
5247
|
-
human: z33.boolean()
|
|
6332
|
+
human: z33.boolean(),
|
|
6333
|
+
kind: buildTaskKindSchema.optional()
|
|
5248
6334
|
}).strict();
|
|
5249
6335
|
var pacingBasisSchema = z33.object({
|
|
5250
6336
|
outcome: z33.literal("basis"),
|
|
5251
|
-
claudePlan:
|
|
6337
|
+
claudePlan: z33.string().nullable().optional(),
|
|
5252
6338
|
keepFreePercent: keepFreePercentSchema,
|
|
5253
|
-
settingsUrl: z33.string().
|
|
6339
|
+
settingsUrl: z33.string().optional(),
|
|
5254
6340
|
phase: phaseNameSchema.nullable(),
|
|
5255
6341
|
tasks: z33.array(pacingBasisTaskSchema).max(BUILD_SNAPSHOT_TASK_LIMIT),
|
|
5256
6342
|
medianTaskMinutes: z33.number().nonnegative().nullable(),
|
|
@@ -5260,12 +6346,21 @@ var paceSuggestionSchema = z33.object({
|
|
|
5260
6346
|
number: z33.number().int().min(1).max(PACE_AGENTS_MAX),
|
|
5261
6347
|
minutes: z33.number().int().nonnegative().nullable(),
|
|
5262
6348
|
pauses: z33.number().int().nonnegative(),
|
|
5263
|
-
claudePlan: claudePlanSchema.nullable(),
|
|
5264
6349
|
keepFreePercent: keepFreePercentSchema,
|
|
5265
|
-
|
|
5266
|
-
|
|
6350
|
+
knownWindow: paceWindowKindSchema.nullable(),
|
|
6351
|
+
expectedTokens: z33.number().int().nonnegative().optional(),
|
|
6352
|
+
limitTokens: z33.number().int().positive().optional(),
|
|
6353
|
+
multiple: z33.number().positive().nullable().optional(),
|
|
6354
|
+
firstGuess: z33.boolean().optional(),
|
|
6355
|
+
limitSetBy: z33.enum(["default", "person", "other"]).optional()
|
|
5267
6356
|
}).strict();
|
|
5268
6357
|
var PACE_NOT_PACED_REASONS = ["not-configured", "unreadable"];
|
|
6358
|
+
var limitHeldSchema = z33.object({
|
|
6359
|
+
used: z33.number().int().nonnegative(),
|
|
6360
|
+
limit: z33.number().int().positive(),
|
|
6361
|
+
raiseBy: z33.number().int().nonnegative(),
|
|
6362
|
+
working: z33.number().int().nonnegative()
|
|
6363
|
+
}).strict();
|
|
5269
6364
|
var paceRunResultSchema = z33.discriminatedUnion("outcome", [
|
|
5270
6365
|
z33.object({
|
|
5271
6366
|
outcome: z33.literal("paced"),
|
|
@@ -5274,83 +6369,93 @@ var paceRunResultSchema = z33.discriminatedUnion("outcome", [
|
|
|
5274
6369
|
allowedNow: z33.number().int().nonnegative(),
|
|
5275
6370
|
windowKind: paceWindowKindSchema.optional(),
|
|
5276
6371
|
until: utcIso8601.optional(),
|
|
5277
|
-
|
|
6372
|
+
keepFreePercent: keepFreePercentSchema.optional(),
|
|
6373
|
+
suggestion: paceSuggestionSchema.optional(),
|
|
6374
|
+
limit: z33.literal("reached").optional(),
|
|
6375
|
+
limitHeld: limitHeldSchema.optional()
|
|
5278
6376
|
}).strict(),
|
|
5279
6377
|
z33.object({ outcome: z33.literal("not-paced"), reason: z33.enum(PACE_NOT_PACED_REASONS) }).strict()
|
|
5280
6378
|
]);
|
|
6379
|
+
function tokenCountWordsOf(n) {
|
|
6380
|
+
if (n < 1e3)
|
|
6381
|
+
return String(Math.max(0, Math.round(n)));
|
|
6382
|
+
const rounded = Number(n.toPrecision(2));
|
|
6383
|
+
const [unit, word] = rounded >= 1e9 ? [1e9, "billion"] : rounded >= 1e6 ? [1e6, "million"] : [1e3, "thousand"];
|
|
6384
|
+
return `${String(Number((rounded / unit).toPrecision(2)))} ${word}`;
|
|
6385
|
+
}
|
|
5281
6386
|
function paceAgentsOf(n) {
|
|
5282
6387
|
return n === 1 ? "1 agent" : `${String(n)} agents`;
|
|
5283
6388
|
}
|
|
5284
6389
|
var DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
6390
|
+
var MONTH_NAMES = [
|
|
6391
|
+
"January",
|
|
6392
|
+
"February",
|
|
6393
|
+
"March",
|
|
6394
|
+
"April",
|
|
6395
|
+
"May",
|
|
6396
|
+
"June",
|
|
6397
|
+
"July",
|
|
6398
|
+
"August",
|
|
6399
|
+
"September",
|
|
6400
|
+
"October",
|
|
6401
|
+
"November",
|
|
6402
|
+
"December"
|
|
6403
|
+
];
|
|
6404
|
+
var SIX_DAYS_MS = 6 * 24 * 60 * 60 * 1e3;
|
|
5285
6405
|
function paceClockOf(at, now) {
|
|
5286
6406
|
const hours = at.getHours();
|
|
5287
6407
|
const clock = `${String(hours % 12 === 0 ? 12 : hours % 12)}:${String(at.getMinutes()).padStart(2, "0")} ${hours < 12 ? "am" : "pm"}`;
|
|
5288
6408
|
const sameDay = at.getFullYear() === now.getFullYear() && at.getMonth() === now.getMonth() && at.getDate() === now.getDate();
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
function claudePlanFromAnswer(answer) {
|
|
5297
|
-
const trimmed = answer.trim();
|
|
5298
|
-
if (trimmed === "")
|
|
5299
|
-
return CLAUDE_PLAN_UNKNOWN;
|
|
5300
|
-
if (!/^[1-9][0-9]*$/.test(trimmed))
|
|
5301
|
-
return null;
|
|
5302
|
-
return CLAUDE_PLANS[Number(trimmed) - 1]?.id ?? null;
|
|
5303
|
-
}
|
|
5304
|
-
function claudePlanRememberedText(plan, settingsUrl) {
|
|
5305
|
-
return `Using the plan you gave before: ${claudePlanOf(plan).words}. You can change it on your settings page, not by typing in chat: ${settingsUrl}`;
|
|
6409
|
+
if (sameDay)
|
|
6410
|
+
return clock;
|
|
6411
|
+
const day = DAY_NAMES[at.getDay()] ?? "";
|
|
6412
|
+
if (at.getTime() - now.getTime() > SIX_DAYS_MS) {
|
|
6413
|
+
return `${day} ${String(at.getDate())} ${MONTH_NAMES[at.getMonth()] ?? ""} ${clock}`;
|
|
6414
|
+
}
|
|
6415
|
+
return `${day} ${clock}`;
|
|
5306
6416
|
}
|
|
5307
6417
|
function paceP1Of(n, minutes) {
|
|
5308
6418
|
const first = `Halfcycle suggests running ${paceAgentsOf(n)} at once.`;
|
|
5309
6419
|
return minutes === null ? first : `${first} The work should take about ${timeDurationLabelOf(minutes * 60)}.`;
|
|
5310
6420
|
}
|
|
5311
6421
|
var PACE_NO_BUILD_PLAN = "Halfcycle has no build plan for this phase yet, so it cannot say how long the work will take.";
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
}
|
|
5315
|
-
function paceP3Of(window) {
|
|
5316
|
-
return `This keeps about a fifth of each ${PACE_WINDOW_WORDS[window]} free for your own work.`;
|
|
5317
|
-
}
|
|
5318
|
-
function paceP4Of(n, settingsUrl) {
|
|
5319
|
-
const first = `Halfcycle does not know which Claude plan you use, so it starts with ${String(n)}.`;
|
|
5320
|
-
return settingsUrl === null ? first : `${first} You can say which on your settings page, not by typing in chat: ${settingsUrl}`;
|
|
6422
|
+
var PACE_P2 = "This is a careful start, so your allowance lasts. It leaves room for your own work.";
|
|
6423
|
+
function paceP3Of(keepFreePercent) {
|
|
6424
|
+
return `This keeps about ${paceShareWordsOf(keepFreePercent)} of each usage window free for your own work.`;
|
|
5321
6425
|
}
|
|
6426
|
+
var PACE_P4 = 'Say "faster" for one more agent at once, or "run 5 agents" for a number. More agents use your allowance faster.';
|
|
5322
6427
|
var PACE_P5 = "This build may use all of your allowance. It will not slow down to leave room for you.";
|
|
5323
6428
|
function paceP6Of(k) {
|
|
5324
6429
|
return `It will probably pause ${k === 1 ? "once" : `${String(k)} times`} while your usage window resets.`;
|
|
5325
6430
|
}
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
}
|
|
5330
|
-
function paceStartLinesOf(s) {
|
|
6431
|
+
var PACE_P7 = 'Or say how much of your allowance to keep free, like "keep 30% free", or how much this build may use, like "use up to 70%".';
|
|
6432
|
+
var PACE_P8 = 'For these to work, run "npx halfcycle" once more in this project.';
|
|
6433
|
+
function paceStartLinesOf(s, promptScriptBehind2 = false) {
|
|
5331
6434
|
const lines = [paceP1Of(s.number, s.minutes)];
|
|
5332
6435
|
if (s.minutes === null)
|
|
5333
6436
|
lines.push(PACE_NO_BUILD_PLAN);
|
|
5334
|
-
if (s.keepFreePercent !== 0) {
|
|
5335
|
-
if (s.knownWindow !== null)
|
|
5336
|
-
lines.push(paceP3Of(s.knownWindow));
|
|
5337
|
-
else if (s.claudePlan !== null && claudePlanKnown(s.claudePlan))
|
|
5338
|
-
lines.push(paceP2Of(s.claudePlan));
|
|
5339
|
-
}
|
|
5340
|
-
if (!claudePlanKnown(s.claudePlan))
|
|
5341
|
-
lines.push(paceP4Of(s.number, s.settingsUrl));
|
|
5342
6437
|
if (s.keepFreePercent === 0)
|
|
5343
6438
|
lines.push(PACE_P5);
|
|
6439
|
+
else
|
|
6440
|
+
lines.push(s.knownWindow !== null ? paceP3Of(s.keepFreePercent) : PACE_P2);
|
|
6441
|
+
if (s.limitTokens !== void 0 && s.expectedTokens !== void 0) {
|
|
6442
|
+
lines.push(limitL1Of(s.expectedTokens, s.limitTokens, s.multiple ?? null, s.limitSetBy ?? "default"));
|
|
6443
|
+
if (s.firstGuess === true)
|
|
6444
|
+
lines.push(LIMIT_L1B);
|
|
6445
|
+
} else
|
|
6446
|
+
lines.push(LIMIT_L1C);
|
|
5344
6447
|
if (s.pauses > 0)
|
|
5345
6448
|
lines.push(paceP6Of(s.pauses));
|
|
5346
|
-
lines.push(
|
|
6449
|
+
lines.push(PACE_P4, PACE_P7, LIMIT_L2);
|
|
6450
|
+
if (promptScriptBehind2)
|
|
6451
|
+
lines.push(PACE_P8);
|
|
5347
6452
|
return lines;
|
|
5348
6453
|
}
|
|
5349
|
-
function paceS1Of(window, k, until, now) {
|
|
5350
|
-
return `Halfcycle is slowing this build so you keep about
|
|
6454
|
+
function paceS1Of(window, k, until, now, keepFreePercent) {
|
|
6455
|
+
return `Halfcycle is slowing this build so you keep about ${paceShareWordsOf(keepFreePercent)} of this ${paceWindowWordsOf(window)} for your own work. It will run at most ${paceAgentsOf(k)} at once until ${paceClockOf(until, now)}.`;
|
|
5351
6456
|
}
|
|
5352
|
-
function paceS2Of(window, k, until, now) {
|
|
5353
|
-
const head = `Halfcycle has paused new work in this build until ${paceClockOf(until, now)}, when your ${
|
|
6457
|
+
function paceS2Of(window, k, until, now, keepFreePercent) {
|
|
6458
|
+
const head = `Halfcycle has paused new work in this build until ${paceClockOf(until, now)}, when your ${paceWindowWordsOf(window)} resets, so you keep about ${paceShareWordsOf(keepFreePercent)} of it for your own work.`;
|
|
5354
6459
|
if (k === 0)
|
|
5355
6460
|
return head;
|
|
5356
6461
|
return k === 1 ? `${head} The 1 agent already working will finish its task.` : `${head} The ${String(k)} agents already working will finish their tasks.`;
|
|
@@ -5359,6 +6464,97 @@ function paceAllowedNowOf(k) {
|
|
|
5359
6464
|
return k === 0 ? "Start no more agents now." : `Up to ${String(k)} more ${k === 1 ? "agent" : "agents"} can start now.`;
|
|
5360
6465
|
}
|
|
5361
6466
|
var PACE_AGENTS_TOO_MANY = `Run at most ${String(PACE_AGENTS_MAX)} agents at once.`;
|
|
6467
|
+
var LIMIT_HISTORY_MAX = 20;
|
|
6468
|
+
var LIMIT_PARTS = ["half", "three-quarters", "nine-tenths", "limit"];
|
|
6469
|
+
var limitPartSchema = z33.enum(LIMIT_PARTS);
|
|
6470
|
+
var LIMIT_SET_BY = ["default", "person", "other"];
|
|
6471
|
+
var limitSetBySchema = z33.enum(LIMIT_SET_BY);
|
|
6472
|
+
var LIMIT_TYPED_BY = ["person", "not-attended", "cannot-tell"];
|
|
6473
|
+
var limitTypedBySchema = z33.enum(LIMIT_TYPED_BY);
|
|
6474
|
+
var limitHistoryEntrySchema = z33.object({
|
|
6475
|
+
at: utcIso8601,
|
|
6476
|
+
kind: z33.enum(["raised", "set", "stopped"]),
|
|
6477
|
+
limit: z33.number().int().positive(),
|
|
6478
|
+
by: limitTypedBySchema,
|
|
6479
|
+
relayed: z33.literal(true).optional()
|
|
6480
|
+
}).strict();
|
|
6481
|
+
var PHASE_LIMIT_FILENAME = "limit.json";
|
|
6482
|
+
var phaseLimitFileSchema = z33.object({
|
|
6483
|
+
phase: phaseNameSchema,
|
|
6484
|
+
expected: z33.number().int().nonnegative(),
|
|
6485
|
+
expectedMinutes: z33.number().nonnegative().nullable(),
|
|
6486
|
+
limit: z33.number().int().positive(),
|
|
6487
|
+
multiple: z33.number().positive().nullable(),
|
|
6488
|
+
setBy: limitSetBySchema,
|
|
6489
|
+
firstGuess: z33.boolean(),
|
|
6490
|
+
shown: z33.array(limitPartSchema).max(LIMIT_PARTS.length),
|
|
6491
|
+
awaiting: z33.boolean(),
|
|
6492
|
+
history: z33.array(limitHistoryEntrySchema).max(LIMIT_HISTORY_MAX)
|
|
6493
|
+
}).strict();
|
|
6494
|
+
function phaseLimitFileOf(raw, phase) {
|
|
6495
|
+
const parsed = phaseLimitFileSchema.safeParse(raw);
|
|
6496
|
+
if (!parsed.success)
|
|
6497
|
+
return null;
|
|
6498
|
+
return phase === null || parsed.data.phase === phase ? parsed.data : null;
|
|
6499
|
+
}
|
|
6500
|
+
var phaseUsageFileSchema = z33.object({
|
|
6501
|
+
phase: phaseNameSchema,
|
|
6502
|
+
openedAt: utcIso8601,
|
|
6503
|
+
tokens: z33.number().int().nonnegative(),
|
|
6504
|
+
at: utcIso8601
|
|
6505
|
+
}).strict();
|
|
6506
|
+
function multipleTextOf(multiple) {
|
|
6507
|
+
return String(Math.round(multiple * 100) / 100);
|
|
6508
|
+
}
|
|
6509
|
+
function multipleWordsOf(multiple) {
|
|
6510
|
+
return multiple === 2 ? "twice" : `${multipleTextOf(multiple)} times`;
|
|
6511
|
+
}
|
|
6512
|
+
function paceWhenOf(at) {
|
|
6513
|
+
const hours = at.getHours();
|
|
6514
|
+
const clock = `${String(hours % 12 === 0 ? 12 : hours % 12)}:${String(at.getMinutes()).padStart(2, "0")} ${hours < 12 ? "am" : "pm"}`;
|
|
6515
|
+
return `${String(at.getDate())} ${MONTH_NAMES[at.getMonth()] ?? ""}, ${clock}`;
|
|
6516
|
+
}
|
|
6517
|
+
function limitL1Of(expected, limit, multiple, setBy) {
|
|
6518
|
+
const head = `This phase is expected to use about ${tokenCountWordsOf(expected)} tokens.`;
|
|
6519
|
+
const l = tokenCountWordsOf(limit);
|
|
6520
|
+
if (multiple !== null && setBy !== "other") {
|
|
6521
|
+
return `${head} Halfcycle will stop new work and ask you at ${multipleWordsOf(multiple)} that, ${l} tokens.`;
|
|
6522
|
+
}
|
|
6523
|
+
if (setBy === "person")
|
|
6524
|
+
return `${head} You set the limit at ${l} tokens; Halfcycle will stop new work and ask you there.`;
|
|
6525
|
+
return `${head} The limit is ${l} tokens; Halfcycle will stop new work and ask there.`;
|
|
6526
|
+
}
|
|
6527
|
+
var LIMIT_L1B = "That is a first guess: this project has not finished enough work for Halfcycle to go on.";
|
|
6528
|
+
var LIMIT_L1C = "Halfcycle cannot say how much the work will use, so it has set no usage limit yet.";
|
|
6529
|
+
var LIMIT_L2 = 'To change the limit, say "limit it to 1.5 times" or "limit it to 300 million".';
|
|
6530
|
+
function limitReachedLineOf(used, limit, working, raiseBy) {
|
|
6531
|
+
const head = `This phase has reached its usage limit: ${tokenCountWordsOf(used)} of ${tokenCountWordsOf(limit)} tokens. No new work will start.`;
|
|
6532
|
+
const finish = working === 0 ? "" : working === 1 ? " The 1 agent already working will finish its task." : ` The ${String(working)} agents already working will finish their tasks.`;
|
|
6533
|
+
return `${head}${finish} Say "keep going" to raise the limit by about ${tokenCountWordsOf(raiseBy)} tokens and carry on, or "stop" to leave the phase paused here. Nothing is lost either way.`;
|
|
6534
|
+
}
|
|
6535
|
+
var LIMIT_C1_NONE = "No usage limit was set: this phase had no build plan.";
|
|
6536
|
+
function limitCloseLineOf(file) {
|
|
6537
|
+
const l = tokenCountWordsOf(file.limit);
|
|
6538
|
+
const t = tokenCountWordsOf(file.expected);
|
|
6539
|
+
if (file.setBy === "default")
|
|
6540
|
+
return `Usage limit: ${l} tokens, from an expected ${t}.`;
|
|
6541
|
+
if (file.setBy === "person")
|
|
6542
|
+
return `Usage limit: ${l} tokens, set by you. Expected: ${t}.`;
|
|
6543
|
+
return `Usage limit: ${l} tokens. Expected: ${t}.`;
|
|
6544
|
+
}
|
|
6545
|
+
function limitHistoryLineOf(entry) {
|
|
6546
|
+
if (entry.relayed === true)
|
|
6547
|
+
return null;
|
|
6548
|
+
const when = paceWhenOf(new Date(entry.at));
|
|
6549
|
+
const you = entry.by === "person";
|
|
6550
|
+
if (entry.kind === "stopped") {
|
|
6551
|
+
return you ? `On ${when} you stopped the phase at its limit. Nothing was lost.` : `On ${when} the phase was stopped at its limit. Nothing was lost.`;
|
|
6552
|
+
}
|
|
6553
|
+
const l = tokenCountWordsOf(entry.limit);
|
|
6554
|
+
if (!you)
|
|
6555
|
+
return `On ${when} the limit was set to ${l} tokens.`;
|
|
6556
|
+
return entry.kind === "raised" ? `On ${when} you said "keep going": the limit went up to ${l} tokens.` : `On ${when} you set the limit to ${l} tokens.`;
|
|
6557
|
+
}
|
|
5362
6558
|
|
|
5363
6559
|
// ../events/dist/autonomy.js
|
|
5364
6560
|
import { z as z34 } from "zod";
|
|
@@ -5582,6 +6778,107 @@ function recordedClaimLine(claim) {
|
|
|
5582
6778
|
}
|
|
5583
6779
|
var NO_UNCHECKED_CLAIMS_LINE = "No agent said a check passes that Halfcycle never ran.";
|
|
5584
6780
|
|
|
6781
|
+
// ../events/dist/relayed-choices.js
|
|
6782
|
+
import { z as z35 } from "zod";
|
|
6783
|
+
var RELAYED_CHOICES_FILENAME = "relayed.json";
|
|
6784
|
+
var RELAYED_SAID_MAX = 80;
|
|
6785
|
+
var relayedChoiceSchema = z35.object({
|
|
6786
|
+
at: utcIso8601,
|
|
6787
|
+
said: z35.string().min(1).max(RELAYED_SAID_MAX)
|
|
6788
|
+
}).strict();
|
|
6789
|
+
var relayedChoicesFileSchema = z35.object({
|
|
6790
|
+
phase: phaseNameSchema,
|
|
6791
|
+
entries: z35.array(relayedChoiceSchema)
|
|
6792
|
+
}).strict();
|
|
6793
|
+
function relayedChoiceLineOf(entry) {
|
|
6794
|
+
return `On ${paceWhenOf(new Date(entry.at))} "${entry.said}" reached Halfcycle through Codex, which cannot show who typed it. Halfcycle could not confirm that you typed it, and it let this phase use more of your allowance.`;
|
|
6795
|
+
}
|
|
6796
|
+
|
|
6797
|
+
// ../events/dist/tier-figures.js
|
|
6798
|
+
import { z as z36 } from "zod";
|
|
6799
|
+
var TIER_ORDER = ["fast", "standard", "deep"];
|
|
6800
|
+
function tierRankOf(tier) {
|
|
6801
|
+
return TIER_ORDER.indexOf(tier);
|
|
6802
|
+
}
|
|
6803
|
+
function tierAboveOf(tier) {
|
|
6804
|
+
return TIER_ORDER[Math.min(tierRankOf(tier) + 1, TIER_ORDER.length - 1)];
|
|
6805
|
+
}
|
|
6806
|
+
function hasCheaperRole(member) {
|
|
6807
|
+
return member.role === "builder" && member.tier === "standard";
|
|
6808
|
+
}
|
|
6809
|
+
function cheaperRoleNameOf(callsign) {
|
|
6810
|
+
return `${callsign.toLowerCase()}fast`;
|
|
6811
|
+
}
|
|
6812
|
+
function routeCheaperClaudeLineOf(holder, task, model) {
|
|
6813
|
+
return `Start ${holder} on ${task} on the cheaper tier: give the Agent tool the model "${model}". Tell the reviewer the builder tier is fast.`;
|
|
6814
|
+
}
|
|
6815
|
+
function routeCheaperCodexLineOf(holder, task) {
|
|
6816
|
+
return `Start ${holder} on ${task} on the cheaper tier: use the agent type "${cheaperRoleNameOf(holder)}". Tell the reviewer the builder tier is fast.`;
|
|
6817
|
+
}
|
|
6818
|
+
function routeStartLineOf(holder, task, tier) {
|
|
6819
|
+
return `Start ${holder} on ${task}. Tell the reviewer the builder tier is ${tier}.`;
|
|
6820
|
+
}
|
|
6821
|
+
function routeStartUnknownLineOf(holder, task) {
|
|
6822
|
+
return `Start ${holder} on ${task}.`;
|
|
6823
|
+
}
|
|
6824
|
+
function routeNoSuchTaskLineOf(task) {
|
|
6825
|
+
return `"${task}" is not a task in the plan. Check the task id, then run this again.`;
|
|
6826
|
+
}
|
|
6827
|
+
var routingRecordSchema = z36.object({
|
|
6828
|
+
phase: z36.string().min(1).max(200),
|
|
6829
|
+
cheapStarted: z36.array(z36.string().min(1).max(200)).max(2e3)
|
|
6830
|
+
}).strict();
|
|
6831
|
+
var PHASE_MODEL_NAME_MAX = 64;
|
|
6832
|
+
var phaseModelsFileSchema = z36.object({
|
|
6833
|
+
phase: z36.string().min(1).max(200),
|
|
6834
|
+
openedAt: z36.string().min(1).max(64),
|
|
6835
|
+
tasks: z36.record(z36.string().min(1).max(200), z36.record(z36.string().min(1).max(PHASE_MODEL_NAME_MAX), z36.number().int().min(0)))
|
|
6836
|
+
}).strict();
|
|
6837
|
+
function inAttemptOrder(attempts) {
|
|
6838
|
+
return [...attempts].sort((a, b) => a.attempt - b.attempt);
|
|
6839
|
+
}
|
|
6840
|
+
function tierFiguresOf(work) {
|
|
6841
|
+
const finishedCheaper = [];
|
|
6842
|
+
const movedUp = [];
|
|
6843
|
+
const hadCheaperAttempt = [];
|
|
6844
|
+
for (const task of work.tasks) {
|
|
6845
|
+
const attempts = inAttemptOrder(task.attempts);
|
|
6846
|
+
if (attempts.length === 0)
|
|
6847
|
+
continue;
|
|
6848
|
+
if (task.standing === "passed" && attempts.every((a) => a.builderTier === "fast"))
|
|
6849
|
+
finishedCheaper.push(task.task);
|
|
6850
|
+
if (attempts.length >= 2 && tierRankOf(attempts[attempts.length - 1].builderTier) > tierRankOf(attempts[0].builderTier)) {
|
|
6851
|
+
movedUp.push(task.task);
|
|
6852
|
+
}
|
|
6853
|
+
if (attempts.some((a) => a.builderTier === "fast"))
|
|
6854
|
+
hadCheaperAttempt.push(task.task);
|
|
6855
|
+
}
|
|
6856
|
+
return { finishedCheaper, movedUp, hadCheaperAttempt };
|
|
6857
|
+
}
|
|
6858
|
+
function tasksWordOf(n) {
|
|
6859
|
+
return n === 1 ? "1 task" : `${String(n)} tasks`;
|
|
6860
|
+
}
|
|
6861
|
+
function tierCountsLineOf(figures) {
|
|
6862
|
+
if (figures.hadCheaperAttempt.length === 0)
|
|
6863
|
+
return "No task started on the cheaper tier in this phase.";
|
|
6864
|
+
return `The reviews recorded ${tasksWordOf(figures.finishedCheaper.length)} finishing on the cheaper tier and ${String(figures.movedUp.length)} moving up to a stronger one.`;
|
|
6865
|
+
}
|
|
6866
|
+
var NO_EXPECTED_LINE = "No expected time or usage was recorded for this phase.";
|
|
6867
|
+
function expectedActualLineOf(expected, actual) {
|
|
6868
|
+
const took = `The phase took ${timeDurationLabelOf(actual.seconds)} and ${tokenCountWordsOf(actual.tokens)} tokens.`;
|
|
6869
|
+
if (expected.minutes === null)
|
|
6870
|
+
return `Expected about ${tokenCountWordsOf(expected.tokens)} tokens. ${took}`;
|
|
6871
|
+
return `Expected ${timeDurationLabelOf(expected.minutes * 60)} and about ${tokenCountWordsOf(expected.tokens)} tokens. ${took}`;
|
|
6872
|
+
}
|
|
6873
|
+
function joinedOf(names) {
|
|
6874
|
+
if (names.length <= 1)
|
|
6875
|
+
return names.join("");
|
|
6876
|
+
return `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`;
|
|
6877
|
+
}
|
|
6878
|
+
function seenModelsLineOf(task, models) {
|
|
6879
|
+
return `Seen in your coding agent's own records: ${task} ran on ${joinedOf(models)}.`;
|
|
6880
|
+
}
|
|
6881
|
+
|
|
5585
6882
|
// dist/engagement-credential.js
|
|
5586
6883
|
import { chmodSync as chmodSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
5587
6884
|
import { platform as platform2 } from "node:os";
|
|
@@ -7260,8 +8557,9 @@ function crewProfileBody(role, group, tier) {
|
|
|
7260
8557
|
return `${lines.join("\n")}
|
|
7261
8558
|
`;
|
|
7262
8559
|
}
|
|
7263
|
-
function crewProfileDescription(member) {
|
|
7264
|
-
|
|
8560
|
+
function crewProfileDescription(member, cheaper = false) {
|
|
8561
|
+
const attempt = cheaper ? " (cheaper attempt)" : "";
|
|
8562
|
+
return `${member.callsign}, ${CREW_ROLE_WORDS[member.role]} (${member.discipline})${attempt}, a member of this project's build crew.`;
|
|
7265
8563
|
}
|
|
7266
8564
|
function plainYamlValue(value) {
|
|
7267
8565
|
if (/[\r\n]|: | #|^[-?:,[\]{}#&*!|>'"%@`\s]|\s$/.test(value)) {
|
|
@@ -7282,17 +8580,19 @@ function claudeCrewProfile(member) {
|
|
|
7282
8580
|
crewProfileBody(member.role, member.group, member.tier)
|
|
7283
8581
|
].join("\n");
|
|
7284
8582
|
}
|
|
7285
|
-
function codexCrewProfile(member) {
|
|
7286
|
-
const
|
|
8583
|
+
function codexCrewProfile(member, cheaper = false) {
|
|
8584
|
+
const tier = cheaper ? "fast" : member.tier;
|
|
8585
|
+
const { model, effort } = AGENT_PROFILES.codex.tierModels[tier];
|
|
7287
8586
|
if (effort === void 0)
|
|
7288
|
-
throw new Error(`[halfcycle] Codex's ${
|
|
7289
|
-
const body = crewProfileBody(member.role, member.group,
|
|
8587
|
+
throw new Error(`[halfcycle] Codex's ${tier} tier names no reasoning effort.`);
|
|
8588
|
+
const body = crewProfileBody(member.role, member.group, tier);
|
|
7290
8589
|
if (/[\\"]|[\u0000-\u0009\u000b-\u001f\u007f]/.test(body)) {
|
|
7291
8590
|
throw new Error("[halfcycle] a crew profile body holds a character a Codex role cannot carry as written.");
|
|
7292
8591
|
}
|
|
8592
|
+
const name = cheaper ? cheaperRoleNameOf(member.callsign) : member.callsign.toLowerCase();
|
|
7293
8593
|
return [
|
|
7294
|
-
`name = ${tomlBasicString(
|
|
7295
|
-
`description = ${tomlBasicString(crewProfileDescription(member))}`,
|
|
8594
|
+
`name = ${tomlBasicString(name)}`,
|
|
8595
|
+
`description = ${tomlBasicString(crewProfileDescription(member, cheaper))}`,
|
|
7296
8596
|
`model = ${tomlBasicString(model)}`,
|
|
7297
8597
|
`model_reasoning_effort = ${tomlBasicString(effort)}`,
|
|
7298
8598
|
`developer_instructions = """`,
|
|
@@ -7303,7 +8603,10 @@ function codexCrewProfile(member) {
|
|
|
7303
8603
|
function renderCrewProfiles(roster = CREW_ROSTER) {
|
|
7304
8604
|
return [
|
|
7305
8605
|
...roster.map((member) => ({ rel: crewProfileRel("claude-code", member.callsign), text: claudeCrewProfile(member) })),
|
|
7306
|
-
...roster.filter((member) => member.group !== "reviewer").
|
|
8606
|
+
...roster.filter((member) => member.group !== "reviewer").flatMap((member) => [
|
|
8607
|
+
{ rel: crewProfileRel("codex", member.callsign), text: codexCrewProfile(member) },
|
|
8608
|
+
...hasCheaperRole(member) ? [{ rel: crewProfileRel("codex", `${member.callsign}FAST`), text: codexCrewProfile(member, true) }] : []
|
|
8609
|
+
])
|
|
7307
8610
|
];
|
|
7308
8611
|
}
|
|
7309
8612
|
var __filename = fileURLToPath(import.meta.url);
|
|
@@ -7602,12 +8905,12 @@ function findNotShared(targetRepo, manifest, collidedPaths) {
|
|
|
7602
8905
|
if (ignored === null)
|
|
7603
8906
|
return null;
|
|
7604
8907
|
const sorted = [...ignored].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
7605
|
-
const
|
|
8908
|
+
const named2 = sorted.find((entry) => entry.path === SETTINGS_REL) ?? sorted[0];
|
|
7606
8909
|
return {
|
|
7607
|
-
path:
|
|
7608
|
-
pattern:
|
|
7609
|
-
source:
|
|
7610
|
-
line:
|
|
8910
|
+
path: named2.path,
|
|
8911
|
+
pattern: named2.pattern,
|
|
8912
|
+
source: named2.source,
|
|
8913
|
+
line: named2.line,
|
|
7611
8914
|
others: sorted.length - 1
|
|
7612
8915
|
};
|
|
7613
8916
|
}
|
|
@@ -8175,14 +9478,19 @@ ${GENERATED_USER_PROMPT_SCOPE_HEADER} the Halfcycle installer, now also for the
|
|
|
8175
9478
|
# <phase>/<part>", "question <phase>/<part>: <words>", "carry <phase>/<part>:
|
|
8176
9479
|
# <words>", "drop <phase>/<part>: <words>", "reject <phase>/<task>: <words>",
|
|
8177
9480
|
# "autonomy on" or "autonomy off". Also lets the person who is typing let one
|
|
8178
|
-
# step Halfcycle paused run once, by typing "allow" as the whole prompt
|
|
9481
|
+
# step Halfcycle paused run once, by typing "allow" as the whole prompt, and change
|
|
9482
|
+
# how a build is paced, by typing "faster", "run 5 agents", "keep 30% free" or
|
|
9483
|
+
# "use up to 70%" as the whole prompt, and answer a phase's usage limit by
|
|
9484
|
+
# typing "keep going", "stop", "limit it to 1.5 times" or "limit it to 300
|
|
9485
|
+
# million" as the whole prompt.
|
|
8179
9486
|
#
|
|
8180
9487
|
# AN ORDINARY PROMPT STARTS NOTHING. This script reads the hook input once and
|
|
8181
9488
|
# looks only at the start of the prompt's value. Unless the prompt begins, after
|
|
8182
9489
|
# any whitespace (typed, or escaped in the input), with "approve ", "send back ",
|
|
8183
9490
|
# "accept ", "question ", "carry ", "drop ", "reject " or "autonomy ", in any case,
|
|
8184
|
-
# or is "allow"
|
|
8185
|
-
#
|
|
9491
|
+
# or is "allow", one of the five pacing phrases or one of the usage-limit phrases
|
|
9492
|
+
# and nothing else but whitespace,
|
|
9493
|
+
# it exits 0 with no output, and Node never starts. A quote inside any other field arrives
|
|
8186
9494
|
# escaped, so no other field (the folder the session is in, for one) can read as
|
|
8187
9495
|
# the start of the prompt.
|
|
8188
9496
|
#
|
|
@@ -8208,7 +9516,7 @@ IFS= read -r -d '' HC_INPUT || true
|
|
|
8208
9516
|
# \\u escape, or as the UTF-8 bytes of a Unicode space. Compared byte for byte.
|
|
8209
9517
|
HC_LEAD='[[:space:]]|\\\\[nrtf]|\\\\u(000[9a-d]|0020|00a0|1680|200[0-9a]|202[89f]|205f|3000|feff)'
|
|
8210
9518
|
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'
|
|
8211
|
-
if ! printf '%s' "$HC_INPUT" | LC_ALL=C grep -Eiq "\\"prompt\\"[[:space:]]*:[[:space:]]*\\"($HC_LEAD)*((approve|send back|accept|question|carry|drop|reject|autonomy) |allow($HC_LEAD)*\\")"; then
|
|
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
|
|
8212
9520
|
exit 0
|
|
8213
9521
|
fi
|
|
8214
9522
|
|
|
@@ -9632,14 +10940,14 @@ async function bindLoopback(env = process.env, engagementId) {
|
|
|
9632
10940
|
else
|
|
9633
10941
|
pending = callback;
|
|
9634
10942
|
}, engagementId));
|
|
9635
|
-
const bindProblem = await new Promise((
|
|
10943
|
+
const bindProblem = await new Promise((resolve9) => {
|
|
9636
10944
|
const onError = (err) => {
|
|
9637
|
-
|
|
10945
|
+
resolve9(`${err.code ?? "bind failed"} on ${LOOPBACK_REDIRECT_HOST}:${wanted.port}`);
|
|
9638
10946
|
};
|
|
9639
10947
|
server.once("error", onError);
|
|
9640
10948
|
server.listen({ host: LOOPBACK_REDIRECT_HOST, port: wanted.port }, () => {
|
|
9641
10949
|
server.removeListener("error", onError);
|
|
9642
|
-
|
|
10950
|
+
resolve9(null);
|
|
9643
10951
|
});
|
|
9644
10952
|
});
|
|
9645
10953
|
if (bindProblem !== null) {
|
|
@@ -9655,9 +10963,9 @@ async function bindLoopback(env = process.env, engagementId) {
|
|
|
9655
10963
|
bound: true,
|
|
9656
10964
|
target: { port: address.port, state },
|
|
9657
10965
|
boundAddress: address.address,
|
|
9658
|
-
waitForCallback: ({ timeoutMs, onStillWaiting, stillWaitingMs = DEFAULT_STILL_WAITING_MS }) => new Promise((
|
|
10966
|
+
waitForCallback: ({ timeoutMs, onStillWaiting, stillWaitingMs = DEFAULT_STILL_WAITING_MS }) => new Promise((resolve9) => {
|
|
9659
10967
|
if (pending !== null) {
|
|
9660
|
-
|
|
10968
|
+
resolve9(pending);
|
|
9661
10969
|
return;
|
|
9662
10970
|
}
|
|
9663
10971
|
const startedAt = Date.now();
|
|
@@ -9668,7 +10976,7 @@ async function bindLoopback(env = process.env, engagementId) {
|
|
|
9668
10976
|
clearTimeout(timer);
|
|
9669
10977
|
deliver = null;
|
|
9670
10978
|
abandon = null;
|
|
9671
|
-
|
|
10979
|
+
resolve9(value);
|
|
9672
10980
|
};
|
|
9673
10981
|
const timer = setTimeout(() => finish(null), Math.max(0, timeoutMs));
|
|
9674
10982
|
deliver = finish;
|
|
@@ -9682,9 +10990,9 @@ async function bindLoopback(env = process.env, engagementId) {
|
|
|
9682
10990
|
};
|
|
9683
10991
|
}
|
|
9684
10992
|
function closeServer(server) {
|
|
9685
|
-
return new Promise((
|
|
10993
|
+
return new Promise((resolve9) => {
|
|
9686
10994
|
server.closeAllConnections();
|
|
9687
|
-
server.close(() =>
|
|
10995
|
+
server.close(() => resolve9());
|
|
9688
10996
|
});
|
|
9689
10997
|
}
|
|
9690
10998
|
|
|
@@ -9707,7 +11015,7 @@ function defaultWrite(text) {
|
|
|
9707
11015
|
process.stdout.write(text);
|
|
9708
11016
|
}
|
|
9709
11017
|
function defaultSleep(ms) {
|
|
9710
|
-
return new Promise((
|
|
11018
|
+
return new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
9711
11019
|
}
|
|
9712
11020
|
function browserOpenCommand(os, url) {
|
|
9713
11021
|
if (os === "darwin")
|
|
@@ -9754,12 +11062,12 @@ async function openBrowser(url, env = process.env, os = platform3(), spawnImpl =
|
|
|
9754
11062
|
};
|
|
9755
11063
|
}
|
|
9756
11064
|
const { command, args: args2, windowsVerbatimArguments } = browserOpenCommand(os, url);
|
|
9757
|
-
return new Promise((
|
|
11065
|
+
return new Promise((resolve9) => {
|
|
9758
11066
|
let child;
|
|
9759
11067
|
try {
|
|
9760
11068
|
child = spawnImpl(command, args2, { stdio: "ignore", env, windowsVerbatimArguments });
|
|
9761
11069
|
} catch {
|
|
9762
|
-
|
|
11070
|
+
resolve9({ opened: false, reason: `${command} could not be run` });
|
|
9763
11071
|
return;
|
|
9764
11072
|
}
|
|
9765
11073
|
let settled = false;
|
|
@@ -9768,7 +11076,7 @@ async function openBrowser(url, env = process.env, os = platform3(), spawnImpl =
|
|
|
9768
11076
|
return;
|
|
9769
11077
|
settled = true;
|
|
9770
11078
|
clearTimeout(timer);
|
|
9771
|
-
|
|
11079
|
+
resolve9(attempt);
|
|
9772
11080
|
};
|
|
9773
11081
|
const timer = setTimeout(() => {
|
|
9774
11082
|
child.unref();
|
|
@@ -9811,8 +11119,8 @@ async function startDeviceAuth(serviceUrl) {
|
|
|
9811
11119
|
}
|
|
9812
11120
|
const body = await res.json().catch(() => null);
|
|
9813
11121
|
if (res.status !== 201) {
|
|
9814
|
-
const
|
|
9815
|
-
throw new SignInRefused(
|
|
11122
|
+
const refusal3 = tryRefusal(body);
|
|
11123
|
+
throw new SignInRefused(refusal3?.status ?? String(res.status), refusal3?.message ?? `Sign-in could not be started: ${url} answered ${res.status}.`);
|
|
9816
11124
|
}
|
|
9817
11125
|
const parsed = deviceAuthStartResponseSchema.safeParse(body);
|
|
9818
11126
|
if (!parsed.success) {
|
|
@@ -9913,17 +11221,17 @@ async function pollBesideLoopback(serviceUrl, started, wake, deps) {
|
|
|
9913
11221
|
continue;
|
|
9914
11222
|
}
|
|
9915
11223
|
if (sleep === void 0) {
|
|
9916
|
-
await new Promise((
|
|
9917
|
-
const timer = setTimeout(
|
|
11224
|
+
await new Promise((resolve9) => {
|
|
11225
|
+
const timer = setTimeout(resolve9, started.pollIntervalMs);
|
|
9918
11226
|
interrupt = () => {
|
|
9919
11227
|
clearTimeout(timer);
|
|
9920
|
-
|
|
11228
|
+
resolve9();
|
|
9921
11229
|
};
|
|
9922
11230
|
});
|
|
9923
11231
|
} else {
|
|
9924
|
-
await new Promise((
|
|
9925
|
-
interrupt =
|
|
9926
|
-
void sleep(started.pollIntervalMs).then(
|
|
11232
|
+
await new Promise((resolve9) => {
|
|
11233
|
+
interrupt = resolve9;
|
|
11234
|
+
void sleep(started.pollIntervalMs).then(resolve9);
|
|
9927
11235
|
});
|
|
9928
11236
|
}
|
|
9929
11237
|
interrupt = null;
|
|
@@ -10111,9 +11419,9 @@ async function requestEngagementValues(method, url, requestBody, credential, sha
|
|
|
10111
11419
|
}
|
|
10112
11420
|
if (!res.ok) {
|
|
10113
11421
|
const detail = await res.text().catch(() => "");
|
|
10114
|
-
const
|
|
10115
|
-
if (
|
|
10116
|
-
throw new CreateEngagementRefused(res.status,
|
|
11422
|
+
const refusal3 = res.status === 404 ? void 0 : planeRefusal(detail);
|
|
11423
|
+
if (refusal3) {
|
|
11424
|
+
throw new CreateEngagementRefused(res.status, refusal3.message, refusal3.error, refusal3.reason, `[halfcycle] ${shape.action} failed: ${url} returned ${res.status}. ${refusal3.message} ${shape.nothingHappened}.`);
|
|
10117
11425
|
}
|
|
10118
11426
|
const wrongOrigin = res.status === 404 ? `That address answered, but it does not serve ${shape.route} \u2014 so it is not the Halfcycle service. ` : "";
|
|
10119
11427
|
throw new Error(`[halfcycle] ${shape.action} failed: ${url} returned ${res.status}. ${wrongOrigin}${detail ? `Response: ${detail.slice(0, 300)}. ` : ""}${shape.nothingHappened}. ` + CONTROL_ORIGIN_HINT);
|
|
@@ -10460,8 +11768,8 @@ function renderBlock(content, layout, environment) {
|
|
|
10460
11768
|
function renderLine(content, environment) {
|
|
10461
11769
|
const unicode = decideUnicode(environment);
|
|
10462
11770
|
const dot = unicode ? UNICODE_GLYPHS.dot : ASCII_GLYPHS.dot;
|
|
10463
|
-
const
|
|
10464
|
-
const raw = `${
|
|
11771
|
+
const named2 = content.version === "" ? "Halfcycle" : `Halfcycle v${content.version}`;
|
|
11772
|
+
const raw = `${named2} ${dot} ${content.url}`;
|
|
10465
11773
|
return unicode ? raw : toAscii(raw);
|
|
10466
11774
|
}
|
|
10467
11775
|
function renderBanner(content, environment = {}) {
|
|
@@ -10545,9 +11853,9 @@ function wrongAccountVisibility(act) {
|
|
|
10545
11853
|
async function askLine(question2) {
|
|
10546
11854
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
10547
11855
|
try {
|
|
10548
|
-
return await new Promise((
|
|
10549
|
-
rl.once("close", () =>
|
|
10550
|
-
rl.question(question2).then(
|
|
11856
|
+
return await new Promise((resolve9) => {
|
|
11857
|
+
rl.once("close", () => resolve9(""));
|
|
11858
|
+
rl.question(question2).then(resolve9, () => resolve9(""));
|
|
10551
11859
|
});
|
|
10552
11860
|
} finally {
|
|
10553
11861
|
rl.close();
|
|
@@ -10814,10 +12122,10 @@ var SYSTEM_FOLDERS = [
|
|
|
10814
12122
|
"C:\\Program Files",
|
|
10815
12123
|
"C:\\Program Files (x86)"
|
|
10816
12124
|
];
|
|
10817
|
-
function refuseSystemFolder(targetRepo,
|
|
12125
|
+
function refuseSystemFolder(targetRepo, platform5 = process.platform) {
|
|
10818
12126
|
const key = (folder) => {
|
|
10819
12127
|
const real = realOrLiteral(folder);
|
|
10820
|
-
return
|
|
12128
|
+
return platform5 === "win32" ? real.toLowerCase() : real;
|
|
10821
12129
|
};
|
|
10822
12130
|
const target = key(targetRepo);
|
|
10823
12131
|
if (SYSTEM_FOLDERS.some((folder) => key(folder) === target)) {
|
|
@@ -10967,9 +12275,9 @@ var WORDS = {
|
|
|
10967
12275
|
planFailed: "halfcycle: setting up this folder would stop on a file that could not be read. Nothing has been written; fix that file, then run the preview again."
|
|
10968
12276
|
};
|
|
10969
12277
|
function runPreview(targetRepo, options) {
|
|
10970
|
-
const
|
|
10971
|
-
if (
|
|
10972
|
-
return { refusal:
|
|
12278
|
+
const refusal3 = previewRefusal(targetRepo, options);
|
|
12279
|
+
if (refusal3 !== void 0)
|
|
12280
|
+
return { refusal: refusal3 };
|
|
10973
12281
|
let plan;
|
|
10974
12282
|
try {
|
|
10975
12283
|
plan = planInstall({ targetRepo, env: options.env, home: options.home });
|
|
@@ -11395,105 +12703,6 @@ function isObject(value) {
|
|
|
11395
12703
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11396
12704
|
}
|
|
11397
12705
|
|
|
11398
|
-
// dist/claude-plan.js
|
|
11399
|
-
import { chmodSync as chmodSync3, mkdirSync as mkdirSync6, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "node:fs";
|
|
11400
|
-
import { platform as platform4 } from "node:os";
|
|
11401
|
-
import { dirname as dirname5, join as join11 } from "node:path";
|
|
11402
|
-
var CLAUDE_PLAN_FILENAME = "claude-plan.json";
|
|
11403
|
-
var PLAN_QUESTION_TRIES = 3;
|
|
11404
|
-
var SEND_TIMEOUT_MS = 5e3;
|
|
11405
|
-
var SETTINGS_PAGE_SEGMENT = "settings";
|
|
11406
|
-
function claudePlanPath(home) {
|
|
11407
|
-
return join11(halfcycleHome(home), CLAUDE_PLAN_FILENAME);
|
|
11408
|
-
}
|
|
11409
|
-
function readRememberedClaudePlan(home) {
|
|
11410
|
-
try {
|
|
11411
|
-
const parsed = claudePlanRequestSchema.safeParse(JSON.parse(readFileSync11(claudePlanPath(home), "utf-8")));
|
|
11412
|
-
return parsed.success ? parsed.data.claudePlan : void 0;
|
|
11413
|
-
} catch {
|
|
11414
|
-
return void 0;
|
|
11415
|
-
}
|
|
11416
|
-
}
|
|
11417
|
-
function rememberClaudePlan(plan, home) {
|
|
11418
|
-
const path = claudePlanPath(home);
|
|
11419
|
-
try {
|
|
11420
|
-
mkdirSync6(dirname5(path), { recursive: true, mode: 448 });
|
|
11421
|
-
writeFileSync6(path, `${JSON.stringify(claudePlanRequestSchema.parse({ claudePlan: plan }))}
|
|
11422
|
-
`, { mode: 384 });
|
|
11423
|
-
if (platform4() !== "win32")
|
|
11424
|
-
chmodSync3(path, 384);
|
|
11425
|
-
return true;
|
|
11426
|
-
} catch {
|
|
11427
|
-
return false;
|
|
11428
|
-
}
|
|
11429
|
-
}
|
|
11430
|
-
function settingsPageUrl(consoleUrl) {
|
|
11431
|
-
if (consoleUrl === void 0 || consoleUrl.trim() === "")
|
|
11432
|
-
return void 0;
|
|
11433
|
-
return `${consoleUrl.replace(/\/+$/, "")}/${SETTINGS_PAGE_SEGMENT}`;
|
|
11434
|
-
}
|
|
11435
|
-
async function sendClaudePlan(serviceUrl, engagementId, credential, plan, fetchImpl = fetch) {
|
|
11436
|
-
const url = `${serviceUrl.replace(/\/+$/, "")}/engagements/${encodeURIComponent(engagementId)}/claude-plan`;
|
|
11437
|
-
const controller = new AbortController();
|
|
11438
|
-
const timer = setTimeout(() => controller.abort(), SEND_TIMEOUT_MS);
|
|
11439
|
-
try {
|
|
11440
|
-
const res = await fetchImpl(url, {
|
|
11441
|
-
method: "PUT",
|
|
11442
|
-
headers: { authorization: `Bearer ${credential}`, "content-type": "application/json" },
|
|
11443
|
-
body: JSON.stringify(claudePlanRequestSchema.parse({ claudePlan: plan })),
|
|
11444
|
-
signal: controller.signal
|
|
11445
|
-
});
|
|
11446
|
-
return res.ok;
|
|
11447
|
-
} catch {
|
|
11448
|
-
return false;
|
|
11449
|
-
} finally {
|
|
11450
|
-
clearTimeout(timer);
|
|
11451
|
-
}
|
|
11452
|
-
}
|
|
11453
|
-
function accountCredential(serviceUrl, env, home) {
|
|
11454
|
-
const fromEnv = env["HALFCYCLE_TOKEN"]?.trim();
|
|
11455
|
-
if (fromEnv)
|
|
11456
|
-
return fromEnv;
|
|
11457
|
-
return readStoredCredential(serviceUrl, home)?.credential;
|
|
11458
|
-
}
|
|
11459
|
-
async function setupClaudePlan(input, deps) {
|
|
11460
|
-
try {
|
|
11461
|
-
if (!input.ownsProject)
|
|
11462
|
-
return { kind: "not-owner" };
|
|
11463
|
-
if (!deps.interactive())
|
|
11464
|
-
return { kind: "no-person" };
|
|
11465
|
-
const env = deps.env ?? process.env;
|
|
11466
|
-
const send2 = async (plan2) => {
|
|
11467
|
-
const credential = accountCredential(input.serviceUrl, env, deps.home);
|
|
11468
|
-
if (credential === void 0)
|
|
11469
|
-
return false;
|
|
11470
|
-
return sendClaudePlan(input.serviceUrl, input.engagementId, credential, plan2, deps.fetchImpl);
|
|
11471
|
-
};
|
|
11472
|
-
const remembered = readRememberedClaudePlan(deps.home);
|
|
11473
|
-
if (remembered !== void 0) {
|
|
11474
|
-
const sent = await send2(remembered);
|
|
11475
|
-
const settingsUrl = settingsPageUrl(input.consoleUrl);
|
|
11476
|
-
if (sent && settingsUrl !== void 0) {
|
|
11477
|
-
deps.write(`[halfcycle] ${claudePlanRememberedText(remembered, settingsUrl)}
|
|
11478
|
-
`);
|
|
11479
|
-
}
|
|
11480
|
-
return { kind: "remembered", plan: remembered, sent };
|
|
11481
|
-
}
|
|
11482
|
-
if (deps.assumeYes === true)
|
|
11483
|
-
return { kind: "no-person" };
|
|
11484
|
-
let plan = null;
|
|
11485
|
-
for (let tries = 0; tries < PLAN_QUESTION_TRIES && plan === null; tries++) {
|
|
11486
|
-
plan = claudePlanFromAnswer(await deps.ask(`${CLAUDE_PLAN_QUESTION} `));
|
|
11487
|
-
}
|
|
11488
|
-
if (plan === null)
|
|
11489
|
-
return { kind: "no-answer" };
|
|
11490
|
-
rememberClaudePlan(plan, deps.home);
|
|
11491
|
-
return { kind: "answered", plan, sent: await send2(plan) };
|
|
11492
|
-
} catch {
|
|
11493
|
-
return { kind: "failed" };
|
|
11494
|
-
}
|
|
11495
|
-
}
|
|
11496
|
-
|
|
11497
12706
|
// dist/probe-mcp.js
|
|
11498
12707
|
var PROBE_TIMEOUT_MS = 5e3;
|
|
11499
12708
|
async function probeMcpOrigin(mcpUrl, token) {
|
|
@@ -11549,12 +12758,12 @@ async function probeMcpOrigin(mcpUrl, token) {
|
|
|
11549
12758
|
// dist/claude-version-probe.js
|
|
11550
12759
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
11551
12760
|
import { existsSync as existsSync8, realpathSync as realpathSync4 } from "node:fs";
|
|
11552
|
-
import { delimiter as delimiter2, join as
|
|
12761
|
+
import { delimiter as delimiter2, join as join11 } from "node:path";
|
|
11553
12762
|
function execVersionCommand(command, timeoutMs, deps) {
|
|
11554
|
-
const
|
|
12763
|
+
const platform5 = deps.platform ?? process.platform;
|
|
11555
12764
|
const exec = deps.exec ?? execFileSync3;
|
|
11556
12765
|
try {
|
|
11557
|
-
return
|
|
12766
|
+
return platform5 === "win32" ? exec("cmd.exe", ["/d", "/s", "/c", `"${command} --version"`], {
|
|
11558
12767
|
encoding: "utf-8",
|
|
11559
12768
|
timeout: timeoutMs,
|
|
11560
12769
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -11591,7 +12800,7 @@ var INSTALL_METHOD_RULES = [
|
|
|
11591
12800
|
];
|
|
11592
12801
|
function probeClaudeCodeInstallMethod(deps = {}) {
|
|
11593
12802
|
const env = deps.env ?? process.env;
|
|
11594
|
-
const
|
|
12803
|
+
const platform5 = deps.platform ?? process.platform;
|
|
11595
12804
|
const exists = deps.exists ?? existsSync8;
|
|
11596
12805
|
const realpath = deps.realpath ?? ((file) => {
|
|
11597
12806
|
try {
|
|
@@ -11600,11 +12809,11 @@ function probeClaudeCodeInstallMethod(deps = {}) {
|
|
|
11600
12809
|
return file;
|
|
11601
12810
|
}
|
|
11602
12811
|
});
|
|
11603
|
-
const names =
|
|
12812
|
+
const names = platform5 === "win32" ? ["claude.exe", "claude.cmd"] : ["claude"];
|
|
11604
12813
|
const dirs = (env["PATH"] ?? "").split(delimiter2).filter((dir) => dir !== "");
|
|
11605
12814
|
for (const dir of dirs) {
|
|
11606
12815
|
for (const name of names) {
|
|
11607
|
-
const file =
|
|
12816
|
+
const file = join11(dir, name);
|
|
11608
12817
|
if (!exists(file))
|
|
11609
12818
|
continue;
|
|
11610
12819
|
const real = realpath(file);
|
|
@@ -11639,8 +12848,8 @@ function codexInstallLine(codex) {
|
|
|
11639
12848
|
}
|
|
11640
12849
|
|
|
11641
12850
|
// dist/build-record/write.js
|
|
11642
|
-
import { existsSync as existsSync9, mkdirSync as
|
|
11643
|
-
import { basename as basename2, join as
|
|
12851
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync6, rmSync as rmSync4 } from "node:fs";
|
|
12852
|
+
import { basename as basename2, join as join12, resolve as resolve3, relative as relative2, isAbsolute as isAbsolute3 } from "node:path";
|
|
11644
12853
|
|
|
11645
12854
|
// dist/build-record/render-v2.js
|
|
11646
12855
|
var HIDDEN = /[\p{Cc}\p{Zl}\p{Zp}\p{Default_Ignorable_Code_Point}]/u;
|
|
@@ -11987,10 +13196,10 @@ function leftForYouLines(left, p) {
|
|
|
11987
13196
|
return lines;
|
|
11988
13197
|
}
|
|
11989
13198
|
function shownBy(steps) {
|
|
11990
|
-
const
|
|
11991
|
-
if (
|
|
13199
|
+
const named2 = steps.filter((s) => s.name !== void 0).length;
|
|
13200
|
+
if (named2 === 0)
|
|
11992
13201
|
return "Each is shown by the method's own label for it:";
|
|
11993
|
-
if (
|
|
13202
|
+
if (named2 === steps.length)
|
|
11994
13203
|
return "Each is shown by the method's own name for it:";
|
|
11995
13204
|
return "Each is shown by the method's own name for it, or by its label where the record holds no name:";
|
|
11996
13205
|
}
|
|
@@ -12226,7 +13435,7 @@ function renderBuildRecordV2Markdown(record2) {
|
|
|
12226
13435
|
}
|
|
12227
13436
|
|
|
12228
13437
|
// dist/build-record/pair-io.js
|
|
12229
|
-
import { closeSync, openSync, readFileSync as
|
|
13438
|
+
import { closeSync, openSync, readFileSync as readFileSync11, renameSync as renameSync2, rmSync as rmSync3, writeSync } from "node:fs";
|
|
12230
13439
|
function createExclusiveOnDisk(path, data) {
|
|
12231
13440
|
const bytes = typeof data === "string" ? Buffer.from(data, "utf-8") : data;
|
|
12232
13441
|
const fd = openSync(path, "wx");
|
|
@@ -12257,7 +13466,7 @@ function isEexist(err) {
|
|
|
12257
13466
|
}
|
|
12258
13467
|
function readIfPresent(path) {
|
|
12259
13468
|
try {
|
|
12260
|
-
return
|
|
13469
|
+
return readFileSync11(path, "utf-8");
|
|
12261
13470
|
} catch {
|
|
12262
13471
|
return null;
|
|
12263
13472
|
}
|
|
@@ -12289,7 +13498,7 @@ function replaceOne(io, tmp, finalPath, text) {
|
|
|
12289
13498
|
}
|
|
12290
13499
|
|
|
12291
13500
|
// dist/build-record/write.js
|
|
12292
|
-
var BUILD_RECORD_DIR =
|
|
13501
|
+
var BUILD_RECORD_DIR = join12("docs", "build-records");
|
|
12293
13502
|
function assertInsideOutDir(outDir, candidate, identity) {
|
|
12294
13503
|
const rel = relative2(outDir, resolve3(candidate));
|
|
12295
13504
|
const isPlainFilename = rel !== "" && rel !== ".." && !isAbsolute3(rel) && rel === basename2(rel);
|
|
@@ -12327,8 +13536,8 @@ function writeRecordPair(repoRoot, phase, rawRecord, record2, io = NODE_RECORD_P
|
|
|
12327
13536
|
}
|
|
12328
13537
|
const outDir = resolve3(repoRoot, BUILD_RECORD_DIR);
|
|
12329
13538
|
const segment = renderPhaseSegment(phase);
|
|
12330
|
-
const jsonPath =
|
|
12331
|
-
const mdPath =
|
|
13539
|
+
const jsonPath = join12(outDir, `${segment}.json`);
|
|
13540
|
+
const mdPath = join12(outDir, `${segment}.md`);
|
|
12332
13541
|
const jsonTmp = `${jsonPath}.tmp`;
|
|
12333
13542
|
const mdTmp = `${mdPath}.tmp`;
|
|
12334
13543
|
for (const candidate of [jsonPath, mdPath, jsonTmp, mdTmp]) {
|
|
@@ -12336,7 +13545,7 @@ function writeRecordPair(repoRoot, phase, rawRecord, record2, io = NODE_RECORD_P
|
|
|
12336
13545
|
}
|
|
12337
13546
|
const jsonText = serialiseServedRecord(rawRecord);
|
|
12338
13547
|
const mdText = renderBuildRecordV2Markdown(record2);
|
|
12339
|
-
|
|
13548
|
+
mkdirSync6(outDir, { recursive: true });
|
|
12340
13549
|
if (createPairExclusive(io, jsonPath, jsonText, mdPath, mdText) === "created") {
|
|
12341
13550
|
return { kind: "created", jsonPath, mdPath };
|
|
12342
13551
|
}
|
|
@@ -12374,14 +13583,16 @@ import { join as join20 } from "node:path";
|
|
|
12374
13583
|
|
|
12375
13584
|
// dist/close-phase.js
|
|
12376
13585
|
import { execFile, execFileSync as execFileSync5 } from "node:child_process";
|
|
13586
|
+
import { readFileSync as readFileSync16 } from "node:fs";
|
|
13587
|
+
import { join as join19 } from "node:path";
|
|
12377
13588
|
|
|
12378
13589
|
// dist/plan.js
|
|
12379
|
-
import { existsSync as existsSync11, readFileSync as
|
|
12380
|
-
import { join as
|
|
13590
|
+
import { existsSync as existsSync11, readFileSync as readFileSync15 } from "node:fs";
|
|
13591
|
+
import { join as join18 } from "node:path";
|
|
12381
13592
|
|
|
12382
13593
|
// dist/draw/write.js
|
|
12383
|
-
import { closeSync as closeSync2, lstatSync as lstatSync2, mkdirSync as
|
|
12384
|
-
import { dirname as
|
|
13594
|
+
import { closeSync as closeSync2, lstatSync as lstatSync2, mkdirSync as mkdirSync7, openSync as openSync2, readFileSync as readFileSync12, readSync, realpathSync as realpathSync5, rmSync as rmSync5 } from "node:fs";
|
|
13595
|
+
import { dirname as dirname5, isAbsolute as isAbsolute4, join as join13, relative as relative3, resolve as resolve4, sep } from "node:path";
|
|
12385
13596
|
var DOCS_DIR = "docs";
|
|
12386
13597
|
var DIAGRAMS_DIR = "diagrams";
|
|
12387
13598
|
var DIAGRAM_DIR_REL = `${DOCS_DIR}/${DIAGRAMS_DIR}`;
|
|
@@ -12437,9 +13648,9 @@ function embedImageLink(base, doc) {
|
|
|
12437
13648
|
}
|
|
12438
13649
|
function diagramPaths(repoRoot, base) {
|
|
12439
13650
|
const root = resolve4(repoRoot);
|
|
12440
|
-
const dir =
|
|
13651
|
+
const dir = join13(root, DOCS_DIR, DIAGRAMS_DIR);
|
|
12441
13652
|
const names = fileNames(base);
|
|
12442
|
-
const file = Object.fromEntries(DIAGRAM_FILES.map((which) => [which,
|
|
13653
|
+
const file = Object.fromEntries(DIAGRAM_FILES.map((which) => [which, join13(dir, names[which])]));
|
|
12443
13654
|
return { repoRoot: root, dir, file };
|
|
12444
13655
|
}
|
|
12445
13656
|
function diagramRel(base, which) {
|
|
@@ -12474,7 +13685,7 @@ function checkDiagramPlace(paths) {
|
|
|
12474
13685
|
}
|
|
12475
13686
|
let ancestor = paths.dir;
|
|
12476
13687
|
while (lstatOrNull2(ancestor) === null) {
|
|
12477
|
-
const parent =
|
|
13688
|
+
const parent = dirname5(ancestor);
|
|
12478
13689
|
if (parent === ancestor)
|
|
12479
13690
|
break;
|
|
12480
13691
|
ancestor = parent;
|
|
@@ -12534,7 +13745,7 @@ function readExistingSet(paths) {
|
|
|
12534
13745
|
if (!stat.isFile())
|
|
12535
13746
|
return { ok: false, inTheWay: which, temp: false };
|
|
12536
13747
|
try {
|
|
12537
|
-
found[which] =
|
|
13748
|
+
found[which] = readFileSync12(paths.file[which]);
|
|
12538
13749
|
} catch {
|
|
12539
13750
|
return { ok: false, inTheWay: which, temp: false };
|
|
12540
13751
|
}
|
|
@@ -12637,7 +13848,7 @@ function writeDiagramSet(repoRoot, base, set, io = NODE_RECORD_PAIR_IO) {
|
|
|
12637
13848
|
if (changed.length === 0)
|
|
12638
13849
|
return { kind: "up-to-date" };
|
|
12639
13850
|
try {
|
|
12640
|
-
|
|
13851
|
+
mkdirSync7(paths.dir, { recursive: true });
|
|
12641
13852
|
} catch {
|
|
12642
13853
|
return { kind: "failed", file: changed[0], removed: [] };
|
|
12643
13854
|
}
|
|
@@ -12712,10 +13923,10 @@ function isMissingRoute(method, status, body) {
|
|
|
12712
13923
|
}
|
|
12713
13924
|
|
|
12714
13925
|
// dist/change.js
|
|
12715
|
-
import { join as
|
|
13926
|
+
import { join as join16 } from "node:path";
|
|
12716
13927
|
|
|
12717
13928
|
// dist/cli-contract.js
|
|
12718
|
-
var CLI_VERBS = ["install", "check-drift", "build-record", "open-phase", "close-phase", "system", "draw", "plan", "review", "check-tasks", "uninstall", "change", "pace", "prove", "preview", "doctor", "restore"];
|
|
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"];
|
|
12719
13930
|
var CONTRACTS = [
|
|
12720
13931
|
{ verb: "install", required: [], conditional: [] },
|
|
12721
13932
|
{ verb: "check-drift", required: [], conditional: [] },
|
|
@@ -12725,12 +13936,26 @@ var CONTRACTS = [
|
|
|
12725
13936
|
{ verb: "plan", required: [], conditional: [] },
|
|
12726
13937
|
{ verb: "review", required: [], conditional: [] },
|
|
12727
13938
|
{ verb: "check-tasks", required: [], conditional: [] },
|
|
13939
|
+
{ verb: "check-criteria", required: [], conditional: [] },
|
|
13940
|
+
{ verb: "ready", required: [], conditional: [] },
|
|
12728
13941
|
{ verb: "uninstall", required: [], conditional: [] },
|
|
12729
13942
|
{ verb: "pace", required: [], conditional: [] },
|
|
13943
|
+
{ verb: "route", required: [], conditional: [] },
|
|
12730
13944
|
{ verb: "prove", required: [], conditional: [] },
|
|
12731
13945
|
{ verb: "preview", required: [], conditional: [] },
|
|
12732
13946
|
{ verb: "doctor", required: [], conditional: [] },
|
|
12733
13947
|
{ verb: "restore", required: [], conditional: [] },
|
|
13948
|
+
{
|
|
13949
|
+
verb: "check-report",
|
|
13950
|
+
required: [
|
|
13951
|
+
{
|
|
13952
|
+
flag: "--head",
|
|
13953
|
+
takesValue: true,
|
|
13954
|
+
missingMessage: "halfcycle check-report: --head is required \u2014 give the full commit id of the work the report is about, so the report can be checked against it."
|
|
13955
|
+
}
|
|
13956
|
+
],
|
|
13957
|
+
conditional: []
|
|
13958
|
+
},
|
|
12734
13959
|
{
|
|
12735
13960
|
verb: "open-phase",
|
|
12736
13961
|
required: [
|
|
@@ -12910,12 +14135,12 @@ function placeholderRefusal(verb, tokens) {
|
|
|
12910
14135
|
|
|
12911
14136
|
// dist/system-report.js
|
|
12912
14137
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
12913
|
-
import { readFileSync as
|
|
12914
|
-
import { join as
|
|
14138
|
+
import { readFileSync as readFileSync13 } from "node:fs";
|
|
14139
|
+
import { join as join14 } from "node:path";
|
|
12915
14140
|
function readDescriptionFile(repoRoot) {
|
|
12916
14141
|
let text;
|
|
12917
14142
|
try {
|
|
12918
|
-
text =
|
|
14143
|
+
text = readFileSync13(join14(repoRoot, SYSTEM_DESCRIPTION_REL), "utf-8");
|
|
12919
14144
|
} catch (err) {
|
|
12920
14145
|
const code = err.code;
|
|
12921
14146
|
return code === "ENOENT" || code === "ENOTDIR" ? { kind: "missing" } : { kind: "unreadable" };
|
|
@@ -13090,9 +14315,9 @@ async function checkAndReportDescription(repoRoot, deps, verb, beforeSend = () =
|
|
|
13090
14315
|
}
|
|
13091
14316
|
const unowned = unownedPatterns(checked.description, deps.listTracked(repoRoot));
|
|
13092
14317
|
const warnings = unowned === null ? "" : unownedPatternLines(unowned, verb);
|
|
13093
|
-
const
|
|
13094
|
-
if (
|
|
13095
|
-
return { kind: "stopped", stderr: warnings +
|
|
14318
|
+
const refusal3 = beforeSend(checked.description);
|
|
14319
|
+
if (refusal3 !== null)
|
|
14320
|
+
return { kind: "stopped", stderr: warnings + refusal3 };
|
|
13096
14321
|
const credentialRead = readCredentialOrLine(repoRoot, deps, verb);
|
|
13097
14322
|
if (!credentialRead.ok)
|
|
13098
14323
|
return { kind: "stopped", stderr: warnings + credentialRead.line };
|
|
@@ -13139,8 +14364,8 @@ async function runSystem(repoRoot, deps) {
|
|
|
13139
14364
|
}
|
|
13140
14365
|
|
|
13141
14366
|
// dist/build-record/write-change.js
|
|
13142
|
-
import { existsSync as existsSync10, mkdirSync as
|
|
13143
|
-
import { basename as basename3, isAbsolute as isAbsolute5, join as
|
|
14367
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync8 } from "node:fs";
|
|
14368
|
+
import { basename as basename3, isAbsolute as isAbsolute5, join as join15, relative as relative4, resolve as resolve5 } from "node:path";
|
|
13144
14369
|
|
|
13145
14370
|
// dist/build-record/render-change.js
|
|
13146
14371
|
function counted2(n, one, many) {
|
|
@@ -13285,7 +14510,7 @@ function renderChangeRecordMarkdown(record2) {
|
|
|
13285
14510
|
}
|
|
13286
14511
|
|
|
13287
14512
|
// dist/build-record/write-change.js
|
|
13288
|
-
var CHANGE_RECORD_DIR =
|
|
14513
|
+
var CHANGE_RECORD_DIR = join15("docs", "build-records", "changes");
|
|
13289
14514
|
var InvalidChangeIdError = class extends Error {
|
|
13290
14515
|
id;
|
|
13291
14516
|
constructor(id) {
|
|
@@ -13305,15 +14530,15 @@ function writeChangeRecordPair(repoRoot, sortingId, rawRecord, record2, io = NOD
|
|
|
13305
14530
|
if (!checked.success)
|
|
13306
14531
|
throw new InvalidChangeIdError(sortingId);
|
|
13307
14532
|
const outDir = resolve5(repoRoot, CHANGE_RECORD_DIR);
|
|
13308
|
-
const jsonPath =
|
|
13309
|
-
const mdPath =
|
|
14533
|
+
const jsonPath = join15(outDir, `${checked.data}.json`);
|
|
14534
|
+
const mdPath = join15(outDir, `${checked.data}.md`);
|
|
13310
14535
|
const mdTmp = `${mdPath}.tmp`;
|
|
13311
14536
|
for (const candidate of [jsonPath, mdPath, mdTmp]) {
|
|
13312
14537
|
assertInsideChangeDir(outDir, candidate, sortingId);
|
|
13313
14538
|
}
|
|
13314
14539
|
const jsonText = serialiseServedRecord(rawRecord);
|
|
13315
14540
|
const mdText = renderChangeRecordMarkdown(record2);
|
|
13316
|
-
|
|
14541
|
+
mkdirSync8(outDir, { recursive: true });
|
|
13317
14542
|
if (createPairExclusive(io, jsonPath, jsonText, mdPath, mdText) === "created") {
|
|
13318
14543
|
return { kind: "created", jsonPath, mdPath };
|
|
13319
14544
|
}
|
|
@@ -13457,16 +14682,16 @@ function parseChangeArgs(rest) {
|
|
|
13457
14682
|
return notAChangeId("up", change);
|
|
13458
14683
|
return { ok: true, args: { form: "up", to, target: { kind: "change", sortingId: change }, ...where } };
|
|
13459
14684
|
}
|
|
13460
|
-
const
|
|
13461
|
-
if (!isValidPhaseIdentity(
|
|
13462
|
-
return { ok: false, line: `halfcycle change up: '${
|
|
14685
|
+
const named2 = phase ?? "";
|
|
14686
|
+
if (!isValidPhaseIdentity(named2)) {
|
|
14687
|
+
return { ok: false, line: `halfcycle change up: '${named2}' cannot be a phase name. Nothing was sent.
|
|
13463
14688
|
`, exitCode: 2 };
|
|
13464
14689
|
}
|
|
13465
14690
|
if (to === "mini") {
|
|
13466
14691
|
return { ok: false, line: `halfcycle change up: ${CHANGE_PATH_WORDS["only-up"]} Nothing was sent.
|
|
13467
14692
|
`, exitCode: 1 };
|
|
13468
14693
|
}
|
|
13469
|
-
return { ok: true, args: { form: "up", to, target: { kind: "phase", phase:
|
|
14694
|
+
return { ok: true, args: { form: "up", to, target: { kind: "phase", phase: named2 }, ...where } };
|
|
13470
14695
|
}
|
|
13471
14696
|
var OBSERVE_CHANGE_UNREACHABLE = { outcome: "not-sent", reason: "unreachable" };
|
|
13472
14697
|
function readObserveChangeLine(stdout) {
|
|
@@ -13557,9 +14782,9 @@ async function readChangeRecord(deps, credential, sortingId) {
|
|
|
13557
14782
|
}
|
|
13558
14783
|
if (isMissingRoute("GET", status, raw))
|
|
13559
14784
|
return { kind: "no-route" };
|
|
13560
|
-
const
|
|
13561
|
-
if (
|
|
13562
|
-
return { kind: "refused", line:
|
|
14785
|
+
const refusal3 = changeRefusalErrorSchema.safeParse(raw);
|
|
14786
|
+
if (refusal3.success)
|
|
14787
|
+
return { kind: "refused", line: refusal3.data.message };
|
|
13563
14788
|
if (status === 404) {
|
|
13564
14789
|
const gone = wireErrorSchema.safeParse(raw);
|
|
13565
14790
|
if (gone.success && gone.data.error === "EngagementNotFound") {
|
|
@@ -13582,7 +14807,7 @@ function writePairLines(deps, repoRoot, sortingId, found) {
|
|
|
13582
14807
|
};
|
|
13583
14808
|
}
|
|
13584
14809
|
return {
|
|
13585
|
-
stdout: `[halfcycle] Its record is in ${
|
|
14810
|
+
stdout: `[halfcycle] Its record is in ${join16(CHANGE_RECORD_DIR, `${sortingId}.md`)}.
|
|
13586
14811
|
`,
|
|
13587
14812
|
stderr: "",
|
|
13588
14813
|
ok: true
|
|
@@ -13821,27 +15046,27 @@ function runChange(repoRoot, args2, deps) {
|
|
|
13821
15046
|
}
|
|
13822
15047
|
|
|
13823
15048
|
// dist/shown-record.js
|
|
13824
|
-
import { chmodSync as
|
|
13825
|
-
import { platform as
|
|
13826
|
-
import { join as
|
|
15049
|
+
import { chmodSync as chmodSync3, mkdirSync as mkdirSync9, readFileSync as readFileSync14, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync6 } from "node:fs";
|
|
15050
|
+
import { platform as platform4 } from "node:os";
|
|
15051
|
+
import { join as join17 } from "node:path";
|
|
13827
15052
|
function updateShownRecord(engagementId, update, home) {
|
|
13828
15053
|
let temp = null;
|
|
13829
15054
|
try {
|
|
13830
15055
|
const dir = engagementStateDir(engagementId, home);
|
|
13831
|
-
const path =
|
|
15056
|
+
const path = join17(dir, SHOWN_PLANS_FILENAME);
|
|
13832
15057
|
let current;
|
|
13833
15058
|
try {
|
|
13834
|
-
current = parseShownRecord(
|
|
15059
|
+
current = parseShownRecord(readFileSync14(path, "utf-8"));
|
|
13835
15060
|
} catch {
|
|
13836
15061
|
current = { entries: [], reviews: [] };
|
|
13837
15062
|
}
|
|
13838
15063
|
const next = update(current);
|
|
13839
15064
|
const body = serialiseShownRecord(next);
|
|
13840
|
-
|
|
13841
|
-
temp =
|
|
13842
|
-
|
|
13843
|
-
if (
|
|
13844
|
-
|
|
15065
|
+
mkdirSync9(dir, { recursive: true, mode: 448 });
|
|
15066
|
+
temp = join17(dir, `.${SHOWN_PLANS_FILENAME}.${process.pid}.tmp`);
|
|
15067
|
+
writeFileSync6(temp, body, { mode: 384 });
|
|
15068
|
+
if (platform4() !== "win32")
|
|
15069
|
+
chmodSync3(temp, 384);
|
|
13845
15070
|
renameSync3(temp, path);
|
|
13846
15071
|
return { ok: true, record: next };
|
|
13847
15072
|
} catch {
|
|
@@ -13915,7 +15140,7 @@ function readAndCheckPlan(repoRoot, phase, description, verb) {
|
|
|
13915
15140
|
const fileRel = planFileRel(phase);
|
|
13916
15141
|
let text;
|
|
13917
15142
|
try {
|
|
13918
|
-
text =
|
|
15143
|
+
text = readFileSync15(join18(repoRoot, fileRel), "utf-8");
|
|
13919
15144
|
} catch (err) {
|
|
13920
15145
|
const code = err.code;
|
|
13921
15146
|
if (code === "ENOENT" || code === "ENOTDIR") {
|
|
@@ -13939,11 +15164,11 @@ function readAndCheckPlan(repoRoot, phase, description, verb) {
|
|
|
13939
15164
|
return { ok: false, line: `${p} ${fileRel} is not valid JSON (${why}). Fix it, then run this again. Nothing was sent.
|
|
13940
15165
|
` };
|
|
13941
15166
|
}
|
|
13942
|
-
const
|
|
13943
|
-
if (isValidPhaseIdentity(
|
|
15167
|
+
const named2 = typeof raw === "object" && raw !== null ? raw["phase"] : void 0;
|
|
15168
|
+
if (isValidPhaseIdentity(named2) && renderPhaseSegment(named2) !== renderPhaseSegment(phase)) {
|
|
13944
15169
|
return {
|
|
13945
15170
|
ok: false,
|
|
13946
|
-
line: `${p} ${fileRel} is the plan for phase "${String(
|
|
15171
|
+
line: `${p} ${fileRel} is the plan for phase "${String(named2)}", not phase "${phase}". Run this for the phase the plan names, or correct the plan's phase. Nothing was sent.
|
|
13947
15172
|
`
|
|
13948
15173
|
};
|
|
13949
15174
|
}
|
|
@@ -14071,7 +15296,7 @@ function removalOwedLines(verb, parts) {
|
|
|
14071
15296
|
function redrawLine(repoRoot, phase) {
|
|
14072
15297
|
let exists;
|
|
14073
15298
|
try {
|
|
14074
|
-
exists = existsSync11(
|
|
15299
|
+
exists = existsSync11(join18(repoRoot, DIAGRAM_DIR_REL, embedImageName(phaseDiagramBase(phase))));
|
|
14075
15300
|
} catch {
|
|
14076
15301
|
return null;
|
|
14077
15302
|
}
|
|
@@ -14658,7 +15883,7 @@ function readObserveLine(stdout) {
|
|
|
14658
15883
|
function runRunnerChild(binary, args2, credential, repoRoot, readLine, fallback) {
|
|
14659
15884
|
if (binary instanceof Error)
|
|
14660
15885
|
return Promise.resolve(fallback);
|
|
14661
|
-
return new Promise((
|
|
15886
|
+
return new Promise((resolve9) => {
|
|
14662
15887
|
try {
|
|
14663
15888
|
execFile(process.execPath, [binary, ...args2], {
|
|
14664
15889
|
cwd: repoRoot,
|
|
@@ -14668,10 +15893,10 @@ function runRunnerChild(binary, args2, credential, repoRoot, readLine, fallback)
|
|
|
14668
15893
|
windowsHide: true
|
|
14669
15894
|
}, (err, stdout) => {
|
|
14670
15895
|
const stopped = err !== null && err.killed === true;
|
|
14671
|
-
|
|
15896
|
+
resolve9(stopped || typeof stdout !== "string" ? fallback : readLine(stdout));
|
|
14672
15897
|
});
|
|
14673
15898
|
} catch {
|
|
14674
|
-
|
|
15899
|
+
resolve9(fallback);
|
|
14675
15900
|
}
|
|
14676
15901
|
});
|
|
14677
15902
|
}
|
|
@@ -14719,10 +15944,10 @@ async function openPhaseReview(credential, phase, request, observe, repoRoot, se
|
|
|
14719
15944
|
}
|
|
14720
15945
|
};
|
|
14721
15946
|
const parsed = openReviewRequestSchema.safeParse(body);
|
|
14722
|
-
const
|
|
14723
|
-
if (!parsed.success || !
|
|
15947
|
+
const named2 = phaseNameSchema.safeParse(phase);
|
|
15948
|
+
if (!parsed.success || !named2.success) {
|
|
14724
15949
|
const issues = [
|
|
14725
|
-
...
|
|
15950
|
+
...named2.success ? [] : named2.error.issues.map((i) => `the phase name: ${i.message}`),
|
|
14726
15951
|
...parsed.success ? [] : parsed.error.issues.map((i) => `${describeIssuePath(i.path)}: ${i.message}`)
|
|
14727
15952
|
];
|
|
14728
15953
|
throw new CloseRequestInvalid(`that close is not one this platform accepts, so nothing was sent: ${issues.join("; ")}.`);
|
|
@@ -15014,6 +16239,101 @@ function wentAheadCloseLines(calls) {
|
|
|
15014
16239
|
function callsListedLine(n) {
|
|
15015
16240
|
return `${String(n)} calls made without asking are listed in the review.`;
|
|
15016
16241
|
}
|
|
16242
|
+
var PHASE_MODELS_FILENAME = "phase-models.json";
|
|
16243
|
+
var PACING_FOLDER = "pacing";
|
|
16244
|
+
function pacingFile(engagementId, name, home) {
|
|
16245
|
+
try {
|
|
16246
|
+
return join19(engagementStateDir(engagementId, home), PACING_FOLDER, name);
|
|
16247
|
+
} catch {
|
|
16248
|
+
return null;
|
|
16249
|
+
}
|
|
16250
|
+
}
|
|
16251
|
+
function readPhaseLimitFile(engagementId, phase, home) {
|
|
16252
|
+
const path = pacingFile(engagementId, PHASE_LIMIT_FILENAME, home);
|
|
16253
|
+
if (path === null)
|
|
16254
|
+
return null;
|
|
16255
|
+
let text;
|
|
16256
|
+
try {
|
|
16257
|
+
text = readFileSync16(path, "utf-8");
|
|
16258
|
+
} catch (err) {
|
|
16259
|
+
return err.code === "ENOENT" ? "missing" : null;
|
|
16260
|
+
}
|
|
16261
|
+
try {
|
|
16262
|
+
return phaseLimitFileOf(JSON.parse(text), phase);
|
|
16263
|
+
} catch {
|
|
16264
|
+
return null;
|
|
16265
|
+
}
|
|
16266
|
+
}
|
|
16267
|
+
function readPhaseExpected(engagementId, phase, home) {
|
|
16268
|
+
const file = readPhaseLimitFile(engagementId, phase, home);
|
|
16269
|
+
if (file === null || file === "missing")
|
|
16270
|
+
return null;
|
|
16271
|
+
return { tokens: file.expected, minutes: file.expectedMinutes };
|
|
16272
|
+
}
|
|
16273
|
+
function phaseLimitLines(engagementId, phase, home) {
|
|
16274
|
+
const file = readPhaseLimitFile(engagementId, phase, home);
|
|
16275
|
+
if (file === null)
|
|
16276
|
+
return [];
|
|
16277
|
+
if (file === "missing")
|
|
16278
|
+
return [LIMIT_C1_NONE];
|
|
16279
|
+
const lines = [limitCloseLineOf(file)];
|
|
16280
|
+
for (const entry of file.history) {
|
|
16281
|
+
const line = limitHistoryLineOf(entry);
|
|
16282
|
+
if (line !== null)
|
|
16283
|
+
lines.push(line);
|
|
16284
|
+
}
|
|
16285
|
+
return lines;
|
|
16286
|
+
}
|
|
16287
|
+
function relayedChoiceLines(engagementId, phase, home) {
|
|
16288
|
+
const path = pacingFile(engagementId, RELAYED_CHOICES_FILENAME, home);
|
|
16289
|
+
if (path === null)
|
|
16290
|
+
return [];
|
|
16291
|
+
try {
|
|
16292
|
+
const parsed = relayedChoicesFileSchema.safeParse(JSON.parse(readFileSync16(path, "utf-8")));
|
|
16293
|
+
if (!parsed.success || parsed.data.phase !== phase)
|
|
16294
|
+
return [];
|
|
16295
|
+
return parsed.data.entries.map((entry) => relayedChoiceLineOf(entry));
|
|
16296
|
+
} catch {
|
|
16297
|
+
return [];
|
|
16298
|
+
}
|
|
16299
|
+
}
|
|
16300
|
+
function readPhaseModels(engagementId, phase, openedAt, home) {
|
|
16301
|
+
const path = pacingFile(engagementId, PHASE_MODELS_FILENAME, home);
|
|
16302
|
+
if (path === null)
|
|
16303
|
+
return null;
|
|
16304
|
+
try {
|
|
16305
|
+
const parsed = phaseModelsFileSchema.safeParse(JSON.parse(readFileSync16(path, "utf-8")));
|
|
16306
|
+
if (!parsed.success || parsed.data.phase !== phase)
|
|
16307
|
+
return null;
|
|
16308
|
+
if (openedAt !== null && parsed.data.openedAt !== openedAt)
|
|
16309
|
+
return null;
|
|
16310
|
+
return parsed.data.tasks;
|
|
16311
|
+
} catch {
|
|
16312
|
+
return null;
|
|
16313
|
+
}
|
|
16314
|
+
}
|
|
16315
|
+
function usageTokensOf(usage2) {
|
|
16316
|
+
return usage2.inputTokens + usage2.outputTokens + usage2.cacheWriteTokens + usage2.cacheReadTokens;
|
|
16317
|
+
}
|
|
16318
|
+
function tierFigureLines(work, expected, summary, models) {
|
|
16319
|
+
const lines = [];
|
|
16320
|
+
const figures = work === null ? null : tierFiguresOf(work);
|
|
16321
|
+
if (figures !== null)
|
|
16322
|
+
lines.push(tierCountsLineOf(figures));
|
|
16323
|
+
if (expected === null)
|
|
16324
|
+
lines.push(NO_EXPECTED_LINE);
|
|
16325
|
+
else if (summary !== null) {
|
|
16326
|
+
lines.push(expectedActualLineOf(expected, { seconds: summary.elapsedSeconds, tokens: usageTokensOf(summary.usage) }));
|
|
16327
|
+
}
|
|
16328
|
+
if (figures !== null && models !== null) {
|
|
16329
|
+
for (const task of figures.hadCheaperAttempt) {
|
|
16330
|
+
const seen = Object.keys(models[task] ?? {});
|
|
16331
|
+
if (seen.length > 0)
|
|
16332
|
+
lines.push(seenModelsLineOf(task, seen));
|
|
16333
|
+
}
|
|
16334
|
+
}
|
|
16335
|
+
return lines;
|
|
16336
|
+
}
|
|
15017
16337
|
|
|
15018
16338
|
// dist/open-phase.js
|
|
15019
16339
|
function readMethodUpdate(value) {
|
|
@@ -15471,9 +16791,9 @@ async function runDraw(repoRoot, picture, deps) {
|
|
|
15471
16791
|
const checked = readAndCheckPlan(repoRoot, phase, description, "draw");
|
|
15472
16792
|
if (!checked.ok)
|
|
15473
16793
|
return checked.line;
|
|
15474
|
-
const
|
|
15475
|
-
if (
|
|
15476
|
-
return
|
|
16794
|
+
const refusal3 = localRefusal(repoRoot, base);
|
|
16795
|
+
if (refusal3 !== null)
|
|
16796
|
+
return refusal3;
|
|
15477
16797
|
checkedPlan = checked.plan;
|
|
15478
16798
|
return null;
|
|
15479
16799
|
};
|
|
@@ -15575,7 +16895,7 @@ async function writeDrawOutcome(repoRoot, deps, outcome, target, warnings, canno
|
|
|
15575
16895
|
|
|
15576
16896
|
// dist/check-tasks.js
|
|
15577
16897
|
import { readFileSync as readFileSync18 } from "node:fs";
|
|
15578
|
-
import { join as join21 } from "node:path";
|
|
16898
|
+
import { join as join21, resolve as resolve6 } from "node:path";
|
|
15579
16899
|
var CHECK_TASKS_USAGE = `${HALFCYCLE_COMMAND} check-tasks <phase> [--with-task-list] [--repo <root>]`;
|
|
15580
16900
|
function parseCheckTasksArgs(argv) {
|
|
15581
16901
|
const positionals = [];
|
|
@@ -15605,13 +16925,17 @@ function parseCheckTasksArgs(argv) {
|
|
|
15605
16925
|
return { ok: false };
|
|
15606
16926
|
return { ok: true, phase: positionals[0], withTaskList, repoRoot };
|
|
15607
16927
|
}
|
|
16928
|
+
var MISSING_PLAN_LINE = `there is no ${TASK_PLAN_FILE} in this project yet. Run the task-graph step to write it, then run this again.`;
|
|
16929
|
+
function planCrew() {
|
|
16930
|
+
return CREW_ROSTER.map((member) => ({ callsign: member.callsign, tier: member.tier }));
|
|
16931
|
+
}
|
|
15608
16932
|
function issuePlace(rel, path) {
|
|
15609
16933
|
return path.length === 0 ? rel : `${rel}.${path.map(String).join(".")}`;
|
|
15610
16934
|
}
|
|
15611
16935
|
function readJsonFile(repoRoot, rel, parse) {
|
|
15612
16936
|
let text;
|
|
15613
16937
|
try {
|
|
15614
|
-
text = readFileSync18(
|
|
16938
|
+
text = readFileSync18(resolve6(repoRoot, rel), "utf-8");
|
|
15615
16939
|
} catch (err) {
|
|
15616
16940
|
const code = err.code;
|
|
15617
16941
|
if (code === "ENOENT" || code === "ENOTDIR")
|
|
@@ -15661,7 +16985,77 @@ function readSystemParts(repoRoot) {
|
|
|
15661
16985
|
` + problemLines(file.value, checked.problems)
|
|
15662
16986
|
};
|
|
15663
16987
|
}
|
|
15664
|
-
return { kind: "ok", partIds: checked.description.parts.map((part) => part.id) };
|
|
16988
|
+
return { kind: "ok", partIds: checked.description.parts.map((part) => part.id), description: checked.description };
|
|
16989
|
+
}
|
|
16990
|
+
function readPhasePlanParts(repoRoot, phase, description) {
|
|
16991
|
+
if (!isValidPhaseIdentity(phase))
|
|
16992
|
+
return void 0;
|
|
16993
|
+
const file = planFileRel(phase);
|
|
16994
|
+
let text;
|
|
16995
|
+
try {
|
|
16996
|
+
text = readFileSync18(join21(repoRoot, file), "utf-8");
|
|
16997
|
+
} catch (err) {
|
|
16998
|
+
const code = err.code;
|
|
16999
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
17000
|
+
return void 0;
|
|
17001
|
+
return { file, unusable: { kind: "unreadable" } };
|
|
17002
|
+
}
|
|
17003
|
+
let raw;
|
|
17004
|
+
try {
|
|
17005
|
+
raw = JSON.parse(text);
|
|
17006
|
+
} catch (err) {
|
|
17007
|
+
return { file, unusable: { kind: "not-json", error: err instanceof Error ? err.message : String(err) } };
|
|
17008
|
+
}
|
|
17009
|
+
const named2 = typeof raw === "object" && raw !== null ? raw["phase"] : void 0;
|
|
17010
|
+
if (isValidPhaseIdentity(named2) && renderPhaseSegment(named2) !== renderPhaseSegment(phase)) {
|
|
17011
|
+
return { file, unusable: { kind: "other-phase", phase: String(named2) } };
|
|
17012
|
+
}
|
|
17013
|
+
const checked = checkPhasePlan(raw, description);
|
|
17014
|
+
if (!checked.ok) {
|
|
17015
|
+
return {
|
|
17016
|
+
file,
|
|
17017
|
+
unusable: { kind: "problems", problems: checked.problems.map((p) => ({ path: p.path, message: p.message })) }
|
|
17018
|
+
};
|
|
17019
|
+
}
|
|
17020
|
+
return { file, partIds: checked.plan.changes.map((change) => change.part) };
|
|
17021
|
+
}
|
|
17022
|
+
var SPEC_FOLDER = LAYER_ARTEFACT_PATHS.l4[0];
|
|
17023
|
+
function readPlanSpecFiles(repoRoot, plan) {
|
|
17024
|
+
const entries = [];
|
|
17025
|
+
for (const slug of planSpecNamesToRead(plan)) {
|
|
17026
|
+
const rel = `${SPEC_FOLDER}/${slug}.md`;
|
|
17027
|
+
let text;
|
|
17028
|
+
try {
|
|
17029
|
+
text = readFileSync18(join21(repoRoot, rel), "utf-8");
|
|
17030
|
+
} catch (err) {
|
|
17031
|
+
const code = err.code;
|
|
17032
|
+
if (code === "ENOENT" || code === "ENOTDIR") {
|
|
17033
|
+
entries.push({ slug, criteria: null, boundary: null });
|
|
17034
|
+
continue;
|
|
17035
|
+
}
|
|
17036
|
+
return { kind: "bad", line: `${rel} could not be read. Check that it is a file you can open, then run this again.` };
|
|
17037
|
+
}
|
|
17038
|
+
entries.push({ slug, criteria: readSpecCriteria(text), boundary: readSpecBoundary(text) });
|
|
17039
|
+
}
|
|
17040
|
+
return { kind: "ok", specFiles: { folder: SPEC_FOLDER, entries } };
|
|
17041
|
+
}
|
|
17042
|
+
function readCriteriaRecordFile(repoRoot, rel = CRITERIA_RECORD_FILE) {
|
|
17043
|
+
return readJsonFile(repoRoot, rel, (raw) => workCriteriaRecordSchema.safeParse(raw));
|
|
17044
|
+
}
|
|
17045
|
+
function readCriteriaRecord(repoRoot, plan) {
|
|
17046
|
+
const read = readCriteriaRecordFile(repoRoot);
|
|
17047
|
+
if (read.kind === "missing")
|
|
17048
|
+
return { kind: "ok", record: void 0 };
|
|
17049
|
+
if (read.kind === "bad")
|
|
17050
|
+
return { kind: "bad", line: read.line };
|
|
17051
|
+
const record2 = read.value;
|
|
17052
|
+
if (criteriaRecordMismatch(record2, plan) !== null)
|
|
17053
|
+
return { kind: "ok", record: void 0 };
|
|
17054
|
+
const fixTasks = [];
|
|
17055
|
+
for (const row of record2.rows)
|
|
17056
|
+
if (row.status === "fix")
|
|
17057
|
+
fixTasks.push(row.task);
|
|
17058
|
+
return { kind: "ok", record: { check: record2.check, fixTasks } };
|
|
15665
17059
|
}
|
|
15666
17060
|
function runCheckTasks(repoRoot, phase, withTaskList) {
|
|
15667
17061
|
const p = "halfcycle check-tasks:";
|
|
@@ -15669,7 +17063,7 @@ function runCheckTasks(repoRoot, phase, withTaskList) {
|
|
|
15669
17063
|
if (planRead.kind === "missing") {
|
|
15670
17064
|
return {
|
|
15671
17065
|
stdout: "",
|
|
15672
|
-
stderr: `${p}
|
|
17066
|
+
stderr: `${p} ${MISSING_PLAN_LINE}
|
|
15673
17067
|
`,
|
|
15674
17068
|
exitCode: 2
|
|
15675
17069
|
};
|
|
@@ -15692,16 +17086,284 @@ function runCheckTasks(repoRoot, phase, withTaskList) {
|
|
|
15692
17086
|
if (system.kind === "bad") {
|
|
15693
17087
|
return { stdout: "", stderr: `${p} ${system.line}`, exitCode: 2 };
|
|
15694
17088
|
}
|
|
17089
|
+
const specs = readPlanSpecFiles(repoRoot, plan);
|
|
17090
|
+
if (specs.kind === "bad") {
|
|
17091
|
+
return { stdout: "", stderr: `${p} ${specs.line}
|
|
17092
|
+
`, exitCode: 2 };
|
|
17093
|
+
}
|
|
17094
|
+
const criteria = readCriteriaRecord(repoRoot, plan);
|
|
17095
|
+
if (criteria.kind === "bad") {
|
|
17096
|
+
return { stdout: "", stderr: `${p} ${criteria.line}
|
|
17097
|
+
`, exitCode: 2 };
|
|
17098
|
+
}
|
|
17099
|
+
const phasePlan = system.kind === "ok" ? readPhasePlanParts(repoRoot, phase, system.description) : void 0;
|
|
15695
17100
|
const result = checkTaskPlan(plan, {
|
|
15696
17101
|
phase,
|
|
15697
17102
|
taskList,
|
|
15698
|
-
|
|
17103
|
+
specFiles: specs.specFiles,
|
|
17104
|
+
...criteria.record !== void 0 ? { criteriaRecord: criteria.record } : {},
|
|
17105
|
+
...system.kind === "ok" ? { system: { file: SYSTEM_DESCRIPTION_REL, partIds: system.partIds } } : {},
|
|
17106
|
+
...phasePlan !== void 0 ? { phasePlan } : {},
|
|
17107
|
+
crew: planCrew()
|
|
15699
17108
|
});
|
|
15700
17109
|
const stdout = result.lines.map((line) => `${line.text}
|
|
15701
17110
|
`).join("");
|
|
15702
17111
|
return { stdout, stderr: "", exitCode: result.refused ? 1 : 0 };
|
|
15703
17112
|
}
|
|
15704
17113
|
|
|
17114
|
+
// dist/check-criteria.js
|
|
17115
|
+
var CHECK_CRITERIA_USAGE = `${HALFCYCLE_COMMAND} check-criteria [<record file>] [--repo <root>]`;
|
|
17116
|
+
function parseCheckCriteriaArgs(argv) {
|
|
17117
|
+
const positionals = [];
|
|
17118
|
+
let repoRoot;
|
|
17119
|
+
for (let i = 0; i < argv.length; i++) {
|
|
17120
|
+
const arg = argv[i];
|
|
17121
|
+
if (arg === "--repo") {
|
|
17122
|
+
const value = argv[i + 1];
|
|
17123
|
+
if (repoRoot !== void 0 || value === void 0 || value === "" || value.startsWith("-"))
|
|
17124
|
+
return { ok: false };
|
|
17125
|
+
repoRoot = value;
|
|
17126
|
+
i++;
|
|
17127
|
+
continue;
|
|
17128
|
+
}
|
|
17129
|
+
if (arg.startsWith("-") || arg === "")
|
|
17130
|
+
return { ok: false };
|
|
17131
|
+
positionals.push(arg);
|
|
17132
|
+
}
|
|
17133
|
+
if (positionals.length > 1)
|
|
17134
|
+
return { ok: false };
|
|
17135
|
+
return { ok: true, file: positionals[0], repoRoot };
|
|
17136
|
+
}
|
|
17137
|
+
function missingRecordLine(file) {
|
|
17138
|
+
return `there is no ${file} in this project yet. Check the finished phase against each acceptance criterion of its features, write what you found there, then run this again.`;
|
|
17139
|
+
}
|
|
17140
|
+
function runCheckCriteria(repoRoot, file) {
|
|
17141
|
+
const p = "halfcycle check-criteria:";
|
|
17142
|
+
const fail = (line) => ({ stdout: "", stderr: `${p} ${line}
|
|
17143
|
+
`, exitCode: 2 });
|
|
17144
|
+
const planRead = readJsonFile(repoRoot, TASK_PLAN_FILE, (raw) => taskPlanSchema.safeParse(raw));
|
|
17145
|
+
if (planRead.kind === "missing")
|
|
17146
|
+
return fail(MISSING_PLAN_LINE);
|
|
17147
|
+
if (planRead.kind === "bad")
|
|
17148
|
+
return fail(planRead.line);
|
|
17149
|
+
const plan = planRead.value;
|
|
17150
|
+
const recordFile = file ?? CRITERIA_RECORD_FILE;
|
|
17151
|
+
const recordRead = readCriteriaRecordFile(repoRoot, recordFile);
|
|
17152
|
+
if (recordRead.kind === "missing")
|
|
17153
|
+
return fail(missingRecordLine(recordFile));
|
|
17154
|
+
if (recordRead.kind === "bad")
|
|
17155
|
+
return fail(recordRead.line);
|
|
17156
|
+
const specs = readPlanSpecFiles(repoRoot, plan);
|
|
17157
|
+
if (specs.kind === "bad")
|
|
17158
|
+
return fail(specs.line);
|
|
17159
|
+
const result = checkCriteriaRecord(recordRead.value, plan, { file: recordFile, specFiles: specs.specFiles });
|
|
17160
|
+
const stdout = result.lines.map((line) => `${line.text}
|
|
17161
|
+
`).join("");
|
|
17162
|
+
return { stdout, stderr: "", exitCode: result.refused ? 1 : 0 };
|
|
17163
|
+
}
|
|
17164
|
+
|
|
17165
|
+
// dist/check-report.js
|
|
17166
|
+
import { readFileSync as readFileSync19 } from "node:fs";
|
|
17167
|
+
var CHECK_REPORT_USAGE = `${HALFCYCLE_COMMAND} check-report <file | -> --head <sha>`;
|
|
17168
|
+
function parseCheckReportArgs(argv) {
|
|
17169
|
+
const positionals = [];
|
|
17170
|
+
let head;
|
|
17171
|
+
for (let i = 0; i < argv.length; i++) {
|
|
17172
|
+
const arg = argv[i];
|
|
17173
|
+
if (arg === "--head") {
|
|
17174
|
+
const value = argv[i + 1];
|
|
17175
|
+
if (head !== void 0 || value === void 0 || value.trim() === "" || value.startsWith("-"))
|
|
17176
|
+
return { ok: false };
|
|
17177
|
+
head = value.trim();
|
|
17178
|
+
i++;
|
|
17179
|
+
continue;
|
|
17180
|
+
}
|
|
17181
|
+
if (arg !== "-" && arg.startsWith("-"))
|
|
17182
|
+
return { ok: false };
|
|
17183
|
+
positionals.push(arg);
|
|
17184
|
+
}
|
|
17185
|
+
if (positionals.length !== 1 || head === void 0)
|
|
17186
|
+
return { ok: false };
|
|
17187
|
+
return { ok: true, source: positionals[0], head };
|
|
17188
|
+
}
|
|
17189
|
+
var SHORT_COMMIT = /^[0-9a-fA-F]{1,39}$/;
|
|
17190
|
+
function runCheckReport(source, head, deps = {}) {
|
|
17191
|
+
const p = "halfcycle check-report:";
|
|
17192
|
+
if (SHORT_COMMIT.test(head)) {
|
|
17193
|
+
return {
|
|
17194
|
+
stdout: "",
|
|
17195
|
+
stderr: `${p} --head ${head} is a short commit id. Give the full 40-character commit id, then run this again.
|
|
17196
|
+
`,
|
|
17197
|
+
exitCode: 2
|
|
17198
|
+
};
|
|
17199
|
+
}
|
|
17200
|
+
let body;
|
|
17201
|
+
try {
|
|
17202
|
+
body = source === "-" ? (deps.readStdin ?? (() => readFileSync19(0, "utf-8")))() : readFileSync19(source, "utf-8");
|
|
17203
|
+
} catch (err) {
|
|
17204
|
+
const code = err.code;
|
|
17205
|
+
const what = source === "-" ? "The report could not be read from standard input" : `${source} could not be read`;
|
|
17206
|
+
const line = code === "ENOENT" && source !== "-" ? `${p} there is no file at ${source}. Save the report to a file, then run this again.` : `${p} ${what}. Check that it is a file you can open, then run this again.`;
|
|
17207
|
+
return { stdout: "", stderr: `${line}
|
|
17208
|
+
`, exitCode: 2 };
|
|
17209
|
+
}
|
|
17210
|
+
const result = checkWorkerReport(body, { head, statuses: WORKER_REPORT_STATUSES });
|
|
17211
|
+
if (result.problems.length === 0) {
|
|
17212
|
+
return { stdout: `${WORKER_REPORT_LINES.clear()}
|
|
17213
|
+
`, stderr: "", exitCode: 0 };
|
|
17214
|
+
}
|
|
17215
|
+
return { stdout: result.problems.map((problem) => `${problem.line}
|
|
17216
|
+
`).join(""), stderr: "", exitCode: 1 };
|
|
17217
|
+
}
|
|
17218
|
+
|
|
17219
|
+
// dist/ready.js
|
|
17220
|
+
var READY_USAGE = `${HALFCYCLE_COMMAND} ready <phase> [--cap <n>] [--repo <root>]`;
|
|
17221
|
+
var STATES_TEXT = buildTaskStateSchema.options.join(", ");
|
|
17222
|
+
var READY_LINES = {
|
|
17223
|
+
start: (id, title) => `Ready to start: ${named(id, title)}`,
|
|
17224
|
+
pastCap: (id, title) => `Ready, but past the number allowed now: ${named(id, title)}`,
|
|
17225
|
+
person: (id, title) => `Waiting on a person: ${named(id, title)}`,
|
|
17226
|
+
noCap: () => "No number was given, so every task that can start is listed.",
|
|
17227
|
+
nothing: () => "Nothing can start now.",
|
|
17228
|
+
noTaskList: () => `There is no task list at ${TASK_LIST_FILE}, so which tasks are done is not known. Write the task list for this phase, then run this again.`,
|
|
17229
|
+
otherPhase: (phase) => `The task list in ${TASK_LIST_FILE} is not for "${phase}", so which tasks are done is not known. Write the task list for this phase, then run this again.`,
|
|
17230
|
+
noState: (id) => `"${id}" in ${TASK_LIST_FILE} does not give its state as one of: ${STATES_TEXT}.`
|
|
17231
|
+
};
|
|
17232
|
+
function named(id, title) {
|
|
17233
|
+
return title === void 0 ? id : `${id}, ${title}`;
|
|
17234
|
+
}
|
|
17235
|
+
function parseReadyArgs(argv) {
|
|
17236
|
+
const positionals = [];
|
|
17237
|
+
let repoRoot;
|
|
17238
|
+
let cap;
|
|
17239
|
+
for (let i = 0; i < argv.length; i++) {
|
|
17240
|
+
const arg = argv[i];
|
|
17241
|
+
if (arg === "--repo") {
|
|
17242
|
+
const value = argv[i + 1];
|
|
17243
|
+
if (repoRoot !== void 0 || value === void 0 || value === "" || value.startsWith("-"))
|
|
17244
|
+
return { ok: false };
|
|
17245
|
+
repoRoot = value;
|
|
17246
|
+
i++;
|
|
17247
|
+
continue;
|
|
17248
|
+
}
|
|
17249
|
+
if (arg === "--cap") {
|
|
17250
|
+
const value = argv[i + 1];
|
|
17251
|
+
if (cap !== void 0 || value === void 0 || !/^[0-9]+$/.test(value))
|
|
17252
|
+
return { ok: false };
|
|
17253
|
+
const n = Number(value);
|
|
17254
|
+
if (!Number.isSafeInteger(n))
|
|
17255
|
+
return { ok: false };
|
|
17256
|
+
cap = n;
|
|
17257
|
+
i++;
|
|
17258
|
+
continue;
|
|
17259
|
+
}
|
|
17260
|
+
if (arg.startsWith("-"))
|
|
17261
|
+
return { ok: false };
|
|
17262
|
+
positionals.push(arg);
|
|
17263
|
+
}
|
|
17264
|
+
if (positionals.length !== 1)
|
|
17265
|
+
return { ok: false };
|
|
17266
|
+
return { ok: true, phase: positionals[0], cap, repoRoot };
|
|
17267
|
+
}
|
|
17268
|
+
var PROGRESS = {
|
|
17269
|
+
pending: "pending",
|
|
17270
|
+
completed: "completed",
|
|
17271
|
+
"in-flight": "other",
|
|
17272
|
+
failed: "other",
|
|
17273
|
+
blocked: "other"
|
|
17274
|
+
};
|
|
17275
|
+
function readyRowsFromFiles(plan, taskList) {
|
|
17276
|
+
const listed = /* @__PURE__ */ new Map();
|
|
17277
|
+
for (const entry of taskList.tasks) {
|
|
17278
|
+
if (!listed.has(entry.id))
|
|
17279
|
+
listed.set(entry.id, entry);
|
|
17280
|
+
}
|
|
17281
|
+
const rows = [];
|
|
17282
|
+
const lines = [];
|
|
17283
|
+
plan.tasks.forEach((task, place2) => {
|
|
17284
|
+
const entry = listed.get(task.id);
|
|
17285
|
+
const state = buildTaskStateSchema.safeParse(entry?.state);
|
|
17286
|
+
if (entry === void 0 || !state.success) {
|
|
17287
|
+
lines.push(READY_LINES.noState(task.id));
|
|
17288
|
+
return;
|
|
17289
|
+
}
|
|
17290
|
+
const title = typeof entry.title === "string" && entry.title !== "" ? entry.title : void 0;
|
|
17291
|
+
const minutes = typeof entry.budgetMinutes === "number" ? entry.budgetMinutes : void 0;
|
|
17292
|
+
rows.push({
|
|
17293
|
+
id: task.id,
|
|
17294
|
+
dependsOn: task.dependsOn,
|
|
17295
|
+
progress: PROGRESS[state.data],
|
|
17296
|
+
person: task.heldBy === "person" || entry.agent === HUMAN_AGENT,
|
|
17297
|
+
...minutes === void 0 ? {} : { minutes },
|
|
17298
|
+
tieRank: place2,
|
|
17299
|
+
title
|
|
17300
|
+
});
|
|
17301
|
+
});
|
|
17302
|
+
return lines.length > 0 ? { ok: false, lines } : { ok: true, rows };
|
|
17303
|
+
}
|
|
17304
|
+
function failed2(stderr) {
|
|
17305
|
+
return { stdout: "", stderr, exitCode: 2 };
|
|
17306
|
+
}
|
|
17307
|
+
function runReady(repoRoot, phase, cap) {
|
|
17308
|
+
const p = "halfcycle ready:";
|
|
17309
|
+
const planRead = readJsonFile(repoRoot, TASK_PLAN_FILE, (raw) => taskPlanSchema.safeParse(raw));
|
|
17310
|
+
if (planRead.kind === "missing")
|
|
17311
|
+
return failed2(`${p} ${MISSING_PLAN_LINE}
|
|
17312
|
+
`);
|
|
17313
|
+
if (planRead.kind === "bad")
|
|
17314
|
+
return failed2(`${p} ${planRead.line}
|
|
17315
|
+
`);
|
|
17316
|
+
const plan = planRead.value;
|
|
17317
|
+
const listRead = readJsonFile(repoRoot, TASK_LIST_FILE, (raw) => taskListFileSchema.safeParse(raw));
|
|
17318
|
+
if (listRead.kind === "bad")
|
|
17319
|
+
return failed2(`${p} ${listRead.line}
|
|
17320
|
+
`);
|
|
17321
|
+
const list2 = listRead.kind === "ok" ? listRead.value : null;
|
|
17322
|
+
const specs = readPlanSpecFiles(repoRoot, plan);
|
|
17323
|
+
if (specs.kind === "bad")
|
|
17324
|
+
return failed2(`${p} ${specs.line}
|
|
17325
|
+
`);
|
|
17326
|
+
const criteria = readCriteriaRecord(repoRoot, plan);
|
|
17327
|
+
if (criteria.kind === "bad")
|
|
17328
|
+
return failed2(`${p} ${criteria.line}
|
|
17329
|
+
`);
|
|
17330
|
+
const check = checkTaskPlan(plan, {
|
|
17331
|
+
phase,
|
|
17332
|
+
taskList: { file: list2 },
|
|
17333
|
+
specFiles: specs.specFiles,
|
|
17334
|
+
...criteria.record !== void 0 ? { criteriaRecord: criteria.record } : {},
|
|
17335
|
+
crew: planCrew()
|
|
17336
|
+
});
|
|
17337
|
+
if (check.refused) {
|
|
17338
|
+
return { stdout: check.lines.map((line) => `${line.text}
|
|
17339
|
+
`).join(""), stderr: "", exitCode: 1 };
|
|
17340
|
+
}
|
|
17341
|
+
if (list2 === null)
|
|
17342
|
+
return failed2(`${READY_LINES.noTaskList()}
|
|
17343
|
+
`);
|
|
17344
|
+
if (Array.isArray(list2) || list2.phase !== phase)
|
|
17345
|
+
return failed2(`${READY_LINES.otherPhase(phase)}
|
|
17346
|
+
`);
|
|
17347
|
+
const read = readyRowsFromFiles(plan, list2);
|
|
17348
|
+
if (!read.ok)
|
|
17349
|
+
return failed2(read.lines.map((line) => `${line}
|
|
17350
|
+
`).join(""));
|
|
17351
|
+
const titles = new Map(read.rows.map((row) => [row.id, row.title]));
|
|
17352
|
+
const set = readyTasks(read.rows);
|
|
17353
|
+
const out = [];
|
|
17354
|
+
if (cap === void 0)
|
|
17355
|
+
out.push(READY_LINES.noCap());
|
|
17356
|
+
set.ready.forEach((id, i) => {
|
|
17357
|
+
out.push(cap === void 0 || i < cap ? READY_LINES.start(id, titles.get(id)) : READY_LINES.pastCap(id, titles.get(id)));
|
|
17358
|
+
});
|
|
17359
|
+
for (const id of set.person)
|
|
17360
|
+
out.push(READY_LINES.person(id, titles.get(id)));
|
|
17361
|
+
if (set.ready.length === 0 && set.person.length === 0)
|
|
17362
|
+
out.push(READY_LINES.nothing());
|
|
17363
|
+
return { stdout: out.map((line) => `${line}
|
|
17364
|
+
`).join(""), stderr: "", exitCode: 0 };
|
|
17365
|
+
}
|
|
17366
|
+
|
|
15705
17367
|
// dist/prove.js
|
|
15706
17368
|
import { spawn as spawn2 } from "node:child_process";
|
|
15707
17369
|
var PROVE_USAGE = `${HALFCYCLE_COMMAND} prove <phase> <task> --label "<label>" --before -- <test command>
|
|
@@ -15726,13 +17388,13 @@ function splitProveInvocation(words) {
|
|
|
15726
17388
|
function runProveChild(binary, credential, repoRoot, words, deps = {}) {
|
|
15727
17389
|
const out = deps.stdout ?? process.stdout;
|
|
15728
17390
|
const err = deps.stderr ?? process.stderr;
|
|
15729
|
-
return new Promise((
|
|
17391
|
+
return new Promise((resolve9) => {
|
|
15730
17392
|
let settled = false;
|
|
15731
17393
|
const finish = (code) => {
|
|
15732
17394
|
if (settled)
|
|
15733
17395
|
return;
|
|
15734
17396
|
settled = true;
|
|
15735
|
-
|
|
17397
|
+
resolve9(code);
|
|
15736
17398
|
};
|
|
15737
17399
|
let child;
|
|
15738
17400
|
try {
|
|
@@ -15754,9 +17416,9 @@ function runProveChild(binary, credential, repoRoot, words, deps = {}) {
|
|
|
15754
17416
|
}
|
|
15755
17417
|
|
|
15756
17418
|
// dist/uninstall.js
|
|
15757
|
-
import { lstatSync as lstatSync3, readFileSync as
|
|
17419
|
+
import { lstatSync as lstatSync3, readFileSync as readFileSync20, readdirSync, rmSync as rmSync6, rmdirSync, writeFileSync as writeFileSync7 } from "node:fs";
|
|
15758
17420
|
import { homedir as homedir5 } from "node:os";
|
|
15759
|
-
import { dirname as
|
|
17421
|
+
import { dirname as dirname6, join as join22, relative as relative6, sep as sep2 } from "node:path";
|
|
15760
17422
|
var REMOVE_CREDENTIAL_FLAG = "--remove-credential";
|
|
15761
17423
|
function parseUninstallArgs(args2) {
|
|
15762
17424
|
let removeCredential = false;
|
|
@@ -15797,7 +17459,7 @@ function readMergedJson(targetRepo, rule) {
|
|
|
15797
17459
|
let text;
|
|
15798
17460
|
let value;
|
|
15799
17461
|
try {
|
|
15800
|
-
text =
|
|
17462
|
+
text = readFileSync20(path, "utf-8");
|
|
15801
17463
|
} catch {
|
|
15802
17464
|
text = "";
|
|
15803
17465
|
}
|
|
@@ -15822,7 +17484,7 @@ function validate(options, rules) {
|
|
|
15822
17484
|
let manifest = null;
|
|
15823
17485
|
if (lexists(manifestPath)) {
|
|
15824
17486
|
try {
|
|
15825
|
-
manifest = parseManifest(
|
|
17487
|
+
manifest = parseManifest(readFileSync20(manifestPath, "utf-8"));
|
|
15826
17488
|
} catch (err) {
|
|
15827
17489
|
const why = err instanceof UnreadableManifestError ? err.message : "it could not be read";
|
|
15828
17490
|
throw new Refusal(`${INSTALL_MANIFEST_REL} cannot be read (${why}), so nothing was removed. Restore it from git, or run "${HALFCYCLE_COMMAND}" once to rewrite it, then run "${HALFCYCLE_COMMAND} uninstall" again.`);
|
|
@@ -15832,7 +17494,7 @@ function validate(options, rules) {
|
|
|
15832
17494
|
let pinReadable = true;
|
|
15833
17495
|
if (lexists(pinPath)) {
|
|
15834
17496
|
try {
|
|
15835
|
-
const pin = JSON.parse(
|
|
17497
|
+
const pin = JSON.parse(readFileSync20(pinPath, "utf-8"));
|
|
15836
17498
|
const id = isPlainObject3(pin) ? pin[PIN_ENGAGEMENT_ID_FIELD] : void 0;
|
|
15837
17499
|
engagementId = typeof id === "string" && id !== "" ? id : void 0;
|
|
15838
17500
|
} catch {
|
|
@@ -15857,7 +17519,7 @@ function validate(options, rules) {
|
|
|
15857
17519
|
}
|
|
15858
17520
|
if (engagementId !== void 0) {
|
|
15859
17521
|
const expectedParent = join22(home ?? homedir5(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME);
|
|
15860
|
-
if (
|
|
17522
|
+
if (dirname6(engagementStateDir(engagementId, home)) !== expectedParent) {
|
|
15861
17523
|
throw new Refusal(`the project id in ${BUNDLE_PIN_REL} does not name a folder under ~/.halfcycle/engagements. Nothing was removed.`);
|
|
15862
17524
|
}
|
|
15863
17525
|
}
|
|
@@ -15888,7 +17550,7 @@ function writeBack(targetRepo, rel, current, next, lists, name) {
|
|
|
15888
17550
|
rmSync6(path);
|
|
15889
17551
|
name(lists.removed, rel, rel);
|
|
15890
17552
|
} else {
|
|
15891
|
-
|
|
17553
|
+
writeFileSync7(path, next, "utf-8");
|
|
15892
17554
|
name(lists.restored, rel, rel);
|
|
15893
17555
|
}
|
|
15894
17556
|
} catch {
|
|
@@ -15899,7 +17561,7 @@ function statusLineClaimedFromTree(targetRepo, writeSet) {
|
|
|
15899
17561
|
try {
|
|
15900
17562
|
if (symlinkOnPath(targetRepo, SETTINGS_REL))
|
|
15901
17563
|
return {};
|
|
15902
|
-
const value = JSON.parse(
|
|
17564
|
+
const value = JSON.parse(readFileSync20(join22(targetRepo, SETTINGS_REL), "utf-8"));
|
|
15903
17565
|
if (!isPlainObject3(value) || !("statusLine" in value))
|
|
15904
17566
|
return {};
|
|
15905
17567
|
const hash = canonicalSha256(value["statusLine"]);
|
|
@@ -15913,7 +17575,7 @@ function noRecord(targetRepo, writeSet) {
|
|
|
15913
17575
|
const claim = (rel, header, lines) => {
|
|
15914
17576
|
let text;
|
|
15915
17577
|
try {
|
|
15916
|
-
text = symlinkOnPath(targetRepo, rel) ? "" :
|
|
17578
|
+
text = symlinkOnPath(targetRepo, rel) ? "" : readFileSync20(join22(targetRepo, rel), "utf-8");
|
|
15917
17579
|
} catch {
|
|
15918
17580
|
text = "";
|
|
15919
17581
|
}
|
|
@@ -16195,7 +17857,7 @@ function removeSelfBuildCrewRoster(targetRepo, lists, name) {
|
|
|
16195
17857
|
const abs = join22(targetRepo, rel);
|
|
16196
17858
|
let bytes;
|
|
16197
17859
|
try {
|
|
16198
|
-
bytes =
|
|
17860
|
+
bytes = readFileSync20(abs);
|
|
16199
17861
|
} catch {
|
|
16200
17862
|
return;
|
|
16201
17863
|
}
|
|
@@ -16222,7 +17884,7 @@ function removeSelfBuildCrewProfile(targetRepo, rel, text, lists, name) {
|
|
|
16222
17884
|
const abs = join22(targetRepo, rel);
|
|
16223
17885
|
let bytes;
|
|
16224
17886
|
try {
|
|
16225
|
-
bytes =
|
|
17887
|
+
bytes = readFileSync20(abs);
|
|
16226
17888
|
} catch {
|
|
16227
17889
|
return;
|
|
16228
17890
|
}
|
|
@@ -16333,17 +17995,17 @@ function uninstall(options) {
|
|
|
16333
17995
|
const manifest = checked.manifest ?? noRecord(targetRepo, writeSet);
|
|
16334
17996
|
const deleteWhenEmpty = (created) => created || checked.manifest === null;
|
|
16335
17997
|
const deleteCodexWhenEmpty = (record2) => record2 === void 0 || deleteWhenEmpty(record2.created);
|
|
16336
|
-
const
|
|
17998
|
+
const named2 = /* @__PURE__ */ new Set();
|
|
16337
17999
|
const name = (list2, text, path) => {
|
|
16338
18000
|
list2.push(text);
|
|
16339
18001
|
if (path !== void 0)
|
|
16340
|
-
|
|
18002
|
+
named2.add(path);
|
|
16341
18003
|
};
|
|
16342
18004
|
const settingsEmptyGoes = deleteWhenEmpty(manifest.settings.created);
|
|
16343
18005
|
applyMergedJson(targetRepo, rules.settings, checked.settings, settingsEmptyGoes, result, name, (value) => {
|
|
16344
18006
|
const { changed, refused: refused3 } = unmergeSettings(value, manifest.settings, writeSet);
|
|
16345
|
-
for (const
|
|
16346
|
-
name(result.leftAlone,
|
|
18007
|
+
for (const note2 of refused3)
|
|
18008
|
+
name(result.leftAlone, note2);
|
|
16347
18009
|
return { changed, adopted: manifest.settings.adopted };
|
|
16348
18010
|
});
|
|
16349
18011
|
const mcpRecord = manifest.mcp ?? BEFORE_UNKNOWN_MCP;
|
|
@@ -16403,17 +18065,17 @@ function uninstall(options) {
|
|
|
16403
18065
|
removeInstalledFile(targetRepo, BUNDLE_PIN_REL, recorded.get(BUNDLE_PIN_REL), writeSet, result, name);
|
|
16404
18066
|
try {
|
|
16405
18067
|
rmSync6(join22(targetRepo, INSTALL_MANIFEST_REL), { force: true });
|
|
16406
|
-
|
|
18068
|
+
named2.add(INSTALL_MANIFEST_REL);
|
|
16407
18069
|
} catch {
|
|
16408
18070
|
name(result.failed, INSTALL_MANIFEST_REL, INSTALL_MANIFEST_REL);
|
|
16409
18071
|
}
|
|
16410
18072
|
removeEmptyDir(targetRepo, ".halfcycle", writeSet, result, name);
|
|
16411
18073
|
} else {
|
|
16412
|
-
|
|
16413
|
-
|
|
18074
|
+
named2.add(BUNDLE_PIN_REL);
|
|
18075
|
+
named2.add(INSTALL_MANIFEST_REL);
|
|
16414
18076
|
}
|
|
16415
18077
|
for (const path of filesUnder(targetRepo, ".halfcycle")) {
|
|
16416
|
-
if (!
|
|
18078
|
+
if (!named2.has(path))
|
|
16417
18079
|
name(result.leftAlone, path, path);
|
|
16418
18080
|
}
|
|
16419
18081
|
const exitCode = result.failed.length > 0 || credentialFailed || credential.kind === "no-project-id" ? 1 : 0;
|
|
@@ -16453,7 +18115,7 @@ function removeCodexConfigBlock(targetRepo, record2, deleteWhenEmpty, lists, nam
|
|
|
16453
18115
|
try {
|
|
16454
18116
|
if (!lstatSync3(path).isFile())
|
|
16455
18117
|
return;
|
|
16456
|
-
text =
|
|
18118
|
+
text = readFileSync20(path, "utf-8");
|
|
16457
18119
|
} catch {
|
|
16458
18120
|
return;
|
|
16459
18121
|
}
|
|
@@ -16502,7 +18164,7 @@ function removeInstalledFile(targetRepo, path, entry, writeSet, lists, name) {
|
|
|
16502
18164
|
if (!writeSet.ownedDirs.has(path.slice(0, path.lastIndexOf("/")))) {
|
|
16503
18165
|
let bytes;
|
|
16504
18166
|
try {
|
|
16505
|
-
bytes =
|
|
18167
|
+
bytes = readFileSync20(abs);
|
|
16506
18168
|
} catch {
|
|
16507
18169
|
name(lists.failed, path, path);
|
|
16508
18170
|
return;
|
|
@@ -16544,7 +18206,7 @@ function trimAppendedFile(targetRepo, rec, deleteWhenEmpty, rules, lists, name)
|
|
|
16544
18206
|
const path = join22(targetRepo, rel);
|
|
16545
18207
|
let before;
|
|
16546
18208
|
try {
|
|
16547
|
-
before =
|
|
18209
|
+
before = readFileSync20(path, "utf-8");
|
|
16548
18210
|
} catch {
|
|
16549
18211
|
return [];
|
|
16550
18212
|
}
|
|
@@ -16669,8 +18331,8 @@ function renderUninstallReport(result, home) {
|
|
|
16669
18331
|
}
|
|
16670
18332
|
|
|
16671
18333
|
// dist/banner-facts.js
|
|
16672
|
-
import { readFileSync as
|
|
16673
|
-
import { basename as basename4, join as join23, resolve as
|
|
18334
|
+
import { readFileSync as readFileSync21 } from "node:fs";
|
|
18335
|
+
import { basename as basename4, join as join23, resolve as resolve7 } from "node:path";
|
|
16674
18336
|
var BRAND_URL = "halfcycle.ai";
|
|
16675
18337
|
var TAGLINE = [
|
|
16676
18338
|
"Fewer cycles.",
|
|
@@ -16683,7 +18345,7 @@ var TAGLINE_NARROW = [
|
|
|
16683
18345
|
];
|
|
16684
18346
|
function bundleVersion() {
|
|
16685
18347
|
try {
|
|
16686
|
-
const pkg = JSON.parse(
|
|
18348
|
+
const pkg = JSON.parse(readFileSync21(join23(BUNDLE_ROOT, "package.json"), "utf-8"));
|
|
16687
18349
|
return typeof pkg.version === "string" ? pkg.version : void 0;
|
|
16688
18350
|
} catch {
|
|
16689
18351
|
return void 0;
|
|
@@ -16769,7 +18431,7 @@ function openingBannerContent(projectDir, env = process.env, probes = REAL_PROBE
|
|
|
16769
18431
|
}
|
|
16770
18432
|
function safeBasename(dir) {
|
|
16771
18433
|
try {
|
|
16772
|
-
const name = basename4(
|
|
18434
|
+
const name = basename4(resolve7(dir)).trim();
|
|
16773
18435
|
return name === "" ? "project" : name;
|
|
16774
18436
|
} catch {
|
|
16775
18437
|
return "project";
|
|
@@ -16778,7 +18440,9 @@ function safeBasename(dir) {
|
|
|
16778
18440
|
|
|
16779
18441
|
// dist/pace.js
|
|
16780
18442
|
import { spawn as spawn3 } from "node:child_process";
|
|
16781
|
-
|
|
18443
|
+
import { readFileSync as readFileSync22 } from "node:fs";
|
|
18444
|
+
import { join as join24 } from "node:path";
|
|
18445
|
+
var PACE_USAGE = `${HALFCYCLE_COMMAND} pace [--agent claude-code|codex] [--start <phase> [--agents <n>]] [--repo <root>]`;
|
|
16782
18446
|
function parsePaceArgs(argv) {
|
|
16783
18447
|
let repoRoot;
|
|
16784
18448
|
let phase;
|
|
@@ -16786,6 +18450,7 @@ function parsePaceArgs(argv) {
|
|
|
16786
18450
|
let sawRepo = false;
|
|
16787
18451
|
let sawStart = false;
|
|
16788
18452
|
let sawAgents = false;
|
|
18453
|
+
let agent = null;
|
|
16789
18454
|
for (let i = 0; i < argv.length; i++) {
|
|
16790
18455
|
const arg = argv[i];
|
|
16791
18456
|
const value = () => {
|
|
@@ -16811,6 +18476,13 @@ function parsePaceArgs(argv) {
|
|
|
16811
18476
|
if (v === null)
|
|
16812
18477
|
return { ok: false };
|
|
16813
18478
|
phase = v;
|
|
18479
|
+
} else if (arg === "--agent") {
|
|
18480
|
+
if (agent !== null)
|
|
18481
|
+
return { ok: false };
|
|
18482
|
+
const v = value();
|
|
18483
|
+
if (v !== "claude-code" && v !== "codex")
|
|
18484
|
+
return { ok: false };
|
|
18485
|
+
agent = v;
|
|
16814
18486
|
} else if (arg === "--agents") {
|
|
16815
18487
|
if (sawAgents)
|
|
16816
18488
|
return { ok: false };
|
|
@@ -16826,21 +18498,21 @@ function parsePaceArgs(argv) {
|
|
|
16826
18498
|
if (agents !== void 0 && phase === void 0)
|
|
16827
18499
|
return { ok: false };
|
|
16828
18500
|
if (phase === void 0)
|
|
16829
|
-
return { ok: true, start: null, repoRoot };
|
|
16830
|
-
return { ok: true, start: { phase, agents: agents ?? null }, repoRoot };
|
|
18501
|
+
return { ok: true, start: null, repoRoot, agent };
|
|
18502
|
+
return { ok: true, start: { phase, agents: agents ?? null }, repoRoot, agent };
|
|
16831
18503
|
}
|
|
16832
18504
|
var PACE_CHILD_DEADLINE_MS = 11 * 60 * 1e3;
|
|
16833
18505
|
function runPaceChild(binary, credential, repoRoot, args2, deps = {}) {
|
|
16834
18506
|
if (binary instanceof Error)
|
|
16835
18507
|
return Promise.resolve({ code: 1, stdout: "" });
|
|
16836
18508
|
const err = deps.stderr ?? process.stderr;
|
|
16837
|
-
return new Promise((
|
|
18509
|
+
return new Promise((resolve9) => {
|
|
16838
18510
|
let settled = false;
|
|
16839
18511
|
const done = (output) => {
|
|
16840
18512
|
if (settled)
|
|
16841
18513
|
return;
|
|
16842
18514
|
settled = true;
|
|
16843
|
-
|
|
18515
|
+
resolve9(output);
|
|
16844
18516
|
};
|
|
16845
18517
|
try {
|
|
16846
18518
|
const child = spawn3(process.execPath, [binary, "pace", ...args2], {
|
|
@@ -16881,7 +18553,17 @@ function notPacedLine(reason) {
|
|
|
16881
18553
|
return reason === "not-configured" ? `${PACE_NOT_CONNECTED_LINE}
|
|
16882
18554
|
` : "halfcycle pace: Halfcycle could not read this computer's usage records just now. Run this again in a minute.\n";
|
|
16883
18555
|
}
|
|
16884
|
-
|
|
18556
|
+
function readPromptScript(repoRoot) {
|
|
18557
|
+
try {
|
|
18558
|
+
return readFileSync22(join24(repoRoot, ".claude", "hooks", "user-prompt-scope.sh"), "utf-8");
|
|
18559
|
+
} catch {
|
|
18560
|
+
return null;
|
|
18561
|
+
}
|
|
18562
|
+
}
|
|
18563
|
+
function promptScriptBehind(script) {
|
|
18564
|
+
return script === null || !script.includes("faster");
|
|
18565
|
+
}
|
|
18566
|
+
async function runPace(repoRoot, start, deps, agent = null) {
|
|
16885
18567
|
if (start !== null && !isValidPhaseIdentity(start.phase)) {
|
|
16886
18568
|
return { stdout: "", stderr: notAPhaseNameLine("pace", start.phase), exitCode: 1 };
|
|
16887
18569
|
}
|
|
@@ -16892,7 +18574,10 @@ async function runPace(repoRoot, start, deps) {
|
|
|
16892
18574
|
return { stdout: "", stderr: `${PACE_NOT_CONNECTED_LINE}
|
|
16893
18575
|
`, exitCode: 1 };
|
|
16894
18576
|
}
|
|
16895
|
-
const args2 =
|
|
18577
|
+
const args2 = [
|
|
18578
|
+
...agent === null ? [] : ["--agent", agent],
|
|
18579
|
+
...start === null ? [] : ["--start", start.phase, ...start.agents !== null ? ["--agents", start.agents] : []]
|
|
18580
|
+
];
|
|
16896
18581
|
const child = await deps.spawnPace(credential, repoRoot, args2);
|
|
16897
18582
|
if (child.code !== 0)
|
|
16898
18583
|
return { stdout: "", stderr: "", exitCode: child.code };
|
|
@@ -16904,26 +18589,178 @@ async function runPace(repoRoot, start, deps) {
|
|
|
16904
18589
|
if (start !== null) {
|
|
16905
18590
|
if (result.suggestion === void 0)
|
|
16906
18591
|
return { stdout: "", stderr: notPacedLine("unreadable"), exitCode: 0 };
|
|
16907
|
-
|
|
18592
|
+
const behind = promptScriptBehind((deps.readPromptScript ?? readPromptScript)(repoRoot));
|
|
18593
|
+
return { stdout: `${paceStartLinesOf(result.suggestion, behind).join("\n")}
|
|
16908
18594
|
`, stderr: "", exitCode: 0 };
|
|
16909
18595
|
}
|
|
16910
18596
|
const lines = [paceAllowedNowOf(result.allowedNow)];
|
|
18597
|
+
if (result.limit === "reached" && result.limitHeld !== void 0) {
|
|
18598
|
+
const held = result.limitHeld;
|
|
18599
|
+
lines.push(limitReachedLineOf(held.used, held.limit, held.working, held.raiseBy));
|
|
18600
|
+
}
|
|
16911
18601
|
if (result.windowKind !== void 0 && result.until !== void 0) {
|
|
16912
18602
|
const until = new Date(result.until);
|
|
18603
|
+
const share = result.keepFreePercent ?? KEEP_FREE_DEFAULT;
|
|
16913
18604
|
if (result.pace === "slowing")
|
|
16914
|
-
lines.push(paceS1Of(result.windowKind, result.allowedNow, until, deps.now()));
|
|
18605
|
+
lines.push(paceS1Of(result.windowKind, result.allowedNow, until, deps.now(), share));
|
|
16915
18606
|
else if (result.pace === "holding")
|
|
16916
|
-
lines.push(paceS2Of(result.windowKind, result.allowedNow, until, deps.now()));
|
|
18607
|
+
lines.push(paceS2Of(result.windowKind, result.allowedNow, until, deps.now(), share));
|
|
16917
18608
|
}
|
|
16918
18609
|
return { stdout: `${lines.join("\n")}
|
|
16919
18610
|
`, stderr: "", exitCode: 0 };
|
|
16920
18611
|
}
|
|
16921
18612
|
|
|
18613
|
+
// dist/route.js
|
|
18614
|
+
import { mkdirSync as mkdirSync10, readFileSync as readFileSync23, renameSync as renameSync4, writeFileSync as writeFileSync8 } from "node:fs";
|
|
18615
|
+
import { dirname as dirname7, join as join25 } from "node:path";
|
|
18616
|
+
var ROUTE_USAGE = `${HALFCYCLE_COMMAND} route <phase> <task> [--agent claude-code|codex] [--repo <root>]`;
|
|
18617
|
+
var ROUTING_FILENAME = "routing.json";
|
|
18618
|
+
var ROUTE_WORK_READ_DEADLINE_MS = 5e3;
|
|
18619
|
+
function parseRouteArgs(argv) {
|
|
18620
|
+
const positionals = [];
|
|
18621
|
+
let agent = null;
|
|
18622
|
+
let repoRoot;
|
|
18623
|
+
let sawRepo = false;
|
|
18624
|
+
for (let i = 0; i < argv.length; i++) {
|
|
18625
|
+
const arg = argv[i];
|
|
18626
|
+
const value = () => {
|
|
18627
|
+
const v = argv[i + 1];
|
|
18628
|
+
if (v === void 0 || v === "" || v.startsWith("-"))
|
|
18629
|
+
return null;
|
|
18630
|
+
i += 1;
|
|
18631
|
+
return v;
|
|
18632
|
+
};
|
|
18633
|
+
if (arg === "--agent") {
|
|
18634
|
+
if (agent !== null)
|
|
18635
|
+
return { ok: false };
|
|
18636
|
+
const v = value();
|
|
18637
|
+
if (v !== "claude-code" && v !== "codex")
|
|
18638
|
+
return { ok: false };
|
|
18639
|
+
agent = v;
|
|
18640
|
+
} else if (arg === "--repo") {
|
|
18641
|
+
if (sawRepo)
|
|
18642
|
+
return { ok: false };
|
|
18643
|
+
sawRepo = true;
|
|
18644
|
+
const v = value();
|
|
18645
|
+
if (v === null)
|
|
18646
|
+
return { ok: false };
|
|
18647
|
+
repoRoot = v;
|
|
18648
|
+
} else if (arg.startsWith("-") || arg === "") {
|
|
18649
|
+
return { ok: false };
|
|
18650
|
+
} else {
|
|
18651
|
+
positionals.push(arg);
|
|
18652
|
+
}
|
|
18653
|
+
}
|
|
18654
|
+
if (positionals.length !== 2)
|
|
18655
|
+
return { ok: false };
|
|
18656
|
+
return { ok: true, phase: positionals[0], task: positionals[1], agent: agent ?? "claude-code", repoRoot };
|
|
18657
|
+
}
|
|
18658
|
+
function routingPath(engagementId, home) {
|
|
18659
|
+
return join25(engagementStateDir(engagementId, home), ROUTING_FILENAME);
|
|
18660
|
+
}
|
|
18661
|
+
function readCheapStarted(path, phase) {
|
|
18662
|
+
try {
|
|
18663
|
+
const parsed = routingRecordSchema.safeParse(JSON.parse(readFileSync23(path, "utf-8")));
|
|
18664
|
+
if (!parsed.success || parsed.data.phase !== phase)
|
|
18665
|
+
return [];
|
|
18666
|
+
return [...parsed.data.cheapStarted];
|
|
18667
|
+
} catch {
|
|
18668
|
+
return [];
|
|
18669
|
+
}
|
|
18670
|
+
}
|
|
18671
|
+
function recordCheapStarted(path, phase, task) {
|
|
18672
|
+
const started = readCheapStarted(path, phase);
|
|
18673
|
+
const record2 = { phase, cheapStarted: started.includes(task) ? started : [...started, task] };
|
|
18674
|
+
mkdirSync10(dirname7(path), { recursive: true, mode: 448 });
|
|
18675
|
+
const tmp = `${path}.${String(process.pid)}.tmp`;
|
|
18676
|
+
writeFileSync8(tmp, `${JSON.stringify(record2)}
|
|
18677
|
+
`, { mode: 384 });
|
|
18678
|
+
renameSync4(tmp, path);
|
|
18679
|
+
}
|
|
18680
|
+
function attemptsOf(read, task) {
|
|
18681
|
+
if (read.kind !== "work")
|
|
18682
|
+
return null;
|
|
18683
|
+
return read.work.tasks.find((t) => t.task === task)?.attempts ?? [];
|
|
18684
|
+
}
|
|
18685
|
+
function lastTierOf(attempts) {
|
|
18686
|
+
return [...attempts].sort((a, b) => b.attempt - a.attempt)[0].builderTier;
|
|
18687
|
+
}
|
|
18688
|
+
async function readWorkInTime(deps, credential, phase) {
|
|
18689
|
+
let timer;
|
|
18690
|
+
const late = new Promise((resolve9) => {
|
|
18691
|
+
timer = setTimeout(() => resolve9({ kind: "unreachable" }), ROUTE_WORK_READ_DEADLINE_MS);
|
|
18692
|
+
timer.unref?.();
|
|
18693
|
+
});
|
|
18694
|
+
try {
|
|
18695
|
+
return await Promise.race([deps.readWork(credential, phase).catch(() => ({ kind: "unreachable" })), late]);
|
|
18696
|
+
} finally {
|
|
18697
|
+
if (timer !== void 0)
|
|
18698
|
+
clearTimeout(timer);
|
|
18699
|
+
}
|
|
18700
|
+
}
|
|
18701
|
+
async function runRoute(repoRoot, phase, taskId, agent, deps) {
|
|
18702
|
+
const p = "halfcycle route:";
|
|
18703
|
+
const answer = (line) => ({ stdout: `${line}
|
|
18704
|
+
`, stderr: "", exitCode: 0 });
|
|
18705
|
+
const planRead = readJsonFile(repoRoot, TASK_PLAN_FILE, (raw) => taskPlanSchema.safeParse(raw));
|
|
18706
|
+
if (planRead.kind === "missing")
|
|
18707
|
+
return { stdout: "", stderr: `${p} ${MISSING_PLAN_LINE}
|
|
18708
|
+
`, exitCode: 2 };
|
|
18709
|
+
if (planRead.kind === "bad")
|
|
18710
|
+
return { stdout: "", stderr: `${p} ${planRead.line}
|
|
18711
|
+
`, exitCode: 2 };
|
|
18712
|
+
const plan = planRead.value;
|
|
18713
|
+
if (plan.phase !== phase) {
|
|
18714
|
+
return { stdout: "", stderr: `${p} ${taskPlanLineText.O6(plan.phase, phase)}
|
|
18715
|
+
`, exitCode: 1 };
|
|
18716
|
+
}
|
|
18717
|
+
const task = plan.tasks.find((t) => t.id === taskId);
|
|
18718
|
+
if (task === void 0)
|
|
18719
|
+
return { stdout: "", stderr: `${p} ${routeNoSuchTaskLineOf(taskId)}
|
|
18720
|
+
`, exitCode: 1 };
|
|
18721
|
+
const holder = task.agent;
|
|
18722
|
+
if (holder === void 0)
|
|
18723
|
+
return { stdout: "", stderr: `${p} ${taskPlanLineText.V4(taskId)}
|
|
18724
|
+
`, exitCode: 1 };
|
|
18725
|
+
const member = CREW_ROSTER.find((m) => m.callsign === holder);
|
|
18726
|
+
if (member === void 0)
|
|
18727
|
+
return answer(routeStartUnknownLineOf(holder, taskId));
|
|
18728
|
+
const startsCheaperWhenFirst = task.kind === "build" && task.cheapFirst === true && member.tier === "standard";
|
|
18729
|
+
if (!startsCheaperWhenFirst)
|
|
18730
|
+
return answer(routeStartLineOf(holder, taskId, member.tier));
|
|
18731
|
+
let credential;
|
|
18732
|
+
try {
|
|
18733
|
+
credential = deps.readCredential(repoRoot);
|
|
18734
|
+
} catch {
|
|
18735
|
+
return answer(routeStartLineOf(holder, taskId, member.tier));
|
|
18736
|
+
}
|
|
18737
|
+
const attempts = attemptsOf(await readWorkInTime(deps, credential, phase), taskId);
|
|
18738
|
+
if (attempts !== null && attempts.length > 0) {
|
|
18739
|
+
return answer(routeStartLineOf(holder, taskId, tierAboveOf(lastTierOf(attempts))));
|
|
18740
|
+
}
|
|
18741
|
+
if (agent === "codex" && !hasCheaperRole(member))
|
|
18742
|
+
return answer(routeStartLineOf(holder, taskId, member.tier));
|
|
18743
|
+
let path;
|
|
18744
|
+
try {
|
|
18745
|
+
path = routingPath(credential.engagementId, deps.home);
|
|
18746
|
+
} catch {
|
|
18747
|
+
return answer(routeStartLineOf(holder, taskId, member.tier));
|
|
18748
|
+
}
|
|
18749
|
+
if (readCheapStarted(path, phase).includes(taskId))
|
|
18750
|
+
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
|
+
return answer(agent === "codex" ? routeCheaperCodexLineOf(holder, taskId) : routeCheaperClaudeLineOf(holder, taskId, AGENT_PROFILES["claude-code"].tierModels.fast.model));
|
|
18757
|
+
}
|
|
18758
|
+
|
|
16922
18759
|
// dist/restore.js
|
|
16923
18760
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
16924
|
-
import { closeSync as closeSync3, fchmodSync, lstatSync as lstatSync4, mkdirSync as mkdirSync11, openSync as openSync3, readdirSync as readdirSync2, readFileSync as
|
|
18761
|
+
import { closeSync as closeSync3, fchmodSync, lstatSync as lstatSync4, mkdirSync as mkdirSync11, openSync as openSync3, readdirSync as readdirSync2, readFileSync as readFileSync24, readlinkSync, realpathSync as realpathSync6, rmSync as rmSync7, symlinkSync, writeSync as writeSync2 } from "node:fs";
|
|
16925
18762
|
import { homedir as homedir6 } from "node:os";
|
|
16926
|
-
import { join as
|
|
18763
|
+
import { join as join26, resolve as resolve8 } from "node:path";
|
|
16927
18764
|
var RESTORE_USAGE = `${HALFCYCLE_COMMAND} restore [<id>]`;
|
|
16928
18765
|
function parseRestoreArgs(rest) {
|
|
16929
18766
|
if (rest.length > 1)
|
|
@@ -16945,7 +18782,7 @@ function runRestore(cwd, id, options = {}) {
|
|
|
16945
18782
|
if (engagementId === null) {
|
|
16946
18783
|
return refused2(`Halfcycle is not set up in this project, so it has saved nothing here. To set it up, run: ${HALFCYCLE_COMMAND}`);
|
|
16947
18784
|
}
|
|
16948
|
-
const folder =
|
|
18785
|
+
const folder = join26(options.home ?? homedir6(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, SAVED_DIR_NAME);
|
|
16949
18786
|
const refs = savedRefs(root);
|
|
16950
18787
|
if (refs === null)
|
|
16951
18788
|
return refused2("Halfcycle could not read the saved copies in this project, so nothing was put back.");
|
|
@@ -16998,9 +18835,9 @@ function printable(text) {
|
|
|
16998
18835
|
}
|
|
16999
18836
|
function restoreOne(root, folder, refs, rawId) {
|
|
17000
18837
|
const valid = savedIdSchema.safeParse(rawId);
|
|
17001
|
-
const
|
|
18838
|
+
const named2 = valid.success ? `"${rawId}"` : "with that name";
|
|
17002
18839
|
if (!valid.success || !refs.has(rawId)) {
|
|
17003
|
-
return refused2(`No saved copy ${
|
|
18840
|
+
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}`);
|
|
17004
18841
|
}
|
|
17005
18842
|
const id = valid.data;
|
|
17006
18843
|
const saved = readSaved(folder, id);
|
|
@@ -17021,7 +18858,7 @@ function restoreOne(root, folder, refs, rawId) {
|
|
|
17021
18858
|
if (p.keptIn === "commit")
|
|
17022
18859
|
fromCommit.push(p.path);
|
|
17023
18860
|
else
|
|
17024
|
-
restoreCopy(root,
|
|
18861
|
+
restoreCopy(root, join26(folder, id), p.path, out);
|
|
17025
18862
|
}
|
|
17026
18863
|
if (fromCommit.length > 0)
|
|
17027
18864
|
restoreFromCommit(root, ref, fromCommit, out);
|
|
@@ -17049,7 +18886,7 @@ function makeFolders(root, rel) {
|
|
|
17049
18886
|
const segments = rel.split("/");
|
|
17050
18887
|
let at = root;
|
|
17051
18888
|
for (let i = 0; i < segments.length - 1; i++) {
|
|
17052
|
-
at =
|
|
18889
|
+
at = join26(at, segments[i]);
|
|
17053
18890
|
const shown2 = segments.slice(0, i + 1).join("/");
|
|
17054
18891
|
let st = lstatOrNull3(at);
|
|
17055
18892
|
if (st === null) {
|
|
@@ -17069,7 +18906,7 @@ function makeFolders(root, rel) {
|
|
|
17069
18906
|
return null;
|
|
17070
18907
|
}
|
|
17071
18908
|
function place(root, rel, content, out) {
|
|
17072
|
-
const abs =
|
|
18909
|
+
const abs = join26(root, rel);
|
|
17073
18910
|
const there = lstatOrNull3(abs);
|
|
17074
18911
|
if (there !== null) {
|
|
17075
18912
|
if (sameAs(abs, there, content))
|
|
@@ -17107,7 +18944,7 @@ function sameAs(abs, st, content) {
|
|
|
17107
18944
|
try {
|
|
17108
18945
|
if ("link" in content)
|
|
17109
18946
|
return st.isSymbolicLink() && readlinkSync(abs) === content.link;
|
|
17110
|
-
return st.isFile() &&
|
|
18947
|
+
return st.isFile() && readFileSync24(abs).equals(content.bytes);
|
|
17111
18948
|
} catch {
|
|
17112
18949
|
return false;
|
|
17113
18950
|
}
|
|
@@ -17117,20 +18954,20 @@ function restoreCopy(root, saveDir, rel, out) {
|
|
|
17117
18954
|
let at = saveDir;
|
|
17118
18955
|
const chain = [SAVED_COPIES_DIR_NAME, ...segments];
|
|
17119
18956
|
for (let i = 0; i < chain.length - 1; i++) {
|
|
17120
|
-
at =
|
|
18957
|
+
at = join26(at, chain[i]);
|
|
17121
18958
|
const st2 = lstatOrNull3(at);
|
|
17122
18959
|
if (st2 === null || st2.isSymbolicLink() || !st2.isDirectory()) {
|
|
17123
18960
|
out.left.push(`${rel}: the saved copy does not hold it`);
|
|
17124
18961
|
return;
|
|
17125
18962
|
}
|
|
17126
18963
|
}
|
|
17127
|
-
const src =
|
|
18964
|
+
const src = join26(at, chain[chain.length - 1]);
|
|
17128
18965
|
const st = lstatOrNull3(src);
|
|
17129
18966
|
try {
|
|
17130
18967
|
if (st !== null && st.isSymbolicLink())
|
|
17131
18968
|
return place(root, rel, { link: readlinkSync(src) }, out);
|
|
17132
18969
|
if (st !== null && st.isFile())
|
|
17133
|
-
return place(root, rel, { bytes:
|
|
18970
|
+
return place(root, rel, { bytes: readFileSync24(src), mode: st.mode & 511 }, out);
|
|
17134
18971
|
} catch {
|
|
17135
18972
|
}
|
|
17136
18973
|
out.left.push(`${rel}: the saved copy does not hold it`);
|
|
@@ -17228,7 +19065,7 @@ function restoreLines(root, ref, saved, out) {
|
|
|
17228
19065
|
}
|
|
17229
19066
|
function pinnedEngagement(root) {
|
|
17230
19067
|
try {
|
|
17231
|
-
const pin = JSON.parse(
|
|
19068
|
+
const pin = JSON.parse(readFileSync24(join26(root, BUNDLE_PIN_REL), "utf-8"));
|
|
17232
19069
|
return isEngagementId(pin.engagementId) ? pin.engagementId : null;
|
|
17233
19070
|
} catch {
|
|
17234
19071
|
return null;
|
|
@@ -17250,16 +19087,16 @@ function savedRefs(root) {
|
|
|
17250
19087
|
return refs;
|
|
17251
19088
|
}
|
|
17252
19089
|
function readSaved(folder, id) {
|
|
17253
|
-
const dir =
|
|
19090
|
+
const dir = join26(folder, id);
|
|
17254
19091
|
const st = lstatOrNull3(dir);
|
|
17255
19092
|
if (st === null || !st.isDirectory())
|
|
17256
19093
|
return null;
|
|
17257
|
-
const file =
|
|
19094
|
+
const file = join26(dir, SAVED_FILE_NAME);
|
|
17258
19095
|
const fst = lstatOrNull3(file);
|
|
17259
19096
|
if (fst === null || !fst.isFile())
|
|
17260
19097
|
return null;
|
|
17261
19098
|
try {
|
|
17262
|
-
const parsed = savedSchema.safeParse(JSON.parse(
|
|
19099
|
+
const parsed = savedSchema.safeParse(JSON.parse(readFileSync24(file, "utf-8")));
|
|
17263
19100
|
return parsed.success && parsed.data.id === id ? parsed.data : null;
|
|
17264
19101
|
} catch {
|
|
17265
19102
|
return null;
|
|
@@ -17268,7 +19105,7 @@ function readSaved(folder, id) {
|
|
|
17268
19105
|
function pruneAtRestore(root, folder, refs, now) {
|
|
17269
19106
|
try {
|
|
17270
19107
|
const common = git2(root, ["rev-parse", "--git-common-dir"]);
|
|
17271
|
-
const repository = common.ok && common.text.trim() !== "" ? realOrSelf(
|
|
19108
|
+
const repository = common.ok && common.text.trim() !== "" ? realOrSelf(resolve8(root, common.text.trim())) : null;
|
|
17272
19109
|
let names = [];
|
|
17273
19110
|
try {
|
|
17274
19111
|
names = readdirSync2(folder);
|
|
@@ -17280,7 +19117,7 @@ function pruneAtRestore(root, folder, refs, now) {
|
|
|
17280
19117
|
if (!savedIdSchema.safeParse(id).success)
|
|
17281
19118
|
continue;
|
|
17282
19119
|
here.add(id);
|
|
17283
|
-
const dir =
|
|
19120
|
+
const dir = join26(folder, id);
|
|
17284
19121
|
const st = lstatOrNull3(dir);
|
|
17285
19122
|
const answer = savedPruneAnswer({
|
|
17286
19123
|
refMadeAt: refs.get(id) ?? null,
|
|
@@ -17308,7 +19145,7 @@ function pruneAtRestore(root, folder, refs, now) {
|
|
|
17308
19145
|
}
|
|
17309
19146
|
function markedRepository(dir) {
|
|
17310
19147
|
try {
|
|
17311
|
-
const text =
|
|
19148
|
+
const text = readFileSync24(join26(dir, SAVED_REPOSITORY_FILE_NAME), "utf-8").trim();
|
|
17312
19149
|
return text === "" ? null : text;
|
|
17313
19150
|
} catch {
|
|
17314
19151
|
return null;
|
|
@@ -17412,12 +19249,20 @@ var USAGE = ` ${HALFCYCLE_COMMAND} [install] [target-repo] [engagement-id] [sel
|
|
|
17412
19249
|
run a task's test command on this machine, once before the change and once after, and record what it saw
|
|
17413
19250
|
${CHECK_TASKS_USAGE}
|
|
17414
19251
|
check a phase's plan in docs/build-records/ for work that would collide if it ran at once
|
|
19252
|
+
${CHECK_CRITERIA_USAGE}
|
|
19253
|
+
check that the record of a finished phase's check names every acceptance criterion of its features
|
|
19254
|
+
${CHECK_REPORT_USAGE}
|
|
19255
|
+
check a worker's report, saved from its pull request, against the commit it must be about
|
|
19256
|
+
${READY_USAGE}
|
|
19257
|
+
list the tasks of a phase's plan that can start now, longest chain first, up to the number given
|
|
17415
19258
|
${CHANGE_USAGE}
|
|
17416
19259
|
sort a change you are asked for into a small change, a mini phase or a full phase; move it up;
|
|
17417
19260
|
end a small change and write its record into docs/build-records/changes/
|
|
17418
19261
|
${PACE_USAGE}
|
|
17419
19262
|
suggest how many agents this build should run at once, and for how long; with no flag, how
|
|
17420
19263
|
many may start right now
|
|
19264
|
+
${ROUTE_USAGE}
|
|
19265
|
+
say how to start a task of a phase's plan: which crew member, and on which tier
|
|
17421
19266
|
${HALFCYCLE_COMMAND} preview [folder]
|
|
17422
19267
|
show what setting up this folder would add, change, remove or leave alone; writes nothing
|
|
17423
19268
|
${HALFCYCLE_COMMAND} doctor
|
|
@@ -17864,7 +19709,6 @@ ${PROJECT_RESET_LINE}
|
|
|
17864
19709
|
process.stdout.write(`[halfcycle] Method version: ${methodVersion}
|
|
17865
19710
|
`);
|
|
17866
19711
|
}
|
|
17867
|
-
await setupClaudePlan({ serviceUrl: credential?.serviceUrl ?? serviceUrl, engagementId, ownsProject, consoleUrl }, { interactive: () => isInteractiveRun(), assumeYes, ask: askLine, write: (text) => process.stdout.write(text) });
|
|
17868
19712
|
if (checks.gitInstalled) {
|
|
17869
19713
|
process.stdout.write(`[halfcycle] Git for Windows is installed. Open a new terminal before starting Claude Code.
|
|
17870
19714
|
`);
|
|
@@ -18082,6 +19926,61 @@ halfcycle build-record: until ${obtained.stampFailure.path} can be written, the
|
|
|
18082
19926
|
process.exitCode = run.exitCode;
|
|
18083
19927
|
return;
|
|
18084
19928
|
}
|
|
19929
|
+
if (cmd === "check-criteria") {
|
|
19930
|
+
const parsed = parseCheckCriteriaArgs(rest);
|
|
19931
|
+
if (!parsed.ok) {
|
|
19932
|
+
process.stderr.write(`halfcycle check-criteria: usage: ${CHECK_CRITERIA_USAGE}
|
|
19933
|
+
`);
|
|
19934
|
+
process.exitCode = 2;
|
|
19935
|
+
return;
|
|
19936
|
+
}
|
|
19937
|
+
const run = runCheckCriteria(parsed.repoRoot ?? process.cwd(), parsed.file);
|
|
19938
|
+
if (run.stdout !== "")
|
|
19939
|
+
process.stdout.write(run.stdout);
|
|
19940
|
+
if (run.stderr !== "")
|
|
19941
|
+
process.stderr.write(run.stderr);
|
|
19942
|
+
process.exitCode = run.exitCode;
|
|
19943
|
+
return;
|
|
19944
|
+
}
|
|
19945
|
+
if (cmd === "check-report") {
|
|
19946
|
+
const reportShortfalls = missingRequirements("check-report", rest);
|
|
19947
|
+
if (reportShortfalls.length > 0) {
|
|
19948
|
+
process.stderr.write(`${reportShortfalls[0].message}
|
|
19949
|
+
`);
|
|
19950
|
+
process.exitCode = 2;
|
|
19951
|
+
return;
|
|
19952
|
+
}
|
|
19953
|
+
const parsed = parseCheckReportArgs(rest);
|
|
19954
|
+
if (!parsed.ok) {
|
|
19955
|
+
process.stderr.write(`halfcycle check-report: usage: ${CHECK_REPORT_USAGE}
|
|
19956
|
+
`);
|
|
19957
|
+
process.exitCode = 2;
|
|
19958
|
+
return;
|
|
19959
|
+
}
|
|
19960
|
+
const run = runCheckReport(parsed.source, parsed.head);
|
|
19961
|
+
if (run.stdout !== "")
|
|
19962
|
+
process.stdout.write(run.stdout);
|
|
19963
|
+
if (run.stderr !== "")
|
|
19964
|
+
process.stderr.write(run.stderr);
|
|
19965
|
+
process.exitCode = run.exitCode;
|
|
19966
|
+
return;
|
|
19967
|
+
}
|
|
19968
|
+
if (cmd === "ready") {
|
|
19969
|
+
const parsed = parseReadyArgs(rest);
|
|
19970
|
+
if (!parsed.ok) {
|
|
19971
|
+
process.stderr.write(`halfcycle ready: usage: ${READY_USAGE}
|
|
19972
|
+
`);
|
|
19973
|
+
process.exitCode = 2;
|
|
19974
|
+
return;
|
|
19975
|
+
}
|
|
19976
|
+
const run = runReady(parsed.repoRoot ?? process.cwd(), parsed.phase, parsed.cap);
|
|
19977
|
+
if (run.stdout !== "")
|
|
19978
|
+
process.stdout.write(run.stdout);
|
|
19979
|
+
if (run.stderr !== "")
|
|
19980
|
+
process.stderr.write(run.stderr);
|
|
19981
|
+
process.exitCode = run.exitCode;
|
|
19982
|
+
return;
|
|
19983
|
+
}
|
|
18085
19984
|
if (cmd === "pace") {
|
|
18086
19985
|
const parsed = parsePaceArgs(rest);
|
|
18087
19986
|
if (!parsed.ok) {
|
|
@@ -18101,7 +20000,7 @@ halfcycle build-record: until ${obtained.stampFailure.path} can be written, the
|
|
|
18101
20000
|
readCredential: (root) => readRepoCredential(root),
|
|
18102
20001
|
spawnPace: (credential, root, args2) => runPaceChild(runner, credential, root, args2),
|
|
18103
20002
|
now: () => /* @__PURE__ */ new Date()
|
|
18104
|
-
});
|
|
20003
|
+
}, parsed.agent);
|
|
18105
20004
|
if (run.stdout !== "")
|
|
18106
20005
|
process.stdout.write(run.stdout);
|
|
18107
20006
|
if (run.stderr !== "")
|
|
@@ -18109,6 +20008,23 @@ halfcycle build-record: until ${obtained.stampFailure.path} can be written, the
|
|
|
18109
20008
|
process.exitCode = run.exitCode;
|
|
18110
20009
|
return;
|
|
18111
20010
|
}
|
|
20011
|
+
if (cmd === "route") {
|
|
20012
|
+
const parsed = parseRouteArgs(rest);
|
|
20013
|
+
if (!parsed.ok) {
|
|
20014
|
+
process.stderr.write(`halfcycle route: usage: ${ROUTE_USAGE}
|
|
20015
|
+
`);
|
|
20016
|
+
process.exitCode = 2;
|
|
20017
|
+
return;
|
|
20018
|
+
}
|
|
20019
|
+
const run = await runRoute(parsed.repoRoot ?? process.cwd(), parsed.phase, parsed.task, parsed.agent, {
|
|
20020
|
+
readCredential: (root) => readRepoCredential(root),
|
|
20021
|
+
readWork: (credential, phase) => readPhaseWork(credential, phase)
|
|
20022
|
+
});
|
|
20023
|
+
if (run.stderr !== "")
|
|
20024
|
+
process.stderr.write(run.stderr);
|
|
20025
|
+
process.stdout.write(run.stdout, () => process.exit(run.exitCode));
|
|
20026
|
+
return;
|
|
20027
|
+
}
|
|
18112
20028
|
if (cmd === "change") {
|
|
18113
20029
|
const parsed = parseChangeArgs(rest);
|
|
18114
20030
|
if (!parsed.ok) {
|
|
@@ -18331,12 +20247,22 @@ halfcycle open-phase: until ${opened.stampFailure.path} can be written, guard ev
|
|
|
18331
20247
|
const timeSummary = await readCloseTimeSummary(credential, phaseArg);
|
|
18332
20248
|
for (const line of timeTotalsLines(timeSummary))
|
|
18333
20249
|
process.stdout.write(`${line}
|
|
20250
|
+
`);
|
|
20251
|
+
for (const line of phaseLimitLines(credential.engagementId, phaseArg))
|
|
20252
|
+
process.stdout.write(`${line}
|
|
20253
|
+
`);
|
|
20254
|
+
for (const line of relayedChoiceLines(credential.engagementId, phaseArg))
|
|
20255
|
+
process.stdout.write(`${line}
|
|
18334
20256
|
`);
|
|
18335
20257
|
const declarations = await readCloseOwnerWaitDeclarations(credential, phaseArg);
|
|
20258
|
+
const work = await readPhaseWork(credential, phaseArg);
|
|
20259
|
+
for (const line of tierFigureLines(work.kind === "work" ? work.work : null, readPhaseExpected(credential.engagementId, phaseArg), timeSummary, readPhaseModels(credential.engagementId, phaseArg, timeSummary?.openedAt ?? null))) {
|
|
20260
|
+
process.stdout.write(`${line}
|
|
20261
|
+
`);
|
|
20262
|
+
}
|
|
18336
20263
|
for (const line of ownerWaitCloseLines(declarations))
|
|
18337
20264
|
process.stdout.write(`${line}
|
|
18338
20265
|
`);
|
|
18339
|
-
const work = await readPhaseWork(credential, phaseArg);
|
|
18340
20266
|
if (work.kind === "work") {
|
|
18341
20267
|
for (const line of wentAheadCloseLines(work.work.calls))
|
|
18342
20268
|
process.stdout.write(`${line}
|