halfcycle 0.5.2 → 0.6.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 +299 -4
- package/dist/check-report.d.ts +57 -0
- package/dist/check-report.d.ts.map +1 -0
- package/dist/check-tasks.d.ts +94 -4
- 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 +1083 -161
- package/dist/cli.js.map +3 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +178 -49
- package/dist/index.js.map +3 -3
- package/dist/ready.d.ts +103 -0
- package/dist/ready.d.ts.map +1 -0
- package/dist/review.d.ts +3 -3
- package/dist/review.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -414,15 +414,15 @@ function readReviewPart(raw) {
|
|
|
414
414
|
const name = raw["name"];
|
|
415
415
|
const built = raw["built"];
|
|
416
416
|
const decidable = raw["decidable"];
|
|
417
|
-
const
|
|
417
|
+
const awaitingAnswer = raw["awaitingAnswer"];
|
|
418
418
|
const sentBack = raw["sentBack"];
|
|
419
419
|
if (!isNonEmptyString(part) || !isNonEmptyString(name))
|
|
420
420
|
return null;
|
|
421
421
|
if (typeof built !== "boolean" || typeof decidable !== "boolean")
|
|
422
422
|
return null;
|
|
423
|
-
if (typeof
|
|
423
|
+
if (typeof awaitingAnswer !== "boolean" || typeof sentBack !== "boolean")
|
|
424
424
|
return null;
|
|
425
|
-
return { part, name, built, decidable, awaitingAnswer
|
|
425
|
+
return { part, name, built, decidable, awaitingAnswer, sentBack };
|
|
426
426
|
}
|
|
427
427
|
function isPlainObject(value) {
|
|
428
428
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
@@ -513,6 +513,11 @@ var TASK_LIST_FILE = "docs/build-records/build-tasks.json";
|
|
|
513
513
|
var TASK_PLAN_TASKS_MAX = 500;
|
|
514
514
|
var TASK_PLAN_ID_MAX = 64;
|
|
515
515
|
var TASK_PLAN_BUILDS_NONE = "none";
|
|
516
|
+
var TASK_PLAN_CRITERIA_NONE = "none";
|
|
517
|
+
var TASK_PLAN_SPECS_NONE = "none";
|
|
518
|
+
var EXAMPLE_SPEC = "order-intake";
|
|
519
|
+
var SPEC_NAME = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
520
|
+
var CITATION = /^([a-z0-9][a-z0-9-]{0,63}) AC((?:0|[1-9][0-9]*)[a-z]?)$/;
|
|
516
521
|
var TASK_PLAN_KINDS = ["build", "review", "spec", "walk", "check"];
|
|
517
522
|
var TASK_PLAN_INTERFACE_KINDS = [
|
|
518
523
|
"route",
|
|
@@ -538,7 +543,9 @@ var taskPlanTaskSchema = z2.object({
|
|
|
538
543
|
}).strict().optional(),
|
|
539
544
|
wholeProject: z2.literal(true).optional(),
|
|
540
545
|
ships: z2.enum(["deploy", "release"]).optional(),
|
|
541
|
-
builds: z2.union([z2.literal(TASK_PLAN_BUILDS_NONE), z2.array(planText)]).optional()
|
|
546
|
+
builds: z2.union([z2.literal(TASK_PLAN_BUILDS_NONE), z2.array(planText)]).optional(),
|
|
547
|
+
criteria: z2.union([z2.literal(TASK_PLAN_CRITERIA_NONE), z2.array(planText)]).optional(),
|
|
548
|
+
whyOutsidePlan: planText.optional()
|
|
542
549
|
}).strict().superRefine((task, ctx) => {
|
|
543
550
|
if (task.reviews !== void 0 && task.kind !== "review") {
|
|
544
551
|
ctx.addIssue({
|
|
@@ -564,6 +571,7 @@ var taskPlanTaskSchema = z2.object({
|
|
|
564
571
|
});
|
|
565
572
|
var taskPlanSchema = z2.object({
|
|
566
573
|
phase: z2.string().min(1).refine((text) => !CONTROL_CHARACTER.test(text), { message: "holds a control character" }),
|
|
574
|
+
specs: z2.union([z2.literal(TASK_PLAN_SPECS_NONE), z2.array(planText)]).optional(),
|
|
567
575
|
tasks: z2.array(taskPlanTaskSchema).max(TASK_PLAN_TASKS_MAX)
|
|
568
576
|
}).strict();
|
|
569
577
|
var taskListRowSchema = z2.object({
|
|
@@ -586,6 +594,20 @@ var taskPlanLineText = {
|
|
|
586
594
|
N1: (asked) => `The task list in ${TASK_LIST_FILE} is not for "${asked}", so it was not compared.`,
|
|
587
595
|
O6: (phase, asked) => `The plan is for "${phase}", not "${asked}".`,
|
|
588
596
|
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}".`,
|
|
597
|
+
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".`,
|
|
598
|
+
C1Form: (a, x, first) => `"${a}" names "${x}", which is not a criterion. Write it as <spec name> AC<number>, for example "${first} AC1".`,
|
|
599
|
+
C1Number: (a, x, folder, slug, n) => `"${a}" names "${x}", but ${folder}/${slug}.md has no AC${n}.`,
|
|
600
|
+
C2: (a, x) => `"${a}" names "${x}", which was retired. Remove it, or name the criterion that replaced it.`,
|
|
601
|
+
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.`,
|
|
602
|
+
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}".`,
|
|
603
|
+
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.`,
|
|
604
|
+
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}".`,
|
|
605
|
+
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".`,
|
|
606
|
+
C6Missing: (slug, folder) => `The plan's specs name "${slug}", but there is no ${folder}/${slug}.md.`,
|
|
607
|
+
B1: (s, t, x) => `"${s}" and "${t}" both own ${x}. 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.`,
|
|
608
|
+
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 /.`,
|
|
609
|
+
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.`,
|
|
610
|
+
N3: (s) => `"${s}" has no Boundary section, so what it owns was not checked.`,
|
|
589
611
|
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
612
|
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
613
|
K3: (b, a, c) => `"${b}" builds on "${a}" before "${c}" has checked it. Add "${c}" to the dependsOn of "${b}".`,
|
|
@@ -610,10 +632,10 @@ function planInterfaceKey(text) {
|
|
|
610
632
|
return null;
|
|
611
633
|
return `${kind}: ${name}`.toLowerCase();
|
|
612
634
|
}
|
|
613
|
-
function
|
|
635
|
+
function pathItemsOf(paths) {
|
|
614
636
|
const files = [];
|
|
615
637
|
const folders = [];
|
|
616
|
-
for (const path of
|
|
638
|
+
for (const path of paths) {
|
|
617
639
|
if (isMalformedPlanPath(path))
|
|
618
640
|
continue;
|
|
619
641
|
const item = { shown: path, key: path.toLowerCase() };
|
|
@@ -621,6 +643,10 @@ function comparableItemsOf(task) {
|
|
|
621
643
|
if (item.key.endsWith("/"))
|
|
622
644
|
folders.push(item);
|
|
623
645
|
}
|
|
646
|
+
return { files, folders };
|
|
647
|
+
}
|
|
648
|
+
function comparableItemsOf(task) {
|
|
649
|
+
const { files, folders } = pathItemsOf(task.touches?.files ?? []);
|
|
624
650
|
const interfaces = [];
|
|
625
651
|
for (const text of task.touches?.interfaces ?? []) {
|
|
626
652
|
const key = planInterfaceKey(text);
|
|
@@ -826,6 +852,9 @@ function firstDifference(plan, list2) {
|
|
|
826
852
|
}
|
|
827
853
|
return null;
|
|
828
854
|
}
|
|
855
|
+
function note(code, text) {
|
|
856
|
+
return { code, text, refuses: false };
|
|
857
|
+
}
|
|
829
858
|
function refusal(code, text) {
|
|
830
859
|
return { code, text, refuses: true };
|
|
831
860
|
}
|
|
@@ -838,6 +867,243 @@ function mustNameParts(task) {
|
|
|
838
867
|
function namesNoPart(task) {
|
|
839
868
|
return task.builds === void 0 || Array.isArray(task.builds) && task.builds.length === 0;
|
|
840
869
|
}
|
|
870
|
+
function partsNotInPlan(tasks, planPartIds) {
|
|
871
|
+
const planned = new Set(planPartIds);
|
|
872
|
+
const out = [];
|
|
873
|
+
for (const task of tasks) {
|
|
874
|
+
if (task.reviews !== void 0 || task.kind === "review")
|
|
875
|
+
continue;
|
|
876
|
+
if (!Array.isArray(task.builds))
|
|
877
|
+
continue;
|
|
878
|
+
if (task.whyOutsidePlan !== void 0 && task.whyOutsidePlan.trim() !== "")
|
|
879
|
+
continue;
|
|
880
|
+
const parts = [...new Set(task.builds.filter((part) => !planned.has(part)))];
|
|
881
|
+
if (parts.length > 0)
|
|
882
|
+
out.push({ id: task.id, parts });
|
|
883
|
+
}
|
|
884
|
+
return out;
|
|
885
|
+
}
|
|
886
|
+
function namesNoCriterion(task) {
|
|
887
|
+
return task.criteria === void 0 || Array.isArray(task.criteria) && task.criteria.length === 0;
|
|
888
|
+
}
|
|
889
|
+
function isPlanSpecName(text) {
|
|
890
|
+
return SPEC_NAME.test(text);
|
|
891
|
+
}
|
|
892
|
+
function planSpecNamesToRead(plan) {
|
|
893
|
+
if (!Array.isArray(plan.specs))
|
|
894
|
+
return [];
|
|
895
|
+
return distinct(plan.specs.filter(isPlanSpecName));
|
|
896
|
+
}
|
|
897
|
+
var CRITERIA_HEADING = /^##\s+Acceptance criteria/;
|
|
898
|
+
var SECTION_HEADING = /^##\s/;
|
|
899
|
+
var ANY_HEADING = /^#{1,6}\s/;
|
|
900
|
+
var CHECKLIST_ITEM = /^- \[[ x]\] /;
|
|
901
|
+
var EXPLICIT_NUMBER = /^\*\*AC((?:0|[1-9][0-9]*)[a-z]?)(?: —|\.|:)/;
|
|
902
|
+
var RETIRED_LEAD = /^\*\*AC(?:0|[1-9][0-9]*)[a-z]? — retired \d{4}-\d{2}-\d{2}/;
|
|
903
|
+
function readSpecCriteria(markdown) {
|
|
904
|
+
const lines = markdown.split(/\r?\n/);
|
|
905
|
+
const start = lines.findIndex((line) => CRITERIA_HEADING.test(line));
|
|
906
|
+
if (start < 0)
|
|
907
|
+
return [];
|
|
908
|
+
const items = [];
|
|
909
|
+
let current = null;
|
|
910
|
+
const close = () => {
|
|
911
|
+
if (current !== null)
|
|
912
|
+
items.push(current);
|
|
913
|
+
current = null;
|
|
914
|
+
};
|
|
915
|
+
for (let i = start + 1; i < lines.length; i += 1) {
|
|
916
|
+
const line = lines[i];
|
|
917
|
+
if (SECTION_HEADING.test(line))
|
|
918
|
+
break;
|
|
919
|
+
if (CHECKLIST_ITEM.test(line)) {
|
|
920
|
+
close();
|
|
921
|
+
current = line.slice("- [ ] ".length);
|
|
922
|
+
continue;
|
|
923
|
+
}
|
|
924
|
+
if (line.trim() === "" || ANY_HEADING.test(line)) {
|
|
925
|
+
close();
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
928
|
+
if (current !== null)
|
|
929
|
+
current = `${current} ${line.trim()}`;
|
|
930
|
+
}
|
|
931
|
+
close();
|
|
932
|
+
return items.map((item, i) => {
|
|
933
|
+
const explicit = EXPLICIT_NUMBER.exec(item);
|
|
934
|
+
return {
|
|
935
|
+
number: explicit ? explicit[1] : String(i + 1),
|
|
936
|
+
retired: RETIRED_LEAD.test(item)
|
|
937
|
+
};
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
function liveNumbers(criteria) {
|
|
941
|
+
const live = new Set(criteria.filter((c) => !c.retired).map((c) => c.number));
|
|
942
|
+
return distinct(criteria.map((c) => c.number)).filter((n) => live.has(n));
|
|
943
|
+
}
|
|
944
|
+
function specLines(plan, specFiles) {
|
|
945
|
+
const folder = specFiles.folder;
|
|
946
|
+
if (plan.specs === TASK_PLAN_SPECS_NONE)
|
|
947
|
+
return [];
|
|
948
|
+
if (plan.specs === void 0 || plan.specs.length === 0) {
|
|
949
|
+
return [refusal("C6", taskPlanLineText.C6None(folder))];
|
|
950
|
+
}
|
|
951
|
+
const criteriaOf = specCriteriaBySlug(specFiles);
|
|
952
|
+
const lines = [];
|
|
953
|
+
for (const name of distinct(plan.specs)) {
|
|
954
|
+
if (!isPlanSpecName(name)) {
|
|
955
|
+
lines.push(refusal("C6", taskPlanLineText.C6Malformed(name, folder)));
|
|
956
|
+
} else if ((criteriaOf.get(name) ?? null) === null) {
|
|
957
|
+
lines.push(refusal("C6", taskPlanLineText.C6Missing(name, folder)));
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
const covered = /* @__PURE__ */ new Set();
|
|
961
|
+
for (const task of plan.tasks) {
|
|
962
|
+
if (task.kind !== "build" || !Array.isArray(task.criteria))
|
|
963
|
+
continue;
|
|
964
|
+
for (const citation of task.criteria)
|
|
965
|
+
covered.add(citation);
|
|
966
|
+
}
|
|
967
|
+
for (const slug of planSpecNamesToRead(plan)) {
|
|
968
|
+
const criteria = criteriaOf.get(slug) ?? null;
|
|
969
|
+
if (criteria === null)
|
|
970
|
+
continue;
|
|
971
|
+
for (const n of liveNumbers(criteria)) {
|
|
972
|
+
if (!covered.has(`${slug} AC${n}`))
|
|
973
|
+
lines.push(refusal("C3", taskPlanLineText.C3(slug, n)));
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
return lines;
|
|
977
|
+
}
|
|
978
|
+
function specCriteriaBySlug(specFiles) {
|
|
979
|
+
const bySlug = /* @__PURE__ */ new Map();
|
|
980
|
+
for (const entry of specFiles.entries) {
|
|
981
|
+
if (!bySlug.has(entry.slug))
|
|
982
|
+
bySlug.set(entry.slug, entry.criteria);
|
|
983
|
+
}
|
|
984
|
+
return bySlug;
|
|
985
|
+
}
|
|
986
|
+
function citationLine(taskId, citation, plan, specFiles, criteriaOf) {
|
|
987
|
+
const match = CITATION.exec(citation);
|
|
988
|
+
if (match === null) {
|
|
989
|
+
const first = Array.isArray(plan.specs) ? plan.specs.find(isPlanSpecName) : void 0;
|
|
990
|
+
return refusal("C1", taskPlanLineText.C1Form(taskId, citation, first ?? EXAMPLE_SPEC));
|
|
991
|
+
}
|
|
992
|
+
if (plan.specs === void 0 || plan.specs.length === 0)
|
|
993
|
+
return null;
|
|
994
|
+
const slug = match[1];
|
|
995
|
+
const n = match[2];
|
|
996
|
+
if (plan.specs === TASK_PLAN_SPECS_NONE || !plan.specs.includes(slug)) {
|
|
997
|
+
return refusal("C5", taskPlanLineText.C5(taskId, citation, slug));
|
|
998
|
+
}
|
|
999
|
+
const criteria = criteriaOf.get(slug) ?? null;
|
|
1000
|
+
if (criteria === null)
|
|
1001
|
+
return null;
|
|
1002
|
+
const items = criteria.filter((c) => c.number === n);
|
|
1003
|
+
if (items.length === 0) {
|
|
1004
|
+
return refusal("C1", taskPlanLineText.C1Number(taskId, citation, specFiles.folder, slug, n));
|
|
1005
|
+
}
|
|
1006
|
+
if (items.every((c) => c.retired))
|
|
1007
|
+
return refusal("C2", taskPlanLineText.C2(taskId, citation));
|
|
1008
|
+
return null;
|
|
1009
|
+
}
|
|
1010
|
+
function shownOwnsText(text) {
|
|
1011
|
+
return text.replace(/[\u0000-\u001f\u007f-\u009f]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
1012
|
+
}
|
|
1013
|
+
function ownedPathsBySpec(plan, specFiles) {
|
|
1014
|
+
const boundaryOf = boundaryBySlug(specFiles);
|
|
1015
|
+
const owned = /* @__PURE__ */ new Map();
|
|
1016
|
+
for (const slug of planSpecNamesToRead(plan)) {
|
|
1017
|
+
const boundary = boundaryOf.get(slug) ?? null;
|
|
1018
|
+
if (boundary === null || boundary.kind !== "section")
|
|
1019
|
+
continue;
|
|
1020
|
+
const paths = [];
|
|
1021
|
+
for (const item of boundary.owns) {
|
|
1022
|
+
if (item.kind === "path" && !isMalformedOwnsPath(item.path))
|
|
1023
|
+
paths.push(item.path);
|
|
1024
|
+
}
|
|
1025
|
+
owned.set(slug, paths);
|
|
1026
|
+
}
|
|
1027
|
+
return owned;
|
|
1028
|
+
}
|
|
1029
|
+
function isMalformedOwnsPath(path) {
|
|
1030
|
+
return isMalformedPlanPath(path) || CONTROL_CHARACTER.test(path);
|
|
1031
|
+
}
|
|
1032
|
+
function boundaryBySlug(specFiles) {
|
|
1033
|
+
const bySlug = /* @__PURE__ */ new Map();
|
|
1034
|
+
for (const entry of specFiles.entries) {
|
|
1035
|
+
if (!bySlug.has(entry.slug))
|
|
1036
|
+
bySlug.set(entry.slug, entry.boundary);
|
|
1037
|
+
}
|
|
1038
|
+
return bySlug;
|
|
1039
|
+
}
|
|
1040
|
+
function ownershipLines(plan, specFiles) {
|
|
1041
|
+
const lines = [];
|
|
1042
|
+
const boundaryOf = boundaryBySlug(specFiles);
|
|
1043
|
+
for (const slug of planSpecNamesToRead(plan)) {
|
|
1044
|
+
const boundary = boundaryOf.get(slug) ?? null;
|
|
1045
|
+
if (boundary === null || boundary.kind !== "section")
|
|
1046
|
+
continue;
|
|
1047
|
+
for (const item of boundary.owns) {
|
|
1048
|
+
if (item.kind === "bad") {
|
|
1049
|
+
lines.push(refusal("B2", taskPlanLineText.B2(slug, shownOwnsText(item.text))));
|
|
1050
|
+
} else if (isMalformedOwnsPath(item.path)) {
|
|
1051
|
+
lines.push(refusal("B2", taskPlanLineText.B2(slug, shownOwnsText(item.path))));
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
const owned = [...ownedPathsBySpec(plan, specFiles)].map(([slug, paths]) => {
|
|
1056
|
+
const { files, folders } = pathItemsOf(paths);
|
|
1057
|
+
const items = {
|
|
1058
|
+
files,
|
|
1059
|
+
fileKeys: new Set(files.map((f) => f.key)),
|
|
1060
|
+
folders,
|
|
1061
|
+
interfaces: [],
|
|
1062
|
+
interfaceKeys: /* @__PURE__ */ new Set()
|
|
1063
|
+
};
|
|
1064
|
+
return { slug, items };
|
|
1065
|
+
});
|
|
1066
|
+
for (let i = 0; i < owned.length; i += 1) {
|
|
1067
|
+
for (let j = i + 1; j < owned.length; j += 1) {
|
|
1068
|
+
const shared = firstSharedItem(owned[i].items, owned[j].items);
|
|
1069
|
+
if (shared !== null)
|
|
1070
|
+
lines.push(refusal("B1", taskPlanLineText.B1(owned[i].slug, owned[j].slug, shared)));
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
return lines;
|
|
1074
|
+
}
|
|
1075
|
+
function ownershipNotes(plan, specFiles) {
|
|
1076
|
+
const lines = [];
|
|
1077
|
+
const boundaryOf = boundaryBySlug(specFiles);
|
|
1078
|
+
for (const slug of planSpecNamesToRead(plan)) {
|
|
1079
|
+
if (boundaryOf.get(slug)?.kind === "none")
|
|
1080
|
+
lines.push(note("N3", taskPlanLineText.N3(slug)));
|
|
1081
|
+
}
|
|
1082
|
+
const owned = ownedPathsBySpec(plan, specFiles);
|
|
1083
|
+
const listed = new Set(planSpecNamesToRead(plan));
|
|
1084
|
+
for (const task of plan.tasks) {
|
|
1085
|
+
if (task.kind !== "build" || !Array.isArray(task.criteria))
|
|
1086
|
+
continue;
|
|
1087
|
+
const ownPaths = [];
|
|
1088
|
+
let anySection = false;
|
|
1089
|
+
for (const slug of distinct(task.criteria.map((c) => CITATION.exec(c)?.[1] ?? ""))) {
|
|
1090
|
+
const paths = listed.has(slug) ? owned.get(slug) : void 0;
|
|
1091
|
+
if (paths === void 0)
|
|
1092
|
+
continue;
|
|
1093
|
+
anySection = true;
|
|
1094
|
+
ownPaths.push(...paths);
|
|
1095
|
+
}
|
|
1096
|
+
if (!anySection)
|
|
1097
|
+
continue;
|
|
1098
|
+
const { files: ownedFiles, folders: ownedFolders } = pathItemsOf(ownPaths);
|
|
1099
|
+
const ownedKeys = new Set(ownedFiles.map((f) => f.key));
|
|
1100
|
+
const unowned = pathItemsOf(task.touches?.files ?? []).files.filter((touched) => !ownedKeys.has(touched.key) && !ownedFolders.some((folder) => touched.key.startsWith(folder.key)));
|
|
1101
|
+
if (unowned.length > 0) {
|
|
1102
|
+
lines.push(note("N2", taskPlanLineText.N2(task.id, distinct(unowned.map((f) => f.shown)))));
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
return lines;
|
|
1106
|
+
}
|
|
841
1107
|
function isWholeProjectCheck(task) {
|
|
842
1108
|
return task.kind === "check" && task.wholeProject === true;
|
|
843
1109
|
}
|
|
@@ -973,6 +1239,8 @@ function checkTaskPlan(plan, request) {
|
|
|
973
1239
|
const tasks = plan.tasks;
|
|
974
1240
|
const lines = [];
|
|
975
1241
|
const system = request.system;
|
|
1242
|
+
const criteriaOf = specCriteriaBySlug(request.specFiles);
|
|
1243
|
+
const phasePlan = request.phasePlan;
|
|
976
1244
|
const ids = new Set(tasks.map((task) => task.id));
|
|
977
1245
|
const seen = /* @__PURE__ */ new Set();
|
|
978
1246
|
const reportedTwice = /* @__PURE__ */ new Set();
|
|
@@ -1013,7 +1281,23 @@ function checkTaskPlan(plan, request) {
|
|
|
1013
1281
|
if (system !== void 0 && system.partIds.length > 0 && mustNameParts(task) && namesNoPart(task)) {
|
|
1014
1282
|
lines.push(refusal("O7", taskPlanLineText.O7(task.id, system.file, system.partIds)));
|
|
1015
1283
|
}
|
|
1284
|
+
if (phasePlan !== void 0) {
|
|
1285
|
+
for (const found of partsNotInPlan([task], phasePlan.partIds)) {
|
|
1286
|
+
lines.push(refusal("O8", taskPlanLineText.O8(found.id, found.parts, phasePlan.file)));
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
if (mustNameParts(task) && namesNoCriterion(task)) {
|
|
1290
|
+
lines.push(refusal("C4", taskPlanLineText.C4(task.id)));
|
|
1291
|
+
}
|
|
1292
|
+
if (Array.isArray(task.criteria)) {
|
|
1293
|
+
for (const citation of task.criteria) {
|
|
1294
|
+
const line = citationLine(task.id, citation, plan, request.specFiles, criteriaOf);
|
|
1295
|
+
if (line !== null)
|
|
1296
|
+
lines.push(line);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1016
1299
|
}
|
|
1300
|
+
lines.push(...specLines(plan, request.specFiles));
|
|
1017
1301
|
const { before, cycles } = waitsFor(tasks);
|
|
1018
1302
|
for (const path of cycles)
|
|
1019
1303
|
lines.push(refusal("O4", taskPlanLineText.O4Cycle(path)));
|
|
@@ -1034,6 +1318,7 @@ function checkTaskPlan(plan, request) {
|
|
|
1034
1318
|
}
|
|
1035
1319
|
}
|
|
1036
1320
|
}
|
|
1321
|
+
lines.push(...ownershipLines(plan, request.specFiles));
|
|
1037
1322
|
if (request.taskList !== void 0) {
|
|
1038
1323
|
const file = request.taskList.file;
|
|
1039
1324
|
if (file !== null && !Array.isArray(file) && file.phase === request.phase) {
|
|
@@ -1044,6 +1329,7 @@ function checkTaskPlan(plan, request) {
|
|
|
1044
1329
|
lines.push({ code: "N1", text: taskPlanLineText.N1(request.phase), refuses: false });
|
|
1045
1330
|
}
|
|
1046
1331
|
}
|
|
1332
|
+
lines.push(...ownershipNotes(plan, request.specFiles));
|
|
1047
1333
|
lines.push(...placementLines(tasks, before));
|
|
1048
1334
|
const refused3 = lines.some((line) => line.refuses);
|
|
1049
1335
|
if (!refused3)
|
|
@@ -1051,6 +1337,108 @@ function checkTaskPlan(plan, request) {
|
|
|
1051
1337
|
return { lines, refused: refused3 };
|
|
1052
1338
|
}
|
|
1053
1339
|
|
|
1340
|
+
// ../core/dist/spec-boundary.js
|
|
1341
|
+
var SPEC_BOUNDARY_OWNS_LABEL = "**Owns.**";
|
|
1342
|
+
var SPEC_BOUNDARY_LABELS = [
|
|
1343
|
+
SPEC_BOUNDARY_OWNS_LABEL,
|
|
1344
|
+
"**Out of boundary.**",
|
|
1345
|
+
"**Allowed dependencies.**",
|
|
1346
|
+
"**Revalidation triggers.**"
|
|
1347
|
+
];
|
|
1348
|
+
var BOUNDARY_HEADING = /^## Boundary[ \t]*$/;
|
|
1349
|
+
var SECTION_HEADING2 = /^## /;
|
|
1350
|
+
var FENCE = /^ {0,3}(`{3,}|~{3,})/;
|
|
1351
|
+
var BULLET = /^- /;
|
|
1352
|
+
var INDENTED = /^[ \t]/;
|
|
1353
|
+
var ONE_SPAN = /^`([^`]+)`$/;
|
|
1354
|
+
function fenceTracker() {
|
|
1355
|
+
let open = null;
|
|
1356
|
+
return (line) => {
|
|
1357
|
+
const match = FENCE.exec(line);
|
|
1358
|
+
if (open === null) {
|
|
1359
|
+
if (match === null)
|
|
1360
|
+
return true;
|
|
1361
|
+
open = match[1];
|
|
1362
|
+
return false;
|
|
1363
|
+
}
|
|
1364
|
+
if (match !== null && match[1][0] === open[0] && match[1].length >= open.length) {
|
|
1365
|
+
const rest = line.slice(line.indexOf(match[1]) + match[1].length);
|
|
1366
|
+
if (rest.trim() === "")
|
|
1367
|
+
open = null;
|
|
1368
|
+
}
|
|
1369
|
+
return false;
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
function itemOf(first, continued) {
|
|
1373
|
+
const head = first.trim();
|
|
1374
|
+
if (continued.length > 0)
|
|
1375
|
+
return { kind: "bad", text: [head, ...continued].join(" ") };
|
|
1376
|
+
const span = ONE_SPAN.exec(head);
|
|
1377
|
+
if (span === null)
|
|
1378
|
+
return { kind: "bad", text: head };
|
|
1379
|
+
return { kind: "path", path: span[1] };
|
|
1380
|
+
}
|
|
1381
|
+
function readSpecBoundary(markdown) {
|
|
1382
|
+
const lines = markdown.split(/\r?\n/);
|
|
1383
|
+
const outside = fenceTracker();
|
|
1384
|
+
let start = -1;
|
|
1385
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
1386
|
+
if (outside(lines[i]) && BOUNDARY_HEADING.test(lines[i])) {
|
|
1387
|
+
start = i;
|
|
1388
|
+
break;
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
if (start < 0)
|
|
1392
|
+
return { kind: "none" };
|
|
1393
|
+
const owns = [];
|
|
1394
|
+
let ownsSeen = false;
|
|
1395
|
+
let listOpen = false;
|
|
1396
|
+
let first = null;
|
|
1397
|
+
let continued = [];
|
|
1398
|
+
const closeBullet = () => {
|
|
1399
|
+
if (first !== null)
|
|
1400
|
+
owns.push(itemOf(first, continued));
|
|
1401
|
+
first = null;
|
|
1402
|
+
continued = [];
|
|
1403
|
+
};
|
|
1404
|
+
const closeList = () => {
|
|
1405
|
+
closeBullet();
|
|
1406
|
+
listOpen = false;
|
|
1407
|
+
};
|
|
1408
|
+
for (let i = start + 1; i < lines.length; i += 1) {
|
|
1409
|
+
const line = lines[i];
|
|
1410
|
+
if (!outside(line)) {
|
|
1411
|
+
closeList();
|
|
1412
|
+
continue;
|
|
1413
|
+
}
|
|
1414
|
+
if (SECTION_HEADING2.test(line))
|
|
1415
|
+
break;
|
|
1416
|
+
const trimmed = line.trimEnd();
|
|
1417
|
+
if (SPEC_BOUNDARY_LABELS.includes(trimmed)) {
|
|
1418
|
+
closeList();
|
|
1419
|
+
if (trimmed === SPEC_BOUNDARY_OWNS_LABEL && !ownsSeen) {
|
|
1420
|
+
ownsSeen = true;
|
|
1421
|
+
listOpen = true;
|
|
1422
|
+
}
|
|
1423
|
+
continue;
|
|
1424
|
+
}
|
|
1425
|
+
if (!listOpen)
|
|
1426
|
+
continue;
|
|
1427
|
+
if (BULLET.test(line)) {
|
|
1428
|
+
closeBullet();
|
|
1429
|
+
first = line.slice(2);
|
|
1430
|
+
continue;
|
|
1431
|
+
}
|
|
1432
|
+
if (INDENTED.test(line) && line.trim() !== "" && first !== null) {
|
|
1433
|
+
continued.push(line.trim());
|
|
1434
|
+
continue;
|
|
1435
|
+
}
|
|
1436
|
+
closeList();
|
|
1437
|
+
}
|
|
1438
|
+
closeList();
|
|
1439
|
+
return { kind: "section", owns };
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1054
1442
|
// ../core/dist/agent-profile.js
|
|
1055
1443
|
var AGENT_IDS = ["claude-code", "codex"];
|
|
1056
1444
|
var CLAUDE_CODE_PROFILE = {
|
|
@@ -1148,6 +1536,222 @@ var AGENT_PROFILES = {
|
|
|
1148
1536
|
};
|
|
1149
1537
|
var ALL_HOOK_COMMANDS = new Set(AGENT_IDS.flatMap((id) => [...AGENT_PROFILES[id].commands]));
|
|
1150
1538
|
|
|
1539
|
+
// ../core/dist/worker-report.js
|
|
1540
|
+
var WORKER_REPORT_STATUSES = Object.freeze([
|
|
1541
|
+
"done",
|
|
1542
|
+
"needs-human-verification",
|
|
1543
|
+
"blocked-on-human",
|
|
1544
|
+
"failed"
|
|
1545
|
+
]);
|
|
1546
|
+
var WORKER_REPORT_RULE_FIELDS = Object.freeze({
|
|
1547
|
+
W1: "Worker report",
|
|
1548
|
+
W2: "Status",
|
|
1549
|
+
W3: "Head",
|
|
1550
|
+
W4: "Head",
|
|
1551
|
+
W5: "Verification",
|
|
1552
|
+
W6: "Verification",
|
|
1553
|
+
W7: "Verification",
|
|
1554
|
+
W8: "Unverified"
|
|
1555
|
+
});
|
|
1556
|
+
var WORKER_REPORT_LINES = Object.freeze({
|
|
1557
|
+
noBlock: () => 'There is no "## Worker report" block in this report.',
|
|
1558
|
+
statusMissing: (statuses) => `The report has no Status line. Use one of: ${statuses.join(", ")}.`,
|
|
1559
|
+
statusUnknown: (found, statuses) => `The report's Status is "${found}". Use one of: ${statuses.join(", ")}.`,
|
|
1560
|
+
headMissing: () => "The report has no Head line. Give the full commit id the report is about.",
|
|
1561
|
+
headMalformed: (found) => `The report's Head is "${found}", which is not a full commit id.`,
|
|
1562
|
+
headDiffers: (found, head) => `The report is about commit ${found}, but the work's head is ${head}. Ask for a report on the current head.`,
|
|
1563
|
+
noVerification: () => "The report does not say what was run. List each check under Verification with its exit code.",
|
|
1564
|
+
noExitCode: (entry) => `"${entry}" under Verification does not give an exit code.`,
|
|
1565
|
+
nonZeroOnDone: (entry) => `"${entry}" exited with a code other than 0, so the work is not done.`,
|
|
1566
|
+
noUnverified: () => 'The report has no Unverified line. Say what was not checked, or "nothing".',
|
|
1567
|
+
clear: () => "The report is complete and names the commit it is about."
|
|
1568
|
+
});
|
|
1569
|
+
var FIELD_LINE = /^([A-Z][A-Za-z0-9 ]*):(.*)$/;
|
|
1570
|
+
var FULL_COMMIT = /^[0-9a-f]{40}$/;
|
|
1571
|
+
var EXIT_CODE = /(?:→|\bexit(?:ed)?(?:\s*code)?)\s*(\d+)/giu;
|
|
1572
|
+
function extractWorkerReportBlock(body) {
|
|
1573
|
+
const lines = String(body ?? "").replace(/\r\n/g, "\n").split("\n");
|
|
1574
|
+
const start = lines.findIndex((l) => /^##\s+Worker report\s*$/.test(l.trim()));
|
|
1575
|
+
if (start === -1)
|
|
1576
|
+
return null;
|
|
1577
|
+
const out = [];
|
|
1578
|
+
for (const line of lines.slice(start + 1)) {
|
|
1579
|
+
if (/^#{1,3}\s/.test(line.trim()))
|
|
1580
|
+
break;
|
|
1581
|
+
if (/^```/.test(line.trim()))
|
|
1582
|
+
continue;
|
|
1583
|
+
out.push(line);
|
|
1584
|
+
}
|
|
1585
|
+
return out;
|
|
1586
|
+
}
|
|
1587
|
+
function parseWorkerReportFields(blockLines) {
|
|
1588
|
+
const fields = /* @__PURE__ */ new Map();
|
|
1589
|
+
let current = null;
|
|
1590
|
+
for (const raw of blockLines) {
|
|
1591
|
+
const m = FIELD_LINE.exec(raw);
|
|
1592
|
+
if (m) {
|
|
1593
|
+
const name = m[1];
|
|
1594
|
+
if (fields.has(name)) {
|
|
1595
|
+
current = null;
|
|
1596
|
+
continue;
|
|
1597
|
+
}
|
|
1598
|
+
current = { inline: m[2].trim(), continuation: [] };
|
|
1599
|
+
fields.set(name, current);
|
|
1600
|
+
continue;
|
|
1601
|
+
}
|
|
1602
|
+
if (current)
|
|
1603
|
+
current.continuation.push(raw);
|
|
1604
|
+
}
|
|
1605
|
+
return fields;
|
|
1606
|
+
}
|
|
1607
|
+
function splitOutsideBackticks(text) {
|
|
1608
|
+
const parts = [];
|
|
1609
|
+
let buf = "";
|
|
1610
|
+
let inTick = false;
|
|
1611
|
+
for (let i = 0; i < text.length; i++) {
|
|
1612
|
+
const ch = text[i];
|
|
1613
|
+
if (ch === "`")
|
|
1614
|
+
inTick = !inTick;
|
|
1615
|
+
if (!inTick && ch === ";") {
|
|
1616
|
+
parts.push(buf);
|
|
1617
|
+
buf = "";
|
|
1618
|
+
continue;
|
|
1619
|
+
}
|
|
1620
|
+
if (!inTick && text.startsWith(" \xB7 ", i)) {
|
|
1621
|
+
parts.push(buf);
|
|
1622
|
+
buf = "";
|
|
1623
|
+
i += 2;
|
|
1624
|
+
continue;
|
|
1625
|
+
}
|
|
1626
|
+
buf += ch;
|
|
1627
|
+
}
|
|
1628
|
+
parts.push(buf);
|
|
1629
|
+
return parts.map((p) => p.trim()).filter(Boolean);
|
|
1630
|
+
}
|
|
1631
|
+
function verificationEntries(field) {
|
|
1632
|
+
if (!field)
|
|
1633
|
+
return [];
|
|
1634
|
+
const entries = field.inline ? splitOutsideBackticks(field.inline) : [];
|
|
1635
|
+
for (const line of field.continuation) {
|
|
1636
|
+
const m = /^\s*[-*]\s+(.*)$/.exec(line);
|
|
1637
|
+
if (m && m[1].trim())
|
|
1638
|
+
entries.push(m[1].trim());
|
|
1639
|
+
}
|
|
1640
|
+
return entries;
|
|
1641
|
+
}
|
|
1642
|
+
function verificationExitCode(entry) {
|
|
1643
|
+
let code = null;
|
|
1644
|
+
for (const m of entry.matchAll(EXIT_CODE))
|
|
1645
|
+
code = Number(m[1]);
|
|
1646
|
+
return code;
|
|
1647
|
+
}
|
|
1648
|
+
function checkWorkerReport(body, options = {}) {
|
|
1649
|
+
const statuses = options.statuses ?? WORKER_REPORT_STATUSES;
|
|
1650
|
+
const given = options.head ? options.head : null;
|
|
1651
|
+
const problems = [];
|
|
1652
|
+
const add = (id, line) => {
|
|
1653
|
+
problems.push({ id, field: WORKER_REPORT_RULE_FIELDS[id], line });
|
|
1654
|
+
};
|
|
1655
|
+
const block = extractWorkerReportBlock(body);
|
|
1656
|
+
if (block === null) {
|
|
1657
|
+
add("W1", WORKER_REPORT_LINES.noBlock());
|
|
1658
|
+
return { problems, headChecked: false, fields: {} };
|
|
1659
|
+
}
|
|
1660
|
+
const fields = parseWorkerReportFields(block);
|
|
1661
|
+
const inline = (name) => fields.get(name)?.inline;
|
|
1662
|
+
const status = inline("Status");
|
|
1663
|
+
if (status === void 0)
|
|
1664
|
+
add("W2", WORKER_REPORT_LINES.statusMissing(statuses));
|
|
1665
|
+
else if (!statuses.includes(status))
|
|
1666
|
+
add("W2", WORKER_REPORT_LINES.statusUnknown(status, statuses));
|
|
1667
|
+
const head = inline("Head");
|
|
1668
|
+
let headChecked = false;
|
|
1669
|
+
if (head === void 0)
|
|
1670
|
+
add("W3", WORKER_REPORT_LINES.headMissing());
|
|
1671
|
+
else if (!FULL_COMMIT.test(head))
|
|
1672
|
+
add("W3", WORKER_REPORT_LINES.headMalformed(head));
|
|
1673
|
+
else if (given !== null) {
|
|
1674
|
+
headChecked = true;
|
|
1675
|
+
if (head !== given)
|
|
1676
|
+
add("W4", WORKER_REPORT_LINES.headDiffers(head, given));
|
|
1677
|
+
}
|
|
1678
|
+
const entries = verificationEntries(fields.get("Verification"));
|
|
1679
|
+
if (entries.length === 0)
|
|
1680
|
+
add("W5", WORKER_REPORT_LINES.noVerification());
|
|
1681
|
+
const codes = entries.map((entry) => ({ entry, code: verificationExitCode(entry) }));
|
|
1682
|
+
for (const { entry, code } of codes) {
|
|
1683
|
+
if (code === null)
|
|
1684
|
+
add("W6", WORKER_REPORT_LINES.noExitCode(entry));
|
|
1685
|
+
}
|
|
1686
|
+
if (status === "done") {
|
|
1687
|
+
for (const { entry, code } of codes) {
|
|
1688
|
+
if (code !== null && code !== 0)
|
|
1689
|
+
add("W7", WORKER_REPORT_LINES.nonZeroOnDone(entry));
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
if (!fields.has("Unverified"))
|
|
1693
|
+
add("W8", WORKER_REPORT_LINES.noUnverified());
|
|
1694
|
+
const flat = {};
|
|
1695
|
+
for (const [name, field] of fields)
|
|
1696
|
+
flat[name] = field.inline;
|
|
1697
|
+
return { problems, headChecked, fields: flat };
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
// ../core/dist/task-ready.js
|
|
1701
|
+
var READY_DEFAULT_MINUTES = 60;
|
|
1702
|
+
function minutesOf(row) {
|
|
1703
|
+
return typeof row.minutes === "number" ? row.minutes : READY_DEFAULT_MINUTES;
|
|
1704
|
+
}
|
|
1705
|
+
function readyTasks(rows) {
|
|
1706
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1707
|
+
for (const row of rows) {
|
|
1708
|
+
if (!byId.has(row.id))
|
|
1709
|
+
byId.set(row.id, row);
|
|
1710
|
+
}
|
|
1711
|
+
const children = /* @__PURE__ */ new Map();
|
|
1712
|
+
for (const id of byId.keys())
|
|
1713
|
+
children.set(id, []);
|
|
1714
|
+
for (const row of byId.values()) {
|
|
1715
|
+
for (const dep of row.dependsOn)
|
|
1716
|
+
children.get(dep)?.push(row.id);
|
|
1717
|
+
}
|
|
1718
|
+
const memo = /* @__PURE__ */ new Map();
|
|
1719
|
+
const pathFrom = (id) => {
|
|
1720
|
+
const known = memo.get(id);
|
|
1721
|
+
if (known !== void 0)
|
|
1722
|
+
return known;
|
|
1723
|
+
memo.set(id, 0);
|
|
1724
|
+
let best = 0;
|
|
1725
|
+
for (const childId of children.get(id) ?? []) {
|
|
1726
|
+
const child = byId.get(childId);
|
|
1727
|
+
if (child === void 0 || child.progress === "completed")
|
|
1728
|
+
continue;
|
|
1729
|
+
best = Math.max(best, pathFrom(childId));
|
|
1730
|
+
}
|
|
1731
|
+
const total = minutesOf(byId.get(id)) + best;
|
|
1732
|
+
memo.set(id, total);
|
|
1733
|
+
return total;
|
|
1734
|
+
};
|
|
1735
|
+
const startable = [];
|
|
1736
|
+
const person = [];
|
|
1737
|
+
for (const row of byId.values()) {
|
|
1738
|
+
if (row.progress !== "pending")
|
|
1739
|
+
continue;
|
|
1740
|
+
if (!row.dependsOn.every((dep) => byId.get(dep)?.progress === "completed"))
|
|
1741
|
+
continue;
|
|
1742
|
+
if (row.person) {
|
|
1743
|
+
person.push(row.id);
|
|
1744
|
+
continue;
|
|
1745
|
+
}
|
|
1746
|
+
startable.push({ id: row.id, path: 0, tieRank: row.tieRank });
|
|
1747
|
+
}
|
|
1748
|
+
for (const entry of startable)
|
|
1749
|
+
entry.path = pathFrom(entry.id);
|
|
1750
|
+
startable.sort((a, b) => b.path - a.path || a.tieRank - b.tieRank || a.id.localeCompare(b.id));
|
|
1751
|
+
person.sort((a, b) => a.localeCompare(b));
|
|
1752
|
+
return { ready: startable.map((entry) => entry.id), person };
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1151
1755
|
// ../events/dist/result.js
|
|
1152
1756
|
import { z as z3 } from "zod";
|
|
1153
1757
|
var firedGuardSchema = z3.object({
|
|
@@ -2293,16 +2897,16 @@ function planCrossFieldProblems(raw, description) {
|
|
|
2293
2897
|
if (!isRecord(raw) || !Array.isArray(raw.changes))
|
|
2294
2898
|
return problems;
|
|
2295
2899
|
const known = new Set(description.parts.map((p) => p.id));
|
|
2296
|
-
const
|
|
2900
|
+
const named2 = /* @__PURE__ */ new Map();
|
|
2297
2901
|
raw.changes.forEach((change, i) => {
|
|
2298
2902
|
if (!isRecord(change) || typeof change.part !== "string")
|
|
2299
2903
|
return;
|
|
2300
2904
|
if (!known.has(change.part)) {
|
|
2301
2905
|
problems.push({ path: `changes.${i}.part`, message: `names no part in the system description: ${shown(change.part)}` });
|
|
2302
2906
|
}
|
|
2303
|
-
const earlier =
|
|
2907
|
+
const earlier = named2.get(change.part);
|
|
2304
2908
|
if (earlier === void 0) {
|
|
2305
|
-
|
|
2909
|
+
named2.set(change.part, i);
|
|
2306
2910
|
} else {
|
|
2307
2911
|
problems.push({ path: `changes.${i}.part`, message: `${shown(change.part)} is already listed at changes.${earlier}` });
|
|
2308
2912
|
}
|
|
@@ -3600,8 +4204,8 @@ function contractProblems(raw, description) {
|
|
|
3600
4204
|
if (!isRecord(contract) || typeof contract.from !== "string" || typeof contract.to !== "string")
|
|
3601
4205
|
return;
|
|
3602
4206
|
const { from, to } = contract;
|
|
3603
|
-
const
|
|
3604
|
-
if (!
|
|
4207
|
+
const named2 = description.connections.some((c) => c.from === from && c.to === to || c.from === to && c.to === from);
|
|
4208
|
+
if (!named2) {
|
|
3605
4209
|
problems.push({
|
|
3606
4210
|
path: `contracts.${i}`,
|
|
3607
4211
|
message: `names no connection in the system description: ${shown(from)} to ${shown(to)}`
|
|
@@ -4503,6 +5107,30 @@ var phaseReviewViewSchema = z20.discriminatedUnion("state", [
|
|
|
4503
5107
|
phaseReviewOpenSchema,
|
|
4504
5108
|
phaseReviewClosedSchema
|
|
4505
5109
|
]);
|
|
5110
|
+
function reviewPartAwaitingAnswer(part) {
|
|
5111
|
+
return part.questions.some((q) => q.agentAnswer === void 0);
|
|
5112
|
+
}
|
|
5113
|
+
function reviewPartSentBackIn(part, round) {
|
|
5114
|
+
return part.sentBack.some((s) => s.round === round);
|
|
5115
|
+
}
|
|
5116
|
+
function shownReviewEntryOf(view, shownAt) {
|
|
5117
|
+
const parts = view.parts.map((p) => ({
|
|
5118
|
+
part: p.part,
|
|
5119
|
+
name: p.name,
|
|
5120
|
+
built: p.built,
|
|
5121
|
+
decidable: p.decidable,
|
|
5122
|
+
awaitingAnswer: reviewPartAwaitingAnswer(p),
|
|
5123
|
+
sentBack: reviewPartSentBackIn(p, view.round)
|
|
5124
|
+
}));
|
|
5125
|
+
return {
|
|
5126
|
+
phase: view.phase,
|
|
5127
|
+
number: view.number,
|
|
5128
|
+
round: view.round,
|
|
5129
|
+
reviewVersion: view.reviewVersion,
|
|
5130
|
+
shownAt,
|
|
5131
|
+
parts
|
|
5132
|
+
};
|
|
5133
|
+
}
|
|
4506
5134
|
|
|
4507
5135
|
// ../events/dist/build-record-v3.js
|
|
4508
5136
|
var BUILD_RECORD_V3_FORMAT = "halfcycle-build-record/v3";
|
|
@@ -5146,8 +5774,35 @@ function ownerWaitClosedLineOf(item, question2) {
|
|
|
5146
5774
|
}
|
|
5147
5775
|
var OWNER_WAIT_DECLARATIONS_READ_FAILED = "Halfcycle could not read the decisions that waited for you.";
|
|
5148
5776
|
|
|
5149
|
-
// ../events/dist/
|
|
5777
|
+
// ../events/dist/owner-wait-answer.js
|
|
5150
5778
|
import { z as z32 } from "zod";
|
|
5779
|
+
var ownerWaitShortIdSchema = z32.string().regex(/^[0-9a-f]{8}$/);
|
|
5780
|
+
var OWNER_WAIT_ANSWER_ACTS = ["approve", "decline"];
|
|
5781
|
+
var ownerWaitAnswerActSchema = z32.enum(OWNER_WAIT_ANSWER_ACTS);
|
|
5782
|
+
var ownerWaitChatRequestSchema = z32.discriminatedUnion("act", [
|
|
5783
|
+
z32.object({ act: z32.literal("approve"), wait: ownerWaitShortIdSchema }).strict(),
|
|
5784
|
+
z32.object({ act: z32.literal("decline"), wait: ownerWaitShortIdSchema, note: scopeNoteWireSchema }).strict()
|
|
5785
|
+
]);
|
|
5786
|
+
function bareOutcome3(outcome) {
|
|
5787
|
+
return z32.object({ outcome: z32.literal(outcome) }).strict();
|
|
5788
|
+
}
|
|
5789
|
+
var ownerWaitChatResultSchema = z32.discriminatedUnion("outcome", [
|
|
5790
|
+
z32.object({
|
|
5791
|
+
outcome: z32.literal("recorded"),
|
|
5792
|
+
act: ownerWaitAnswerActSchema,
|
|
5793
|
+
question: z32.string().min(1)
|
|
5794
|
+
}).strict(),
|
|
5795
|
+
z32.object({ outcome: z32.literal("refused"), problems: z32.array(systemDescriptionProblemSchema).min(1) }).strict(),
|
|
5796
|
+
bareOutcome3("not-owner"),
|
|
5797
|
+
bareOutcome3("no-owner"),
|
|
5798
|
+
bareOutcome3("not-found"),
|
|
5799
|
+
bareOutcome3("ambiguous"),
|
|
5800
|
+
bareOutcome3("not-offered"),
|
|
5801
|
+
bareOutcome3("unavailable")
|
|
5802
|
+
]);
|
|
5803
|
+
|
|
5804
|
+
// ../events/dist/pacing.js
|
|
5805
|
+
import { z as z33 } from "zod";
|
|
5151
5806
|
var CLAUDE_PLANS = Object.freeze([
|
|
5152
5807
|
Object.freeze({ id: "pro", words: "Pro", startingAgents: 2 }),
|
|
5153
5808
|
Object.freeze({ id: "max-5x", words: "Max (5\xD7)", startingAgents: 4 }),
|
|
@@ -5158,7 +5813,7 @@ var CLAUDE_PLANS = Object.freeze([
|
|
|
5158
5813
|
Object.freeze({ id: "not-sure", words: "Not sure", startingAgents: 2 })
|
|
5159
5814
|
]);
|
|
5160
5815
|
var CLAUDE_PLAN_IDS = Object.freeze(CLAUDE_PLANS.map((plan) => plan.id));
|
|
5161
|
-
var claudePlanSchema =
|
|
5816
|
+
var claudePlanSchema = z33.enum(CLAUDE_PLAN_IDS);
|
|
5162
5817
|
var CLAUDE_PLAN_UNKNOWN = "not-sure";
|
|
5163
5818
|
function claudePlanOf(id) {
|
|
5164
5819
|
return CLAUDE_PLANS.find((plan) => plan.id === id) ?? CLAUDE_PLANS.find((plan) => plan.id === CLAUDE_PLAN_UNKNOWN) ?? CLAUDE_PLANS[0];
|
|
@@ -5167,21 +5822,21 @@ function claudePlanKnown(id) {
|
|
|
5167
5822
|
return id !== null && id !== CLAUDE_PLAN_UNKNOWN;
|
|
5168
5823
|
}
|
|
5169
5824
|
var CLAUDE_PLAN_SET_IN = ["setup", "console"];
|
|
5170
|
-
var claudePlanSetInSchema =
|
|
5825
|
+
var claudePlanSetInSchema = z33.enum(CLAUDE_PLAN_SET_IN);
|
|
5171
5826
|
var KEEP_FREE_PERCENTS = [0, 20];
|
|
5172
|
-
var keepFreePercentSchema =
|
|
5827
|
+
var keepFreePercentSchema = z33.union([z33.literal(KEEP_FREE_PERCENTS[0]), z33.literal(KEEP_FREE_PERCENTS[1])]);
|
|
5173
5828
|
var PACE_AGENTS_MAX = 20;
|
|
5174
5829
|
var PACE_WINDOW_KINDS = ["five-hour", "seven-day"];
|
|
5175
|
-
var paceWindowKindSchema =
|
|
5830
|
+
var paceWindowKindSchema = z33.enum(PACE_WINDOW_KINDS);
|
|
5176
5831
|
var PACE_WINDOW_WORDS = Object.freeze({
|
|
5177
5832
|
"five-hour": "5-hour window",
|
|
5178
5833
|
"seven-day": "weekly window"
|
|
5179
5834
|
});
|
|
5180
5835
|
var PACE_STATES = ["full", "slowing", "holding"];
|
|
5181
|
-
var paceStateSchema =
|
|
5182
|
-
var claudePlanRequestSchema =
|
|
5183
|
-
var keepFreeRequestSchema =
|
|
5184
|
-
var pacingSettingsSchema =
|
|
5836
|
+
var paceStateSchema = z33.enum(PACE_STATES);
|
|
5837
|
+
var claudePlanRequestSchema = z33.object({ claudePlan: claudePlanSchema }).strict();
|
|
5838
|
+
var keepFreeRequestSchema = z33.object({ keepFreePercent: keepFreePercentSchema }).strict();
|
|
5839
|
+
var pacingSettingsSchema = z33.object({
|
|
5185
5840
|
claudePlan: claudePlanSchema.nullable(),
|
|
5186
5841
|
claudePlanSetIn: claudePlanSetInSchema.nullable(),
|
|
5187
5842
|
keepFreePercent: keepFreePercentSchema
|
|
@@ -5189,43 +5844,43 @@ var pacingSettingsSchema = z32.object({
|
|
|
5189
5844
|
message: "a plan says where it was given, and only a plan does",
|
|
5190
5845
|
path: ["claudePlanSetIn"]
|
|
5191
5846
|
});
|
|
5192
|
-
var pacingBasisTaskSchema =
|
|
5847
|
+
var pacingBasisTaskSchema = z33.object({
|
|
5193
5848
|
id: buildTaskIdSchema,
|
|
5194
|
-
dependsOn:
|
|
5195
|
-
budgetMinutes:
|
|
5196
|
-
human:
|
|
5849
|
+
dependsOn: z33.array(buildTaskIdSchema).max(BUILD_SNAPSHOT_TASK_LIMIT),
|
|
5850
|
+
budgetMinutes: z33.number().int().min(1).max(BUILD_TASK_BUDGET_MAX_MINUTES).optional(),
|
|
5851
|
+
human: z33.boolean()
|
|
5197
5852
|
}).strict();
|
|
5198
|
-
var pacingBasisSchema =
|
|
5199
|
-
outcome:
|
|
5853
|
+
var pacingBasisSchema = z33.object({
|
|
5854
|
+
outcome: z33.literal("basis"),
|
|
5200
5855
|
claudePlan: claudePlanSchema.nullable(),
|
|
5201
5856
|
keepFreePercent: keepFreePercentSchema,
|
|
5202
|
-
settingsUrl:
|
|
5857
|
+
settingsUrl: z33.string().min(1),
|
|
5203
5858
|
phase: phaseNameSchema.nullable(),
|
|
5204
|
-
tasks:
|
|
5205
|
-
medianTaskMinutes:
|
|
5206
|
-
medianTaskTokens:
|
|
5859
|
+
tasks: z33.array(pacingBasisTaskSchema).max(BUILD_SNAPSHOT_TASK_LIMIT),
|
|
5860
|
+
medianTaskMinutes: z33.number().nonnegative().nullable(),
|
|
5861
|
+
medianTaskTokens: z33.number().nonnegative().nullable()
|
|
5207
5862
|
}).strict();
|
|
5208
|
-
var paceSuggestionSchema =
|
|
5209
|
-
number:
|
|
5210
|
-
minutes:
|
|
5211
|
-
pauses:
|
|
5863
|
+
var paceSuggestionSchema = z33.object({
|
|
5864
|
+
number: z33.number().int().min(1).max(PACE_AGENTS_MAX),
|
|
5865
|
+
minutes: z33.number().int().nonnegative().nullable(),
|
|
5866
|
+
pauses: z33.number().int().nonnegative(),
|
|
5212
5867
|
claudePlan: claudePlanSchema.nullable(),
|
|
5213
5868
|
keepFreePercent: keepFreePercentSchema,
|
|
5214
|
-
settingsUrl:
|
|
5869
|
+
settingsUrl: z33.string().min(1).nullable(),
|
|
5215
5870
|
knownWindow: paceWindowKindSchema.nullable()
|
|
5216
5871
|
}).strict();
|
|
5217
5872
|
var PACE_NOT_PACED_REASONS = ["not-configured", "unreadable"];
|
|
5218
|
-
var paceRunResultSchema =
|
|
5219
|
-
|
|
5220
|
-
outcome:
|
|
5873
|
+
var paceRunResultSchema = z33.discriminatedUnion("outcome", [
|
|
5874
|
+
z33.object({
|
|
5875
|
+
outcome: z33.literal("paced"),
|
|
5221
5876
|
pace: paceStateSchema,
|
|
5222
|
-
number:
|
|
5223
|
-
allowedNow:
|
|
5877
|
+
number: z33.number().int().min(1).max(PACE_AGENTS_MAX),
|
|
5878
|
+
allowedNow: z33.number().int().nonnegative(),
|
|
5224
5879
|
windowKind: paceWindowKindSchema.optional(),
|
|
5225
5880
|
until: utcIso8601.optional(),
|
|
5226
5881
|
suggestion: paceSuggestionSchema.optional()
|
|
5227
5882
|
}).strict(),
|
|
5228
|
-
|
|
5883
|
+
z33.object({ outcome: z33.literal("not-paced"), reason: z33.enum(PACE_NOT_PACED_REASONS) }).strict()
|
|
5229
5884
|
]);
|
|
5230
5885
|
function paceAgentsOf(n) {
|
|
5231
5886
|
return n === 1 ? "1 agent" : `${String(n)} agents`;
|
|
@@ -5310,15 +5965,15 @@ function paceAllowedNowOf(k) {
|
|
|
5310
5965
|
var PACE_AGENTS_TOO_MANY = `Run at most ${String(PACE_AGENTS_MAX)} agents at once.`;
|
|
5311
5966
|
|
|
5312
5967
|
// ../events/dist/autonomy.js
|
|
5313
|
-
import { z as
|
|
5968
|
+
import { z as z34 } from "zod";
|
|
5314
5969
|
var AUTONOMY_CHANGED_IN = ["console", "chat"];
|
|
5315
|
-
var autonomyChangedInSchema =
|
|
5316
|
-
var autonomySettingsSchema =
|
|
5317
|
-
on:
|
|
5970
|
+
var autonomyChangedInSchema = z34.enum(AUTONOMY_CHANGED_IN);
|
|
5971
|
+
var autonomySettingsSchema = z34.object({
|
|
5972
|
+
on: z34.boolean(),
|
|
5318
5973
|
// whether builds run on their own
|
|
5319
|
-
mayPublish:
|
|
5974
|
+
mayPublish: z34.boolean(),
|
|
5320
5975
|
// whether builds may publish or release without waiting
|
|
5321
|
-
maySpend:
|
|
5976
|
+
maySpend: z34.boolean(),
|
|
5322
5977
|
// whether builds may spend money or sign up without waiting
|
|
5323
5978
|
changedAt: utcIso8601.nullable(),
|
|
5324
5979
|
// when the setting last changed; null if never
|
|
@@ -5328,37 +5983,37 @@ var autonomySettingsSchema = z33.object({
|
|
|
5328
5983
|
message: "says when and where it changed, or neither",
|
|
5329
5984
|
path: ["changedIn"]
|
|
5330
5985
|
});
|
|
5331
|
-
var autonomyChangeRequestSchema =
|
|
5332
|
-
on:
|
|
5986
|
+
var autonomyChangeRequestSchema = z34.object({
|
|
5987
|
+
on: z34.boolean(),
|
|
5333
5988
|
// the switch
|
|
5334
|
-
mayPublish:
|
|
5989
|
+
mayPublish: z34.boolean(),
|
|
5335
5990
|
// the publish box
|
|
5336
|
-
maySpend:
|
|
5991
|
+
maySpend: z34.boolean()
|
|
5337
5992
|
// the spend box
|
|
5338
5993
|
}).strict();
|
|
5339
5994
|
var AUTONOMY_CHAT_ACTS = ["on", "off"];
|
|
5340
|
-
var autonomyChatActSchema =
|
|
5341
|
-
var autonomyChatRequestSchema =
|
|
5995
|
+
var autonomyChatActSchema = z34.enum(AUTONOMY_CHAT_ACTS);
|
|
5996
|
+
var autonomyChatRequestSchema = z34.object({
|
|
5342
5997
|
act: autonomyChatActSchema
|
|
5343
5998
|
// what the owner typed
|
|
5344
5999
|
}).strict();
|
|
5345
|
-
var autonomyChatResultSchema =
|
|
5346
|
-
|
|
5347
|
-
outcome:
|
|
5348
|
-
on:
|
|
6000
|
+
var autonomyChatResultSchema = z34.discriminatedUnion("outcome", [
|
|
6001
|
+
z34.object({
|
|
6002
|
+
outcome: z34.literal("recorded"),
|
|
6003
|
+
on: z34.boolean()
|
|
5349
6004
|
// whether builds now run on their own
|
|
5350
6005
|
}).strict(),
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
6006
|
+
z34.object({ outcome: z34.literal("not-owner") }).strict(),
|
|
6007
|
+
z34.object({ outcome: z34.literal("no-owner") }).strict(),
|
|
6008
|
+
z34.object({ outcome: z34.literal("not-offered") }).strict(),
|
|
6009
|
+
z34.object({ outcome: z34.literal("unavailable") }).strict()
|
|
5355
6010
|
]);
|
|
5356
|
-
var autonomyStateSchema =
|
|
5357
|
-
on:
|
|
6011
|
+
var autonomyStateSchema = z34.object({
|
|
6012
|
+
on: z34.boolean(),
|
|
5358
6013
|
// whether builds run on their own
|
|
5359
|
-
mayPublish:
|
|
6014
|
+
mayPublish: z34.boolean(),
|
|
5360
6015
|
// whether builds may publish or release without waiting
|
|
5361
|
-
maySpend:
|
|
6016
|
+
maySpend: z34.boolean()
|
|
5362
6017
|
// whether builds may spend money or sign up without waiting
|
|
5363
6018
|
}).strict();
|
|
5364
6019
|
var autonomyStateFileSchema = autonomyStateSchema.extend({
|
|
@@ -5367,8 +6022,8 @@ var autonomyStateFileSchema = autonomyStateSchema.extend({
|
|
|
5367
6022
|
}).strict();
|
|
5368
6023
|
var AUTONOMY_STATE_FILE_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
5369
6024
|
var AUTONOMY_ACTIVITY_KINDS = ["went-ahead", "waiting", "approved", "closed", "waiting-on-owner"];
|
|
5370
|
-
var autonomyActivityKindSchema =
|
|
5371
|
-
var autonomyActivityRowSchema =
|
|
6025
|
+
var autonomyActivityKindSchema = z34.enum(AUTONOMY_ACTIVITY_KINDS);
|
|
6026
|
+
var autonomyActivityRowSchema = z34.object({
|
|
5372
6027
|
kind: autonomyActivityKindSchema,
|
|
5373
6028
|
// what happened
|
|
5374
6029
|
phase: scopePhaseSchema,
|
|
@@ -5381,6 +6036,8 @@ var autonomyActivityRowSchema = z33.object({
|
|
|
5381
6036
|
// whether the agent says it can be undone
|
|
5382
6037
|
how: statementText(STATEMENT_MAX).optional(),
|
|
5383
6038
|
// how to undo it, when it can be
|
|
6039
|
+
wait: ownerWaitShortIdSchema.optional(),
|
|
6040
|
+
// the short id the owner types to answer a waiting decision
|
|
5384
6041
|
at: utcIso8601
|
|
5385
6042
|
// when it happened
|
|
5386
6043
|
}).strict().superRefine((row, ctx) => {
|
|
@@ -5407,6 +6064,9 @@ var autonomyActivityRowSchema = z33.object({
|
|
|
5407
6064
|
});
|
|
5408
6065
|
}
|
|
5409
6066
|
}
|
|
6067
|
+
if (row.kind !== "waiting" && row.wait !== void 0) {
|
|
6068
|
+
ctx.addIssue({ code: "custom", message: "only a waiting decision is answered by typing", path: ["wait"] });
|
|
6069
|
+
}
|
|
5410
6070
|
if (row.kind === "waiting" && (row.undoable !== void 0 || row.how !== void 0)) {
|
|
5411
6071
|
ctx.addIssue({ code: "custom", message: "a waiting decision has not been made yet", path: ["undoable"] });
|
|
5412
6072
|
}
|
|
@@ -5414,11 +6074,11 @@ var autonomyActivityRowSchema = z33.object({
|
|
|
5414
6074
|
ctx.addIssue({ code: "custom", message: "says how to undo it exactly when it can be undone", path: ["how"] });
|
|
5415
6075
|
}
|
|
5416
6076
|
});
|
|
5417
|
-
var autonomyActivitySchema =
|
|
5418
|
-
rows:
|
|
6077
|
+
var autonomyActivitySchema = z34.object({
|
|
6078
|
+
rows: z34.array(autonomyActivityRowSchema)
|
|
5419
6079
|
// newest first
|
|
5420
6080
|
}).strict();
|
|
5421
|
-
var savedIdSchema =
|
|
6081
|
+
var savedIdSchema = z34.string().min(1).max(64).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "must be lower-case letters, digits and single hyphens");
|
|
5422
6082
|
var SAVED_REF_PREFIX = "refs/halfcycle/saved/";
|
|
5423
6083
|
function savedRefOf(id) {
|
|
5424
6084
|
return `${SAVED_REF_PREFIX}${id}`;
|
|
@@ -5457,33 +6117,33 @@ function isGitFolderSegment(segment) {
|
|
|
5457
6117
|
return t === ".git" || /^git~\d+$/.test(t);
|
|
5458
6118
|
}
|
|
5459
6119
|
var SAVED_PATH_KEPT_IN = ["commit", "copy"];
|
|
5460
|
-
var savedPathSchema =
|
|
5461
|
-
path:
|
|
6120
|
+
var savedPathSchema = z34.object({
|
|
6121
|
+
path: z34.string().max(4096).refine(isProjectRelativePath, { message: "must be a path inside the project" }),
|
|
5462
6122
|
// the file, relative to the project
|
|
5463
|
-
keptIn:
|
|
6123
|
+
keptIn: z34.enum(SAVED_PATH_KEPT_IN)
|
|
5464
6124
|
// in the save's commit, or copied beside this file
|
|
5465
6125
|
}).strict();
|
|
5466
|
-
var savedCommitSchema =
|
|
5467
|
-
var savedBranchSchema =
|
|
5468
|
-
name:
|
|
6126
|
+
var savedCommitSchema = z34.string().regex(/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/, "must be a full commit hash");
|
|
6127
|
+
var savedBranchSchema = z34.object({
|
|
6128
|
+
name: z34.string().min(1).max(255).refine((n) => !hasControlCharacter(n) && !n.startsWith("-") && !n.includes("..") && n !== "HEAD", {
|
|
5469
6129
|
message: "must be a branch name"
|
|
5470
6130
|
}),
|
|
5471
6131
|
// the branch's name
|
|
5472
6132
|
commit: savedCommitSchema
|
|
5473
6133
|
// the commit it pointed at
|
|
5474
6134
|
}).strict();
|
|
5475
|
-
var savedSchema =
|
|
6135
|
+
var savedSchema = z34.object({
|
|
5476
6136
|
id: savedIdSchema,
|
|
5477
6137
|
// what restore is typed with
|
|
5478
6138
|
savedAt: utcIso8601,
|
|
5479
6139
|
// when the copy was made
|
|
5480
|
-
ref:
|
|
6140
|
+
ref: z34.string().min(1),
|
|
5481
6141
|
// where the saved commit is kept
|
|
5482
|
-
what:
|
|
6142
|
+
what: z34.string().min(1).max(2e3),
|
|
5483
6143
|
// what the action would have changed, in words
|
|
5484
|
-
paths:
|
|
6144
|
+
paths: z34.array(savedPathSchema),
|
|
5485
6145
|
// every saved file
|
|
5486
|
-
branches:
|
|
6146
|
+
branches: z34.array(savedBranchSchema)
|
|
5487
6147
|
// every saved line of work
|
|
5488
6148
|
}).strict().refine((s) => s.ref === savedRefOf(s.id), { message: "must be the ref of this save", path: ["ref"] });
|
|
5489
6149
|
function autonomyA13(accepted, carried) {
|
|
@@ -5493,8 +6153,9 @@ function autonomyA14(count, parts, acceptLine) {
|
|
|
5493
6153
|
const typed = acceptLine === null ? 'say "review what was built" and type the lines it gives.' : `say "review what was built", then type ${acceptLine} to accept them with those parts, or type the reject line it gives for a task's tests.`;
|
|
5494
6154
|
return `The phase waits for you: ${String(count)} tests the agents said pass were not run by Halfcycle, so ${namesOf(parts)} wait for you too. Only you can accept or reject those tests. In your own coding agent's session, ` + typed;
|
|
5495
6155
|
}
|
|
5496
|
-
function autonomyA15(count) {
|
|
5497
|
-
|
|
6156
|
+
function autonomyA15(count, acceptLine) {
|
|
6157
|
+
const typed = acceptLine === null ? 'say "review what was built" and type the lines it gives.' : `type ${acceptLine} to accept them and close the phase, or say "review what was built" to decide one differently.`;
|
|
6158
|
+
return `The phase waits for you: ${String(count)} parts were built without an approved scope or outside the plan, and only you can decide them. In your own coding agent's session, ${typed}`;
|
|
5498
6159
|
}
|
|
5499
6160
|
function namesOf(names) {
|
|
5500
6161
|
if (names.length <= 1)
|
|
@@ -7545,12 +8206,12 @@ function findNotShared(targetRepo, manifest, collidedPaths) {
|
|
|
7545
8206
|
if (ignored === null)
|
|
7546
8207
|
return null;
|
|
7547
8208
|
const sorted = [...ignored].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
7548
|
-
const
|
|
8209
|
+
const named2 = sorted.find((entry) => entry.path === SETTINGS_REL) ?? sorted[0];
|
|
7549
8210
|
return {
|
|
7550
|
-
path:
|
|
7551
|
-
pattern:
|
|
7552
|
-
source:
|
|
7553
|
-
line:
|
|
8211
|
+
path: named2.path,
|
|
8212
|
+
pattern: named2.pattern,
|
|
8213
|
+
source: named2.source,
|
|
8214
|
+
line: named2.line,
|
|
7554
8215
|
others: sorted.length - 1
|
|
7555
8216
|
};
|
|
7556
8217
|
}
|
|
@@ -10403,8 +11064,8 @@ function renderBlock(content, layout, environment) {
|
|
|
10403
11064
|
function renderLine(content, environment) {
|
|
10404
11065
|
const unicode = decideUnicode(environment);
|
|
10405
11066
|
const dot = unicode ? UNICODE_GLYPHS.dot : ASCII_GLYPHS.dot;
|
|
10406
|
-
const
|
|
10407
|
-
const raw = `${
|
|
11067
|
+
const named2 = content.version === "" ? "Halfcycle" : `Halfcycle v${content.version}`;
|
|
11068
|
+
const raw = `${named2} ${dot} ${content.url}`;
|
|
10408
11069
|
return unicode ? raw : toAscii(raw);
|
|
10409
11070
|
}
|
|
10410
11071
|
function renderBanner(content, environment = {}) {
|
|
@@ -11930,10 +12591,10 @@ function leftForYouLines(left, p) {
|
|
|
11930
12591
|
return lines;
|
|
11931
12592
|
}
|
|
11932
12593
|
function shownBy(steps) {
|
|
11933
|
-
const
|
|
11934
|
-
if (
|
|
12594
|
+
const named2 = steps.filter((s) => s.name !== void 0).length;
|
|
12595
|
+
if (named2 === 0)
|
|
11935
12596
|
return "Each is shown by the method's own label for it:";
|
|
11936
|
-
if (
|
|
12597
|
+
if (named2 === steps.length)
|
|
11937
12598
|
return "Each is shown by the method's own name for it:";
|
|
11938
12599
|
return "Each is shown by the method's own name for it, or by its label where the record holds no name:";
|
|
11939
12600
|
}
|
|
@@ -12658,7 +13319,7 @@ function isMissingRoute(method, status, body) {
|
|
|
12658
13319
|
import { join as join17 } from "node:path";
|
|
12659
13320
|
|
|
12660
13321
|
// dist/cli-contract.js
|
|
12661
|
-
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"];
|
|
13322
|
+
var CLI_VERBS = ["install", "check-drift", "build-record", "open-phase", "close-phase", "system", "draw", "plan", "review", "check-tasks", "check-report", "ready", "uninstall", "change", "pace", "prove", "preview", "doctor", "restore"];
|
|
12662
13323
|
var CONTRACTS = [
|
|
12663
13324
|
{ verb: "install", required: [], conditional: [] },
|
|
12664
13325
|
{ verb: "check-drift", required: [], conditional: [] },
|
|
@@ -12668,12 +13329,24 @@ var CONTRACTS = [
|
|
|
12668
13329
|
{ verb: "plan", required: [], conditional: [] },
|
|
12669
13330
|
{ verb: "review", required: [], conditional: [] },
|
|
12670
13331
|
{ verb: "check-tasks", required: [], conditional: [] },
|
|
13332
|
+
{ verb: "ready", required: [], conditional: [] },
|
|
12671
13333
|
{ verb: "uninstall", required: [], conditional: [] },
|
|
12672
13334
|
{ verb: "pace", required: [], conditional: [] },
|
|
12673
13335
|
{ verb: "prove", required: [], conditional: [] },
|
|
12674
13336
|
{ verb: "preview", required: [], conditional: [] },
|
|
12675
13337
|
{ verb: "doctor", required: [], conditional: [] },
|
|
12676
13338
|
{ verb: "restore", required: [], conditional: [] },
|
|
13339
|
+
{
|
|
13340
|
+
verb: "check-report",
|
|
13341
|
+
required: [
|
|
13342
|
+
{
|
|
13343
|
+
flag: "--head",
|
|
13344
|
+
takesValue: true,
|
|
13345
|
+
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."
|
|
13346
|
+
}
|
|
13347
|
+
],
|
|
13348
|
+
conditional: []
|
|
13349
|
+
},
|
|
12677
13350
|
{
|
|
12678
13351
|
verb: "open-phase",
|
|
12679
13352
|
required: [
|
|
@@ -13400,16 +14073,16 @@ function parseChangeArgs(rest) {
|
|
|
13400
14073
|
return notAChangeId("up", change);
|
|
13401
14074
|
return { ok: true, args: { form: "up", to, target: { kind: "change", sortingId: change }, ...where } };
|
|
13402
14075
|
}
|
|
13403
|
-
const
|
|
13404
|
-
if (!isValidPhaseIdentity(
|
|
13405
|
-
return { ok: false, line: `halfcycle change up: '${
|
|
14076
|
+
const named2 = phase ?? "";
|
|
14077
|
+
if (!isValidPhaseIdentity(named2)) {
|
|
14078
|
+
return { ok: false, line: `halfcycle change up: '${named2}' cannot be a phase name. Nothing was sent.
|
|
13406
14079
|
`, exitCode: 2 };
|
|
13407
14080
|
}
|
|
13408
14081
|
if (to === "mini") {
|
|
13409
14082
|
return { ok: false, line: `halfcycle change up: ${CHANGE_PATH_WORDS["only-up"]} Nothing was sent.
|
|
13410
14083
|
`, exitCode: 1 };
|
|
13411
14084
|
}
|
|
13412
|
-
return { ok: true, args: { form: "up", to, target: { kind: "phase", phase:
|
|
14085
|
+
return { ok: true, args: { form: "up", to, target: { kind: "phase", phase: named2 }, ...where } };
|
|
13413
14086
|
}
|
|
13414
14087
|
var OBSERVE_CHANGE_UNREACHABLE = { outcome: "not-sent", reason: "unreachable" };
|
|
13415
14088
|
function readObserveChangeLine(stdout) {
|
|
@@ -13882,11 +14555,11 @@ function readAndCheckPlan(repoRoot, phase, description, verb) {
|
|
|
13882
14555
|
return { ok: false, line: `${p} ${fileRel} is not valid JSON (${why}). Fix it, then run this again. Nothing was sent.
|
|
13883
14556
|
` };
|
|
13884
14557
|
}
|
|
13885
|
-
const
|
|
13886
|
-
if (isValidPhaseIdentity(
|
|
14558
|
+
const named2 = typeof raw === "object" && raw !== null ? raw["phase"] : void 0;
|
|
14559
|
+
if (isValidPhaseIdentity(named2) && renderPhaseSegment(named2) !== renderPhaseSegment(phase)) {
|
|
13887
14560
|
return {
|
|
13888
14561
|
ok: false,
|
|
13889
|
-
line: `${p} ${fileRel} is the plan for phase "${String(
|
|
14562
|
+
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.
|
|
13890
14563
|
`
|
|
13891
14564
|
};
|
|
13892
14565
|
}
|
|
@@ -14189,20 +14862,14 @@ var CHANGE_WORDS = {
|
|
|
14189
14862
|
remove: "planned to be removed",
|
|
14190
14863
|
"not-in-plan": "not in the plan"
|
|
14191
14864
|
};
|
|
14192
|
-
function awaitingAnswer(part) {
|
|
14193
|
-
return part.questions.some((q) => q.agentAnswer === void 0);
|
|
14194
|
-
}
|
|
14195
|
-
function sentBackInRound(part, round) {
|
|
14196
|
-
return part.sentBack.some((s) => s.round === round);
|
|
14197
|
-
}
|
|
14198
14865
|
function partStatus(part, round) {
|
|
14199
14866
|
const settled = settledStatus(part);
|
|
14200
14867
|
if (settled !== void 0) {
|
|
14201
14868
|
return part.givenIn === "autonomy" ? `${settled}, ${DECIDED_BY_AUTONOMY_SENTENCE}` : settled;
|
|
14202
14869
|
}
|
|
14203
|
-
if (
|
|
14870
|
+
if (reviewPartAwaitingAnswer(part))
|
|
14204
14871
|
return "Waiting for the agent's answer";
|
|
14205
|
-
if (
|
|
14872
|
+
if (reviewPartSentBackIn(part, round))
|
|
14206
14873
|
return "Sent back, waiting for the next round";
|
|
14207
14874
|
return "Waits for your decision";
|
|
14208
14875
|
}
|
|
@@ -14377,22 +15044,7 @@ var AN3_LINE = "halfcycle review: the answer must be one paragraph of at most 1,
|
|
|
14377
15044
|
var AN4_LINE = "halfcycle review: the answer looks like it holds a password or key, so it was not sent. Write it again without it.";
|
|
14378
15045
|
var AN5_LINE = "halfcycle review: the answer was not accepted. Reword it in plain words and run this again.";
|
|
14379
15046
|
function shownReviewEntry(view, shownAt) {
|
|
14380
|
-
|
|
14381
|
-
part: p.part,
|
|
14382
|
-
name: p.name,
|
|
14383
|
-
built: p.built,
|
|
14384
|
-
decidable: p.decidable,
|
|
14385
|
-
awaitingAnswer: awaitingAnswer(p),
|
|
14386
|
-
sentBack: sentBackInRound(p, view.round)
|
|
14387
|
-
}));
|
|
14388
|
-
return {
|
|
14389
|
-
phase: view.phase,
|
|
14390
|
-
number: view.number,
|
|
14391
|
-
round: view.round,
|
|
14392
|
-
reviewVersion: view.reviewVersion,
|
|
14393
|
-
shownAt,
|
|
14394
|
-
parts
|
|
14395
|
-
};
|
|
15047
|
+
return shownReviewEntryOf(view, shownAt);
|
|
14396
15048
|
}
|
|
14397
15049
|
function writeShownReview(engagementId, entry, home) {
|
|
14398
15050
|
const written = updateShownRecord(engagementId, (current) => ({
|
|
@@ -14683,10 +15335,10 @@ async function openPhaseReview(credential, phase, request, observe, repoRoot, se
|
|
|
14683
15335
|
}
|
|
14684
15336
|
};
|
|
14685
15337
|
const parsed = openReviewRequestSchema.safeParse(body);
|
|
14686
|
-
const
|
|
14687
|
-
if (!parsed.success || !
|
|
15338
|
+
const named2 = phaseNameSchema.safeParse(phase);
|
|
15339
|
+
if (!parsed.success || !named2.success) {
|
|
14688
15340
|
const issues = [
|
|
14689
|
-
...
|
|
15341
|
+
...named2.success ? [] : named2.error.issues.map((i) => `the phase name: ${i.message}`),
|
|
14690
15342
|
...parsed.success ? [] : parsed.error.issues.map((i) => `${describeIssuePath(i.path)}: ${i.message}`)
|
|
14691
15343
|
];
|
|
14692
15344
|
throw new CloseRequestInvalid(`that close is not one this platform accepts, so nothing was sent: ${issues.join("; ")}.`);
|
|
@@ -14864,7 +15516,7 @@ function closeStepReport(phase, attempt, engagementId) {
|
|
|
14864
15516
|
exitCode = 0;
|
|
14865
15517
|
break;
|
|
14866
15518
|
case "autonomy-waits":
|
|
14867
|
-
stdout.push(answer.reason === "unchecked-claims" ? autonomyA14(answer.count, answer.parts.map((p) => p.name), typedAcceptLine(phase)) : autonomyA15(answer.count));
|
|
15519
|
+
stdout.push(answer.reason === "unchecked-claims" ? autonomyA14(answer.count, answer.parts.map((p) => p.name), typedAcceptLine(phase)) : autonomyA15(answer.count, typedAcceptLine(phase)));
|
|
14868
15520
|
exitCode = 0;
|
|
14869
15521
|
break;
|
|
14870
15522
|
default:
|
|
@@ -15569,6 +16221,7 @@ function parseCheckTasksArgs(argv) {
|
|
|
15569
16221
|
return { ok: false };
|
|
15570
16222
|
return { ok: true, phase: positionals[0], withTaskList, repoRoot };
|
|
15571
16223
|
}
|
|
16224
|
+
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.`;
|
|
15572
16225
|
function issuePlace(rel, path) {
|
|
15573
16226
|
return path.length === 0 ? rel : `${rel}.${path.map(String).join(".")}`;
|
|
15574
16227
|
}
|
|
@@ -15625,7 +16278,42 @@ function readSystemParts(repoRoot) {
|
|
|
15625
16278
|
` + problemLines(file.value, checked.problems)
|
|
15626
16279
|
};
|
|
15627
16280
|
}
|
|
15628
|
-
return { kind: "ok", partIds: checked.description.parts.map((part) => part.id) };
|
|
16281
|
+
return { kind: "ok", partIds: checked.description.parts.map((part) => part.id), description: checked.description };
|
|
16282
|
+
}
|
|
16283
|
+
function readPhasePlanParts(repoRoot, phase, description) {
|
|
16284
|
+
if (!isValidPhaseIdentity(phase))
|
|
16285
|
+
return void 0;
|
|
16286
|
+
const file = planFileRel(phase);
|
|
16287
|
+
let raw;
|
|
16288
|
+
try {
|
|
16289
|
+
raw = JSON.parse(readFileSync18(join21(repoRoot, file), "utf-8"));
|
|
16290
|
+
} catch {
|
|
16291
|
+
return void 0;
|
|
16292
|
+
}
|
|
16293
|
+
const checked = checkPhasePlan(raw, description);
|
|
16294
|
+
if (!checked.ok || renderPhaseSegment(checked.plan.phase) !== renderPhaseSegment(phase))
|
|
16295
|
+
return void 0;
|
|
16296
|
+
return { file, partIds: checked.plan.changes.map((change) => change.part) };
|
|
16297
|
+
}
|
|
16298
|
+
var SPEC_FOLDER = LAYER_ARTEFACT_PATHS.l4[0];
|
|
16299
|
+
function readPlanSpecFiles(repoRoot, plan) {
|
|
16300
|
+
const entries = [];
|
|
16301
|
+
for (const slug of planSpecNamesToRead(plan)) {
|
|
16302
|
+
const rel = `${SPEC_FOLDER}/${slug}.md`;
|
|
16303
|
+
let text;
|
|
16304
|
+
try {
|
|
16305
|
+
text = readFileSync18(join21(repoRoot, rel), "utf-8");
|
|
16306
|
+
} catch (err) {
|
|
16307
|
+
const code = err.code;
|
|
16308
|
+
if (code === "ENOENT" || code === "ENOTDIR") {
|
|
16309
|
+
entries.push({ slug, criteria: null, boundary: null });
|
|
16310
|
+
continue;
|
|
16311
|
+
}
|
|
16312
|
+
return { kind: "bad", line: `${rel} could not be read. Check that it is a file you can open, then run this again.` };
|
|
16313
|
+
}
|
|
16314
|
+
entries.push({ slug, criteria: readSpecCriteria(text), boundary: readSpecBoundary(text) });
|
|
16315
|
+
}
|
|
16316
|
+
return { kind: "ok", specFiles: { folder: SPEC_FOLDER, entries } };
|
|
15629
16317
|
}
|
|
15630
16318
|
function runCheckTasks(repoRoot, phase, withTaskList) {
|
|
15631
16319
|
const p = "halfcycle check-tasks:";
|
|
@@ -15633,7 +16321,7 @@ function runCheckTasks(repoRoot, phase, withTaskList) {
|
|
|
15633
16321
|
if (planRead.kind === "missing") {
|
|
15634
16322
|
return {
|
|
15635
16323
|
stdout: "",
|
|
15636
|
-
stderr: `${p}
|
|
16324
|
+
stderr: `${p} ${MISSING_PLAN_LINE}
|
|
15637
16325
|
`,
|
|
15638
16326
|
exitCode: 2
|
|
15639
16327
|
};
|
|
@@ -15656,16 +16344,207 @@ function runCheckTasks(repoRoot, phase, withTaskList) {
|
|
|
15656
16344
|
if (system.kind === "bad") {
|
|
15657
16345
|
return { stdout: "", stderr: `${p} ${system.line}`, exitCode: 2 };
|
|
15658
16346
|
}
|
|
16347
|
+
const specs = readPlanSpecFiles(repoRoot, plan);
|
|
16348
|
+
if (specs.kind === "bad") {
|
|
16349
|
+
return { stdout: "", stderr: `${p} ${specs.line}
|
|
16350
|
+
`, exitCode: 2 };
|
|
16351
|
+
}
|
|
16352
|
+
const phasePlan = system.kind === "ok" ? readPhasePlanParts(repoRoot, phase, system.description) : void 0;
|
|
15659
16353
|
const result = checkTaskPlan(plan, {
|
|
15660
16354
|
phase,
|
|
15661
16355
|
taskList,
|
|
15662
|
-
|
|
16356
|
+
specFiles: specs.specFiles,
|
|
16357
|
+
...system.kind === "ok" ? { system: { file: SYSTEM_DESCRIPTION_REL, partIds: system.partIds } } : {},
|
|
16358
|
+
...phasePlan !== void 0 ? { phasePlan } : {}
|
|
15663
16359
|
});
|
|
15664
16360
|
const stdout = result.lines.map((line) => `${line.text}
|
|
15665
16361
|
`).join("");
|
|
15666
16362
|
return { stdout, stderr: "", exitCode: result.refused ? 1 : 0 };
|
|
15667
16363
|
}
|
|
15668
16364
|
|
|
16365
|
+
// dist/check-report.js
|
|
16366
|
+
import { readFileSync as readFileSync19 } from "node:fs";
|
|
16367
|
+
var CHECK_REPORT_USAGE = `${HALFCYCLE_COMMAND} check-report <file | -> --head <sha>`;
|
|
16368
|
+
function parseCheckReportArgs(argv) {
|
|
16369
|
+
const positionals = [];
|
|
16370
|
+
let head;
|
|
16371
|
+
for (let i = 0; i < argv.length; i++) {
|
|
16372
|
+
const arg = argv[i];
|
|
16373
|
+
if (arg === "--head") {
|
|
16374
|
+
const value = argv[i + 1];
|
|
16375
|
+
if (head !== void 0 || value === void 0 || value.trim() === "" || value.startsWith("-"))
|
|
16376
|
+
return { ok: false };
|
|
16377
|
+
head = value.trim();
|
|
16378
|
+
i++;
|
|
16379
|
+
continue;
|
|
16380
|
+
}
|
|
16381
|
+
if (arg !== "-" && arg.startsWith("-"))
|
|
16382
|
+
return { ok: false };
|
|
16383
|
+
positionals.push(arg);
|
|
16384
|
+
}
|
|
16385
|
+
if (positionals.length !== 1 || head === void 0)
|
|
16386
|
+
return { ok: false };
|
|
16387
|
+
return { ok: true, source: positionals[0], head };
|
|
16388
|
+
}
|
|
16389
|
+
function runCheckReport(source, head, deps = {}) {
|
|
16390
|
+
const p = "halfcycle check-report:";
|
|
16391
|
+
let body;
|
|
16392
|
+
try {
|
|
16393
|
+
body = source === "-" ? (deps.readStdin ?? (() => readFileSync19(0, "utf-8")))() : readFileSync19(source, "utf-8");
|
|
16394
|
+
} catch (err) {
|
|
16395
|
+
const code = err.code;
|
|
16396
|
+
const what = source === "-" ? "The report could not be read from standard input" : `${source} could not be read`;
|
|
16397
|
+
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.`;
|
|
16398
|
+
return { stdout: "", stderr: `${line}
|
|
16399
|
+
`, exitCode: 2 };
|
|
16400
|
+
}
|
|
16401
|
+
const result = checkWorkerReport(body, { head, statuses: WORKER_REPORT_STATUSES });
|
|
16402
|
+
if (result.problems.length === 0) {
|
|
16403
|
+
return { stdout: `${WORKER_REPORT_LINES.clear()}
|
|
16404
|
+
`, stderr: "", exitCode: 0 };
|
|
16405
|
+
}
|
|
16406
|
+
return { stdout: result.problems.map((problem) => `${problem.line}
|
|
16407
|
+
`).join(""), stderr: "", exitCode: 1 };
|
|
16408
|
+
}
|
|
16409
|
+
|
|
16410
|
+
// dist/ready.js
|
|
16411
|
+
var READY_USAGE = `${HALFCYCLE_COMMAND} ready <phase> [--cap <n>] [--repo <root>]`;
|
|
16412
|
+
var STATES_TEXT = buildTaskStateSchema.options.join(", ");
|
|
16413
|
+
var READY_LINES = {
|
|
16414
|
+
start: (id, title) => `Ready to start: ${named(id, title)}`,
|
|
16415
|
+
pastCap: (id, title) => `Ready, but past the number allowed now: ${named(id, title)}`,
|
|
16416
|
+
person: (id, title) => `Waiting on a person: ${named(id, title)}`,
|
|
16417
|
+
noCap: () => "No number was given, so every task that can start is listed.",
|
|
16418
|
+
nothing: () => "Nothing can start now.",
|
|
16419
|
+
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.`,
|
|
16420
|
+
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.`,
|
|
16421
|
+
noState: (id) => `"${id}" in ${TASK_LIST_FILE} does not give its state as one of: ${STATES_TEXT}.`
|
|
16422
|
+
};
|
|
16423
|
+
function named(id, title) {
|
|
16424
|
+
return title === void 0 ? id : `${id}, ${title}`;
|
|
16425
|
+
}
|
|
16426
|
+
function parseReadyArgs(argv) {
|
|
16427
|
+
const positionals = [];
|
|
16428
|
+
let repoRoot;
|
|
16429
|
+
let cap;
|
|
16430
|
+
for (let i = 0; i < argv.length; i++) {
|
|
16431
|
+
const arg = argv[i];
|
|
16432
|
+
if (arg === "--repo") {
|
|
16433
|
+
const value = argv[i + 1];
|
|
16434
|
+
if (repoRoot !== void 0 || value === void 0 || value === "" || value.startsWith("-"))
|
|
16435
|
+
return { ok: false };
|
|
16436
|
+
repoRoot = value;
|
|
16437
|
+
i++;
|
|
16438
|
+
continue;
|
|
16439
|
+
}
|
|
16440
|
+
if (arg === "--cap") {
|
|
16441
|
+
const value = argv[i + 1];
|
|
16442
|
+
if (cap !== void 0 || value === void 0 || !/^[0-9]+$/.test(value))
|
|
16443
|
+
return { ok: false };
|
|
16444
|
+
const n = Number(value);
|
|
16445
|
+
if (!Number.isSafeInteger(n))
|
|
16446
|
+
return { ok: false };
|
|
16447
|
+
cap = n;
|
|
16448
|
+
i++;
|
|
16449
|
+
continue;
|
|
16450
|
+
}
|
|
16451
|
+
if (arg.startsWith("-"))
|
|
16452
|
+
return { ok: false };
|
|
16453
|
+
positionals.push(arg);
|
|
16454
|
+
}
|
|
16455
|
+
if (positionals.length !== 1)
|
|
16456
|
+
return { ok: false };
|
|
16457
|
+
return { ok: true, phase: positionals[0], cap, repoRoot };
|
|
16458
|
+
}
|
|
16459
|
+
var PROGRESS = {
|
|
16460
|
+
pending: "pending",
|
|
16461
|
+
completed: "completed",
|
|
16462
|
+
"in-flight": "other",
|
|
16463
|
+
failed: "other",
|
|
16464
|
+
blocked: "other"
|
|
16465
|
+
};
|
|
16466
|
+
function readyRowsFromFiles(plan, taskList) {
|
|
16467
|
+
const listed = /* @__PURE__ */ new Map();
|
|
16468
|
+
for (const entry of taskList.tasks) {
|
|
16469
|
+
if (!listed.has(entry.id))
|
|
16470
|
+
listed.set(entry.id, entry);
|
|
16471
|
+
}
|
|
16472
|
+
const rows = [];
|
|
16473
|
+
const lines = [];
|
|
16474
|
+
plan.tasks.forEach((task, place2) => {
|
|
16475
|
+
const entry = listed.get(task.id);
|
|
16476
|
+
const state = buildTaskStateSchema.safeParse(entry?.state);
|
|
16477
|
+
if (entry === void 0 || !state.success) {
|
|
16478
|
+
lines.push(READY_LINES.noState(task.id));
|
|
16479
|
+
return;
|
|
16480
|
+
}
|
|
16481
|
+
const title = typeof entry.title === "string" && entry.title !== "" ? entry.title : void 0;
|
|
16482
|
+
const minutes = typeof entry.budgetMinutes === "number" ? entry.budgetMinutes : void 0;
|
|
16483
|
+
rows.push({
|
|
16484
|
+
id: task.id,
|
|
16485
|
+
dependsOn: task.dependsOn,
|
|
16486
|
+
progress: PROGRESS[state.data],
|
|
16487
|
+
person: task.heldBy === "person" || entry.agent === HUMAN_AGENT,
|
|
16488
|
+
...minutes === void 0 ? {} : { minutes },
|
|
16489
|
+
tieRank: place2,
|
|
16490
|
+
title
|
|
16491
|
+
});
|
|
16492
|
+
});
|
|
16493
|
+
return lines.length > 0 ? { ok: false, lines } : { ok: true, rows };
|
|
16494
|
+
}
|
|
16495
|
+
function failed2(stderr) {
|
|
16496
|
+
return { stdout: "", stderr, exitCode: 2 };
|
|
16497
|
+
}
|
|
16498
|
+
function runReady(repoRoot, phase, cap) {
|
|
16499
|
+
const p = "halfcycle ready:";
|
|
16500
|
+
const planRead = readJsonFile(repoRoot, TASK_PLAN_FILE, (raw) => taskPlanSchema.safeParse(raw));
|
|
16501
|
+
if (planRead.kind === "missing")
|
|
16502
|
+
return failed2(`${p} ${MISSING_PLAN_LINE}
|
|
16503
|
+
`);
|
|
16504
|
+
if (planRead.kind === "bad")
|
|
16505
|
+
return failed2(`${p} ${planRead.line}
|
|
16506
|
+
`);
|
|
16507
|
+
const plan = planRead.value;
|
|
16508
|
+
const listRead = readJsonFile(repoRoot, TASK_LIST_FILE, (raw) => taskListFileSchema.safeParse(raw));
|
|
16509
|
+
if (listRead.kind === "bad")
|
|
16510
|
+
return failed2(`${p} ${listRead.line}
|
|
16511
|
+
`);
|
|
16512
|
+
const list2 = listRead.kind === "ok" ? listRead.value : null;
|
|
16513
|
+
const specs = readPlanSpecFiles(repoRoot, plan);
|
|
16514
|
+
if (specs.kind === "bad")
|
|
16515
|
+
return failed2(`${p} ${specs.line}
|
|
16516
|
+
`);
|
|
16517
|
+
const check = checkTaskPlan(plan, { phase, taskList: { file: list2 }, specFiles: specs.specFiles });
|
|
16518
|
+
if (check.refused) {
|
|
16519
|
+
return { stdout: check.lines.map((line) => `${line.text}
|
|
16520
|
+
`).join(""), stderr: "", exitCode: 1 };
|
|
16521
|
+
}
|
|
16522
|
+
if (list2 === null)
|
|
16523
|
+
return failed2(`${READY_LINES.noTaskList()}
|
|
16524
|
+
`);
|
|
16525
|
+
if (Array.isArray(list2) || list2.phase !== phase)
|
|
16526
|
+
return failed2(`${READY_LINES.otherPhase(phase)}
|
|
16527
|
+
`);
|
|
16528
|
+
const read = readyRowsFromFiles(plan, list2);
|
|
16529
|
+
if (!read.ok)
|
|
16530
|
+
return failed2(read.lines.map((line) => `${line}
|
|
16531
|
+
`).join(""));
|
|
16532
|
+
const titles = new Map(read.rows.map((row) => [row.id, row.title]));
|
|
16533
|
+
const set = readyTasks(read.rows);
|
|
16534
|
+
const out = [];
|
|
16535
|
+
if (cap === void 0)
|
|
16536
|
+
out.push(READY_LINES.noCap());
|
|
16537
|
+
set.ready.forEach((id, i) => {
|
|
16538
|
+
out.push(cap === void 0 || i < cap ? READY_LINES.start(id, titles.get(id)) : READY_LINES.pastCap(id, titles.get(id)));
|
|
16539
|
+
});
|
|
16540
|
+
for (const id of set.person)
|
|
16541
|
+
out.push(READY_LINES.person(id, titles.get(id)));
|
|
16542
|
+
if (set.ready.length === 0 && set.person.length === 0)
|
|
16543
|
+
out.push(READY_LINES.nothing());
|
|
16544
|
+
return { stdout: out.map((line) => `${line}
|
|
16545
|
+
`).join(""), stderr: "", exitCode: 0 };
|
|
16546
|
+
}
|
|
16547
|
+
|
|
15669
16548
|
// dist/prove.js
|
|
15670
16549
|
import { spawn as spawn2 } from "node:child_process";
|
|
15671
16550
|
var PROVE_USAGE = `${HALFCYCLE_COMMAND} prove <phase> <task> --label "<label>" --before -- <test command>
|
|
@@ -15718,7 +16597,7 @@ function runProveChild(binary, credential, repoRoot, words, deps = {}) {
|
|
|
15718
16597
|
}
|
|
15719
16598
|
|
|
15720
16599
|
// dist/uninstall.js
|
|
15721
|
-
import { lstatSync as lstatSync3, readFileSync as
|
|
16600
|
+
import { lstatSync as lstatSync3, readFileSync as readFileSync20, readdirSync, rmSync as rmSync6, rmdirSync, writeFileSync as writeFileSync8 } from "node:fs";
|
|
15722
16601
|
import { homedir as homedir5 } from "node:os";
|
|
15723
16602
|
import { dirname as dirname7, join as join22, relative as relative6, sep as sep2 } from "node:path";
|
|
15724
16603
|
var REMOVE_CREDENTIAL_FLAG = "--remove-credential";
|
|
@@ -15761,7 +16640,7 @@ function readMergedJson(targetRepo, rule) {
|
|
|
15761
16640
|
let text;
|
|
15762
16641
|
let value;
|
|
15763
16642
|
try {
|
|
15764
|
-
text =
|
|
16643
|
+
text = readFileSync20(path, "utf-8");
|
|
15765
16644
|
} catch {
|
|
15766
16645
|
text = "";
|
|
15767
16646
|
}
|
|
@@ -15786,7 +16665,7 @@ function validate(options, rules) {
|
|
|
15786
16665
|
let manifest = null;
|
|
15787
16666
|
if (lexists(manifestPath)) {
|
|
15788
16667
|
try {
|
|
15789
|
-
manifest = parseManifest(
|
|
16668
|
+
manifest = parseManifest(readFileSync20(manifestPath, "utf-8"));
|
|
15790
16669
|
} catch (err) {
|
|
15791
16670
|
const why = err instanceof UnreadableManifestError ? err.message : "it could not be read";
|
|
15792
16671
|
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.`);
|
|
@@ -15796,7 +16675,7 @@ function validate(options, rules) {
|
|
|
15796
16675
|
let pinReadable = true;
|
|
15797
16676
|
if (lexists(pinPath)) {
|
|
15798
16677
|
try {
|
|
15799
|
-
const pin = JSON.parse(
|
|
16678
|
+
const pin = JSON.parse(readFileSync20(pinPath, "utf-8"));
|
|
15800
16679
|
const id = isPlainObject3(pin) ? pin[PIN_ENGAGEMENT_ID_FIELD] : void 0;
|
|
15801
16680
|
engagementId = typeof id === "string" && id !== "" ? id : void 0;
|
|
15802
16681
|
} catch {
|
|
@@ -15863,7 +16742,7 @@ function statusLineClaimedFromTree(targetRepo, writeSet) {
|
|
|
15863
16742
|
try {
|
|
15864
16743
|
if (symlinkOnPath(targetRepo, SETTINGS_REL))
|
|
15865
16744
|
return {};
|
|
15866
|
-
const value = JSON.parse(
|
|
16745
|
+
const value = JSON.parse(readFileSync20(join22(targetRepo, SETTINGS_REL), "utf-8"));
|
|
15867
16746
|
if (!isPlainObject3(value) || !("statusLine" in value))
|
|
15868
16747
|
return {};
|
|
15869
16748
|
const hash = canonicalSha256(value["statusLine"]);
|
|
@@ -15877,7 +16756,7 @@ function noRecord(targetRepo, writeSet) {
|
|
|
15877
16756
|
const claim = (rel, header, lines) => {
|
|
15878
16757
|
let text;
|
|
15879
16758
|
try {
|
|
15880
|
-
text = symlinkOnPath(targetRepo, rel) ? "" :
|
|
16759
|
+
text = symlinkOnPath(targetRepo, rel) ? "" : readFileSync20(join22(targetRepo, rel), "utf-8");
|
|
15881
16760
|
} catch {
|
|
15882
16761
|
text = "";
|
|
15883
16762
|
}
|
|
@@ -16159,7 +17038,7 @@ function removeSelfBuildCrewRoster(targetRepo, lists, name) {
|
|
|
16159
17038
|
const abs = join22(targetRepo, rel);
|
|
16160
17039
|
let bytes;
|
|
16161
17040
|
try {
|
|
16162
|
-
bytes =
|
|
17041
|
+
bytes = readFileSync20(abs);
|
|
16163
17042
|
} catch {
|
|
16164
17043
|
return;
|
|
16165
17044
|
}
|
|
@@ -16186,7 +17065,7 @@ function removeSelfBuildCrewProfile(targetRepo, rel, text, lists, name) {
|
|
|
16186
17065
|
const abs = join22(targetRepo, rel);
|
|
16187
17066
|
let bytes;
|
|
16188
17067
|
try {
|
|
16189
|
-
bytes =
|
|
17068
|
+
bytes = readFileSync20(abs);
|
|
16190
17069
|
} catch {
|
|
16191
17070
|
return;
|
|
16192
17071
|
}
|
|
@@ -16297,17 +17176,17 @@ function uninstall(options) {
|
|
|
16297
17176
|
const manifest = checked.manifest ?? noRecord(targetRepo, writeSet);
|
|
16298
17177
|
const deleteWhenEmpty = (created) => created || checked.manifest === null;
|
|
16299
17178
|
const deleteCodexWhenEmpty = (record2) => record2 === void 0 || deleteWhenEmpty(record2.created);
|
|
16300
|
-
const
|
|
17179
|
+
const named2 = /* @__PURE__ */ new Set();
|
|
16301
17180
|
const name = (list2, text, path) => {
|
|
16302
17181
|
list2.push(text);
|
|
16303
17182
|
if (path !== void 0)
|
|
16304
|
-
|
|
17183
|
+
named2.add(path);
|
|
16305
17184
|
};
|
|
16306
17185
|
const settingsEmptyGoes = deleteWhenEmpty(manifest.settings.created);
|
|
16307
17186
|
applyMergedJson(targetRepo, rules.settings, checked.settings, settingsEmptyGoes, result, name, (value) => {
|
|
16308
17187
|
const { changed, refused: refused3 } = unmergeSettings(value, manifest.settings, writeSet);
|
|
16309
|
-
for (const
|
|
16310
|
-
name(result.leftAlone,
|
|
17188
|
+
for (const note2 of refused3)
|
|
17189
|
+
name(result.leftAlone, note2);
|
|
16311
17190
|
return { changed, adopted: manifest.settings.adopted };
|
|
16312
17191
|
});
|
|
16313
17192
|
const mcpRecord = manifest.mcp ?? BEFORE_UNKNOWN_MCP;
|
|
@@ -16367,17 +17246,17 @@ function uninstall(options) {
|
|
|
16367
17246
|
removeInstalledFile(targetRepo, BUNDLE_PIN_REL, recorded.get(BUNDLE_PIN_REL), writeSet, result, name);
|
|
16368
17247
|
try {
|
|
16369
17248
|
rmSync6(join22(targetRepo, INSTALL_MANIFEST_REL), { force: true });
|
|
16370
|
-
|
|
17249
|
+
named2.add(INSTALL_MANIFEST_REL);
|
|
16371
17250
|
} catch {
|
|
16372
17251
|
name(result.failed, INSTALL_MANIFEST_REL, INSTALL_MANIFEST_REL);
|
|
16373
17252
|
}
|
|
16374
17253
|
removeEmptyDir(targetRepo, ".halfcycle", writeSet, result, name);
|
|
16375
17254
|
} else {
|
|
16376
|
-
|
|
16377
|
-
|
|
17255
|
+
named2.add(BUNDLE_PIN_REL);
|
|
17256
|
+
named2.add(INSTALL_MANIFEST_REL);
|
|
16378
17257
|
}
|
|
16379
17258
|
for (const path of filesUnder(targetRepo, ".halfcycle")) {
|
|
16380
|
-
if (!
|
|
17259
|
+
if (!named2.has(path))
|
|
16381
17260
|
name(result.leftAlone, path, path);
|
|
16382
17261
|
}
|
|
16383
17262
|
const exitCode = result.failed.length > 0 || credentialFailed || credential.kind === "no-project-id" ? 1 : 0;
|
|
@@ -16417,7 +17296,7 @@ function removeCodexConfigBlock(targetRepo, record2, deleteWhenEmpty, lists, nam
|
|
|
16417
17296
|
try {
|
|
16418
17297
|
if (!lstatSync3(path).isFile())
|
|
16419
17298
|
return;
|
|
16420
|
-
text =
|
|
17299
|
+
text = readFileSync20(path, "utf-8");
|
|
16421
17300
|
} catch {
|
|
16422
17301
|
return;
|
|
16423
17302
|
}
|
|
@@ -16466,7 +17345,7 @@ function removeInstalledFile(targetRepo, path, entry, writeSet, lists, name) {
|
|
|
16466
17345
|
if (!writeSet.ownedDirs.has(path.slice(0, path.lastIndexOf("/")))) {
|
|
16467
17346
|
let bytes;
|
|
16468
17347
|
try {
|
|
16469
|
-
bytes =
|
|
17348
|
+
bytes = readFileSync20(abs);
|
|
16470
17349
|
} catch {
|
|
16471
17350
|
name(lists.failed, path, path);
|
|
16472
17351
|
return;
|
|
@@ -16508,7 +17387,7 @@ function trimAppendedFile(targetRepo, rec, deleteWhenEmpty, rules, lists, name)
|
|
|
16508
17387
|
const path = join22(targetRepo, rel);
|
|
16509
17388
|
let before;
|
|
16510
17389
|
try {
|
|
16511
|
-
before =
|
|
17390
|
+
before = readFileSync20(path, "utf-8");
|
|
16512
17391
|
} catch {
|
|
16513
17392
|
return [];
|
|
16514
17393
|
}
|
|
@@ -16633,7 +17512,7 @@ function renderUninstallReport(result, home) {
|
|
|
16633
17512
|
}
|
|
16634
17513
|
|
|
16635
17514
|
// dist/banner-facts.js
|
|
16636
|
-
import { readFileSync as
|
|
17515
|
+
import { readFileSync as readFileSync21 } from "node:fs";
|
|
16637
17516
|
import { basename as basename4, join as join23, resolve as resolve6 } from "node:path";
|
|
16638
17517
|
var BRAND_URL = "halfcycle.ai";
|
|
16639
17518
|
var TAGLINE = [
|
|
@@ -16647,7 +17526,7 @@ var TAGLINE_NARROW = [
|
|
|
16647
17526
|
];
|
|
16648
17527
|
function bundleVersion() {
|
|
16649
17528
|
try {
|
|
16650
|
-
const pkg = JSON.parse(
|
|
17529
|
+
const pkg = JSON.parse(readFileSync21(join23(BUNDLE_ROOT, "package.json"), "utf-8"));
|
|
16651
17530
|
return typeof pkg.version === "string" ? pkg.version : void 0;
|
|
16652
17531
|
} catch {
|
|
16653
17532
|
return void 0;
|
|
@@ -16885,7 +17764,7 @@ async function runPace(repoRoot, start, deps) {
|
|
|
16885
17764
|
|
|
16886
17765
|
// dist/restore.js
|
|
16887
17766
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
16888
|
-
import { closeSync as closeSync3, fchmodSync, lstatSync as lstatSync4, mkdirSync as mkdirSync11, openSync as openSync3, readdirSync as readdirSync2, readFileSync as
|
|
17767
|
+
import { closeSync as closeSync3, fchmodSync, lstatSync as lstatSync4, mkdirSync as mkdirSync11, openSync as openSync3, readdirSync as readdirSync2, readFileSync as readFileSync22, readlinkSync, realpathSync as realpathSync6, rmSync as rmSync7, symlinkSync, writeSync as writeSync2 } from "node:fs";
|
|
16889
17768
|
import { homedir as homedir6 } from "node:os";
|
|
16890
17769
|
import { join as join24, resolve as resolve7 } from "node:path";
|
|
16891
17770
|
var RESTORE_USAGE = `${HALFCYCLE_COMMAND} restore [<id>]`;
|
|
@@ -16962,9 +17841,9 @@ function printable(text) {
|
|
|
16962
17841
|
}
|
|
16963
17842
|
function restoreOne(root, folder, refs, rawId) {
|
|
16964
17843
|
const valid = savedIdSchema.safeParse(rawId);
|
|
16965
|
-
const
|
|
17844
|
+
const named2 = valid.success ? `"${rawId}"` : "with that name";
|
|
16966
17845
|
if (!valid.success || !refs.has(rawId)) {
|
|
16967
|
-
return refused2(`No saved copy ${
|
|
17846
|
+
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}`);
|
|
16968
17847
|
}
|
|
16969
17848
|
const id = valid.data;
|
|
16970
17849
|
const saved = readSaved(folder, id);
|
|
@@ -17071,7 +17950,7 @@ function sameAs(abs, st, content) {
|
|
|
17071
17950
|
try {
|
|
17072
17951
|
if ("link" in content)
|
|
17073
17952
|
return st.isSymbolicLink() && readlinkSync(abs) === content.link;
|
|
17074
|
-
return st.isFile() &&
|
|
17953
|
+
return st.isFile() && readFileSync22(abs).equals(content.bytes);
|
|
17075
17954
|
} catch {
|
|
17076
17955
|
return false;
|
|
17077
17956
|
}
|
|
@@ -17094,7 +17973,7 @@ function restoreCopy(root, saveDir, rel, out) {
|
|
|
17094
17973
|
if (st !== null && st.isSymbolicLink())
|
|
17095
17974
|
return place(root, rel, { link: readlinkSync(src) }, out);
|
|
17096
17975
|
if (st !== null && st.isFile())
|
|
17097
|
-
return place(root, rel, { bytes:
|
|
17976
|
+
return place(root, rel, { bytes: readFileSync22(src), mode: st.mode & 511 }, out);
|
|
17098
17977
|
} catch {
|
|
17099
17978
|
}
|
|
17100
17979
|
out.left.push(`${rel}: the saved copy does not hold it`);
|
|
@@ -17192,7 +18071,7 @@ function restoreLines(root, ref, saved, out) {
|
|
|
17192
18071
|
}
|
|
17193
18072
|
function pinnedEngagement(root) {
|
|
17194
18073
|
try {
|
|
17195
|
-
const pin = JSON.parse(
|
|
18074
|
+
const pin = JSON.parse(readFileSync22(join24(root, BUNDLE_PIN_REL), "utf-8"));
|
|
17196
18075
|
return isEngagementId(pin.engagementId) ? pin.engagementId : null;
|
|
17197
18076
|
} catch {
|
|
17198
18077
|
return null;
|
|
@@ -17223,7 +18102,7 @@ function readSaved(folder, id) {
|
|
|
17223
18102
|
if (fst === null || !fst.isFile())
|
|
17224
18103
|
return null;
|
|
17225
18104
|
try {
|
|
17226
|
-
const parsed = savedSchema.safeParse(JSON.parse(
|
|
18105
|
+
const parsed = savedSchema.safeParse(JSON.parse(readFileSync22(file, "utf-8")));
|
|
17227
18106
|
return parsed.success && parsed.data.id === id ? parsed.data : null;
|
|
17228
18107
|
} catch {
|
|
17229
18108
|
return null;
|
|
@@ -17272,7 +18151,7 @@ function pruneAtRestore(root, folder, refs, now) {
|
|
|
17272
18151
|
}
|
|
17273
18152
|
function markedRepository(dir) {
|
|
17274
18153
|
try {
|
|
17275
|
-
const text =
|
|
18154
|
+
const text = readFileSync22(join24(dir, SAVED_REPOSITORY_FILE_NAME), "utf-8").trim();
|
|
17276
18155
|
return text === "" ? null : text;
|
|
17277
18156
|
} catch {
|
|
17278
18157
|
return null;
|
|
@@ -17376,6 +18255,10 @@ var USAGE = ` ${HALFCYCLE_COMMAND} [install] [target-repo] [engagement-id] [sel
|
|
|
17376
18255
|
run a task's test command on this machine, once before the change and once after, and record what it saw
|
|
17377
18256
|
${CHECK_TASKS_USAGE}
|
|
17378
18257
|
check a phase's plan in docs/build-records/ for work that would collide if it ran at once
|
|
18258
|
+
${CHECK_REPORT_USAGE}
|
|
18259
|
+
check a worker's report, saved from its pull request, against the commit it must be about
|
|
18260
|
+
${READY_USAGE}
|
|
18261
|
+
list the tasks of a phase's plan that can start now, longest chain first, up to the number given
|
|
17379
18262
|
${CHANGE_USAGE}
|
|
17380
18263
|
sort a change you are asked for into a small change, a mini phase or a full phase; move it up;
|
|
17381
18264
|
end a small change and write its record into docs/build-records/changes/
|
|
@@ -18046,6 +18929,45 @@ halfcycle build-record: until ${obtained.stampFailure.path} can be written, the
|
|
|
18046
18929
|
process.exitCode = run.exitCode;
|
|
18047
18930
|
return;
|
|
18048
18931
|
}
|
|
18932
|
+
if (cmd === "check-report") {
|
|
18933
|
+
const reportShortfalls = missingRequirements("check-report", rest);
|
|
18934
|
+
if (reportShortfalls.length > 0) {
|
|
18935
|
+
process.stderr.write(`${reportShortfalls[0].message}
|
|
18936
|
+
`);
|
|
18937
|
+
process.exitCode = 2;
|
|
18938
|
+
return;
|
|
18939
|
+
}
|
|
18940
|
+
const parsed = parseCheckReportArgs(rest);
|
|
18941
|
+
if (!parsed.ok) {
|
|
18942
|
+
process.stderr.write(`halfcycle check-report: usage: ${CHECK_REPORT_USAGE}
|
|
18943
|
+
`);
|
|
18944
|
+
process.exitCode = 2;
|
|
18945
|
+
return;
|
|
18946
|
+
}
|
|
18947
|
+
const run = runCheckReport(parsed.source, parsed.head);
|
|
18948
|
+
if (run.stdout !== "")
|
|
18949
|
+
process.stdout.write(run.stdout);
|
|
18950
|
+
if (run.stderr !== "")
|
|
18951
|
+
process.stderr.write(run.stderr);
|
|
18952
|
+
process.exitCode = run.exitCode;
|
|
18953
|
+
return;
|
|
18954
|
+
}
|
|
18955
|
+
if (cmd === "ready") {
|
|
18956
|
+
const parsed = parseReadyArgs(rest);
|
|
18957
|
+
if (!parsed.ok) {
|
|
18958
|
+
process.stderr.write(`halfcycle ready: usage: ${READY_USAGE}
|
|
18959
|
+
`);
|
|
18960
|
+
process.exitCode = 2;
|
|
18961
|
+
return;
|
|
18962
|
+
}
|
|
18963
|
+
const run = runReady(parsed.repoRoot ?? process.cwd(), parsed.phase, parsed.cap);
|
|
18964
|
+
if (run.stdout !== "")
|
|
18965
|
+
process.stdout.write(run.stdout);
|
|
18966
|
+
if (run.stderr !== "")
|
|
18967
|
+
process.stderr.write(run.stderr);
|
|
18968
|
+
process.exitCode = run.exitCode;
|
|
18969
|
+
return;
|
|
18970
|
+
}
|
|
18049
18971
|
if (cmd === "pace") {
|
|
18050
18972
|
const parsed = parsePaceArgs(rest);
|
|
18051
18973
|
if (!parsed.ok) {
|