halfcycle 0.12.0-rc.5 → 0.12.0-rc.7
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 +9 -3
- package/bin/pre-tool-use-filter.sh +1 -0
- package/dist/check-criteria.d.ts +2 -2
- package/dist/check-tasks.d.ts.map +1 -1
- package/dist/cli.js +29 -115
- package/dist/cli.js.map +2 -2
- package/dist/close-phase.d.ts +17 -6
- package/dist/close-phase.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/project-name.d.ts +6 -0
- package/dist/project-name.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "halfcycle",
|
|
3
|
-
"version": "0.12.0-rc.
|
|
3
|
+
"version": "0.12.0-rc.7",
|
|
4
4
|
"description": "Halfcycle Method bundle — resolution-stub slash commands, remote method-delivery registration, and governance-hook wiring for a Halfcycle engagement repo. It ships NO worker role files: a project's roles are authored from that project's own recorded decisions at orchestration kickoff (FX-2, W3-F-27).",
|
|
5
5
|
"commands": [
|
|
6
6
|
{
|
package/bin/bin.bundle.mjs
CHANGED
|
@@ -17367,7 +17367,8 @@ var timeSummarySchema = external_exports.object({
|
|
|
17367
17367
|
totals: timeTotalsSchema,
|
|
17368
17368
|
usage: timeUsageTotalsSchema
|
|
17369
17369
|
}).strict(),
|
|
17370
|
-
share: timeSummaryShareSchema.optional()
|
|
17370
|
+
share: timeSummaryShareSchema.optional(),
|
|
17371
|
+
workMinutes: timeCountSchema.nullable().optional()
|
|
17371
17372
|
}).strict().superRefine((v, ctx) => {
|
|
17372
17373
|
if (v.closed !== (v.closedAt !== null)) {
|
|
17373
17374
|
ctx.addIssue({ code: "custom", path: ["closedAt"], message: "must be set exactly when the phase is closed" });
|
|
@@ -17482,6 +17483,7 @@ var METHOD_FILE_PATTERNS = [
|
|
|
17482
17483
|
"CLAUDE.md",
|
|
17483
17484
|
"AGENTS.md",
|
|
17484
17485
|
".claude/context",
|
|
17486
|
+
"docs/context",
|
|
17485
17487
|
".claude/commands",
|
|
17486
17488
|
".claude/hooks",
|
|
17487
17489
|
".claude/settings.json",
|
|
@@ -17541,7 +17543,7 @@ var PROJECT_RECORD_PATTERNS = [
|
|
|
17541
17543
|
{ pattern: "docs/build-records/task-plan.json", name: () => "the build's plan (task-plan.json)", shrinkAsked: false },
|
|
17542
17544
|
{ pattern: "docs/build-records", name: (file2) => `a closed phase's Build Record (${file2})` },
|
|
17543
17545
|
{
|
|
17544
|
-
pattern: "
|
|
17546
|
+
pattern: "docs/context/tasks/*",
|
|
17545
17547
|
name: (file2) => `a task's notes for this phase (${file2})`,
|
|
17546
17548
|
shrinkAsked: false,
|
|
17547
17549
|
deleteAsked: false
|
|
@@ -20780,6 +20782,7 @@ function pathsDifferingFromHead(repoRoot, timeoutMs = PROVE_GIT_TIMEOUT_MS) {
|
|
|
20780
20782
|
return result;
|
|
20781
20783
|
return parseStatusPathsZ(result.value);
|
|
20782
20784
|
}
|
|
20785
|
+
var GENERATED_CACHE = /(^|\/)(__pycache__|\.pytest_cache|\.mypy_cache|\.ruff_cache|node_modules)\/|\.py[co]$/;
|
|
20783
20786
|
function parseStatusPathsZ(output) {
|
|
20784
20787
|
if (output === "")
|
|
20785
20788
|
return { ok: true, value: [] };
|
|
@@ -20790,7 +20793,10 @@ function parseStatusPathsZ(output) {
|
|
|
20790
20793
|
if (record2.length < 4 || record2.charAt(2) !== " " || /[RC]/.test(record2.slice(0, 2))) {
|
|
20791
20794
|
return { ok: false, message: "git gave an unreadable list of changed files" };
|
|
20792
20795
|
}
|
|
20793
|
-
|
|
20796
|
+
const path = record2.slice(3);
|
|
20797
|
+
if (record2.startsWith("??") && GENERATED_CACHE.test(path))
|
|
20798
|
+
continue;
|
|
20799
|
+
paths.add(path);
|
|
20794
20800
|
}
|
|
20795
20801
|
return { ok: true, value: [...paths] };
|
|
20796
20802
|
}
|
|
@@ -369,6 +369,7 @@ halfcycle_pt_records() {
|
|
|
369
369
|
halfcycle_pt_holds 'build-tasks.json' $'b?(\x01)u?(\x01)i?(\x01)l?(\x01)d?(\x01)-?(\x01)t?(\x01)a?(\x01)s?(\x01)k?(\x01)s?(\x01).?(\x01)j?(\x01)s?(\x01)o?(\x01)n' && return 1
|
|
370
370
|
halfcycle_pt_holds 'task-plan.json' $'t?(\x01)a?(\x01)s?(\x01)k?(\x01)-?(\x01)p?(\x01)l?(\x01)a?(\x01)n?(\x01).?(\x01)j?(\x01)s?(\x01)o?(\x01)n' && return 1
|
|
371
371
|
halfcycle_pt_holds 'build-records' $'b?(\x01)u?(\x01)i?(\x01)l?(\x01)d?(\x01)-?(\x01)r?(\x01)e?(\x01)c?(\x01)o?(\x01)r?(\x01)d?(\x01)s' && return 1
|
|
372
|
+
halfcycle_pt_holds 'context' $'c?(\x01)o?(\x01)n?(\x01)t?(\x01)e?(\x01)x?(\x01)t' && return 1
|
|
372
373
|
halfcycle_pt_holds '.claude' $'.?(\x01)c?(\x01)l?(\x01)a?(\x01)u?(\x01)d?(\x01)e' && return 1
|
|
373
374
|
return 0
|
|
374
375
|
}
|
package/dist/check-criteria.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `npx halfcycle@latest check-criteria [<record file>] [--repo <root>]`
|
|
3
|
-
* (`whole-phase-criteria-check` AC4, M1–M7
|
|
3
|
+
* (`whole-phase-criteria-check` AC4, M1–M7).
|
|
4
4
|
*
|
|
5
5
|
* OFFLINE, ALWAYS. This verb reads only files on the person's own machine — the
|
|
6
6
|
* plan file `docs/build-records/task-plan.json`, each spec file the plan lists in
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* ONE HOME FOR EACH THING IT READS. The plan is parsed with `taskPlanSchema` through
|
|
12
12
|
* `check-tasks.ts`'s `readJsonFile`, as `check-tasks` and `ready` read it; the spec
|
|
13
13
|
* files are opened only by `readPlanSpecFiles`; the record only by
|
|
14
|
-
* `readCriteriaRecordFile`, through the wire's own schema for it. The rules M1–
|
|
14
|
+
* `readCriteriaRecordFile`, through the wire's own schema for it. The rules M1–M7, the
|
|
15
15
|
* clear line and every line's words are `checkCriteriaRecord` (`@halfcycle/core`),
|
|
16
16
|
* which is pure. This module's whole job is reading the files, handing them over,
|
|
17
17
|
* and turning the answer into an exit code: 0 clean, 1 any line, 2 a usage error or a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check-tasks.d.ts","sourceRoot":"","sources":["../src/check-tasks.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"check-tasks.d.ts","sourceRoot":"","sources":["../src/check-tasks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2FG;AAIH,OAAO,EAWL,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACxB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAWL,KAAK,mBAAmB,EAExB,KAAK,aAAa,EAElB,KAAK,QAAQ,EACb,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACvB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAI1E,kFAAkF;AAClF,eAAO,MAAM,iBAAiB,QAA2E,CAAC;AAE1G;;;;GAIG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GAChF;IAAE,EAAE,EAAE,KAAK,CAAA;CAAE,CAAC;AAElB,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,cAAc,CAuB3E;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB,iIAAiH,CAAC;AAEhJ;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,GAAG,oBAAoB,CAAC,MAAM,CAAC,CAGrE;AAED,qHAAqH;AACrH,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAOzG,qGAAqG;AACrG,KAAK,SAAS,CAAC,CAAC,IACZ;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,GAC1B;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE;QAAE,MAAM,EAAE,SAAS;YAAE,IAAI,EAAE,SAAS,WAAW,EAAE,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE,EAAE,CAAA;KAAE,CAAA;CAAE,CAAC;AAExG;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CA0BjH;AAwCD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,iBAAiB,GAC7B,iBAAiB,GAAG,SAAS,CA6B/B;AAQD;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,aAAa,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzG;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,iBAAiB,CAkBrF;AAED;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,mBAAmB,GAAG,SAAS,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAElC;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,GAAG,GAAE,MAA6B,GACjC,QAAQ,CAAC,kBAAkB,CAAC,CAU9B;AAMD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,kBAAkB,CASvF;AAED,sCAAsC;AACtC,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,aAAa,CAuDnH;AAiBD,sEAAsE;AACtE,eAAO,MAAM,qBAAqB,uEAAuE,CAAC;AAE1G,mEAAmE;AACnE,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAEzC,kFAAkF;AAClF,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,cAAc,CAAC;IAC/D,QAAQ,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,iIAAiI;AACjI,MAAM,MAAM,QAAQ,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC;AAElF,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,QAAQ,CAAC,CA8BhG;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,OAAO,EACrB,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,aAAa,CAAC,CAKxB"}
|
package/dist/cli.js
CHANGED
|
@@ -790,8 +790,6 @@ var taskPlanLineText = {
|
|
|
790
790
|
V5: (c, m, b) => `"${c}" checks the finished phase, but ${m} also holds "${b}", which builds part of it. Give "${c}" to a crew member who holds no build task in this phase.`,
|
|
791
791
|
V6Walk: (c, n, ids, w) => `${CRITERIA_RECORD_FILE} says "${c}" turned ${n} criteria into fix tasks: ${ids.join(", ")}. "${w}" must wait for a new check that waits for each of them: add a task of kind "check" with "criteriaCheck": true that waits for ${ids.join(", ")}, and add it to the dependsOn of "${w}".`,
|
|
792
792
|
V6Missing: (x) => `${CRITERIA_RECORD_FILE} names "${x}" as a fix task, and the plan has no build task "${x}". Add it, citing the criterion it fixes.`,
|
|
793
|
-
E1: (crit) => `${crit} is an edge case, and no task writes its test before it is built. Add a build task, held by a crew member who builds none of ${crit}, that writes the test and names ${crit} in its criteria, and make each task that builds ${crit} wait for it.`,
|
|
794
|
-
E2: (t, crit, m, b) => `"${t}" writes the test for ${crit}, an edge case, but ${m} also holds "${b}", which builds it. Give "${t}" to a crew member who builds none of ${crit}.`,
|
|
795
793
|
J1: (a) => `"${a}" says "cheapFirst", but it is not a build task. Remove "cheapFirst".`,
|
|
796
794
|
J2: (a, m) => `"${a}" says "cheapFirst", but ${m} is not on the standard tier. Only a standard-tier task can start on the cheaper tier. Remove "cheapFirst" or give "${a}" to a standard-tier crew member.`,
|
|
797
795
|
P: () => "The plan is clear: no two tasks that could run at once change the same thing, and every check is in place."
|
|
@@ -1148,10 +1146,6 @@ function liveCriterionNumbers(criteria) {
|
|
|
1148
1146
|
const live = new Set(criteria.filter((c) => !c.retired).map((c) => c.number));
|
|
1149
1147
|
return distinct(criteria.map((c) => c.number)).filter((n) => live.has(n));
|
|
1150
1148
|
}
|
|
1151
|
-
function liveEdgeCriterionNumbers(criteria) {
|
|
1152
|
-
const edge = new Set(criteria.filter((c) => !c.retired && c.kind === "edge").map((c) => c.number));
|
|
1153
|
-
return liveCriterionNumbers(criteria).filter((n) => edge.has(n));
|
|
1154
|
-
}
|
|
1155
1149
|
function specLines(plan, specFiles) {
|
|
1156
1150
|
const folder2 = specFiles.folder;
|
|
1157
1151
|
if (plan.specs === TASK_PLAN_SPECS_NONE)
|
|
@@ -1534,57 +1528,6 @@ function criteriaCheckLines(tasks, before, record2) {
|
|
|
1534
1528
|
}
|
|
1535
1529
|
return lines;
|
|
1536
1530
|
}
|
|
1537
|
-
function edgeCriteriaAt(plan, specFiles, before) {
|
|
1538
|
-
const criteriaOf = specCriteriaBySlug(specFiles);
|
|
1539
|
-
const out = [];
|
|
1540
|
-
for (const slug of planSpecNamesToRead(plan)) {
|
|
1541
|
-
const criteria = criteriaOf.get(slug) ?? null;
|
|
1542
|
-
if (criteria === null)
|
|
1543
|
-
continue;
|
|
1544
|
-
for (const n of liveEdgeCriterionNumbers(criteria)) {
|
|
1545
|
-
const criterion = `${slug} AC${n}`;
|
|
1546
|
-
const builders = [];
|
|
1547
|
-
plan.tasks.forEach((task, i) => {
|
|
1548
|
-
if (task.kind === "build" && Array.isArray(task.criteria) && task.criteria.includes(criterion)) {
|
|
1549
|
-
builders.push(i);
|
|
1550
|
-
}
|
|
1551
|
-
});
|
|
1552
|
-
const testTask = builders.length < 2 ? void 0 : builders.find((t) => builders.every((b) => b === t || hasBit(before[b], t)));
|
|
1553
|
-
out.push({ criterion, slug, builders, testTask: testTask ?? null });
|
|
1554
|
-
}
|
|
1555
|
-
}
|
|
1556
|
-
return out;
|
|
1557
|
-
}
|
|
1558
|
-
function edgeCriterionTests(plan, specFiles) {
|
|
1559
|
-
const { before } = waitsFor(plan.tasks);
|
|
1560
|
-
return edgeCriteriaAt(plan, specFiles, before).map((edge) => ({
|
|
1561
|
-
criterion: edge.criterion,
|
|
1562
|
-
slug: edge.slug,
|
|
1563
|
-
builders: edge.builders.map((i) => plan.tasks[i].id),
|
|
1564
|
-
testTask: edge.testTask === null ? null : plan.tasks[edge.testTask].id
|
|
1565
|
-
}));
|
|
1566
|
-
}
|
|
1567
|
-
function edgeCaseLines(plan, specFiles, before) {
|
|
1568
|
-
const tasks = plan.tasks;
|
|
1569
|
-
const lines = [];
|
|
1570
|
-
for (const edge of edgeCriteriaAt(plan, specFiles, before)) {
|
|
1571
|
-
if (edge.builders.length === 0)
|
|
1572
|
-
continue;
|
|
1573
|
-
if (edge.testTask === null) {
|
|
1574
|
-
lines.push(refusal("E1", taskPlanLineText.E1(edge.criterion)));
|
|
1575
|
-
continue;
|
|
1576
|
-
}
|
|
1577
|
-
const t = edge.testTask;
|
|
1578
|
-
const holder = tasks[t].agent;
|
|
1579
|
-
if (holder === void 0)
|
|
1580
|
-
continue;
|
|
1581
|
-
const b = edge.builders.find((p) => p !== t && tasks[p].agent === holder);
|
|
1582
|
-
if (b !== void 0) {
|
|
1583
|
-
lines.push(refusal("E2", taskPlanLineText.E2(tasks[t].id, edge.criterion, holder, tasks[b].id)));
|
|
1584
|
-
}
|
|
1585
|
-
}
|
|
1586
|
-
return lines;
|
|
1587
|
-
}
|
|
1588
1531
|
function phasePlanUnusableWhy(unusable, asked) {
|
|
1589
1532
|
switch (unusable.kind) {
|
|
1590
1533
|
case "unreadable":
|
|
@@ -1732,7 +1675,6 @@ function checkTaskPlan(plan, request) {
|
|
|
1732
1675
|
lines.push(...ownershipNotes(plan, request.specFiles));
|
|
1733
1676
|
lines.push(...placementLines(tasks, before));
|
|
1734
1677
|
lines.push(...criteriaCheckLines(tasks, before, request.criteriaRecord));
|
|
1735
|
-
lines.push(...edgeCaseLines(plan, request.specFiles, before));
|
|
1736
1678
|
const refused3 = lines.some((line) => line.refuses);
|
|
1737
1679
|
if (!refused3)
|
|
1738
1680
|
lines.push({ code: "P", text: taskPlanLineText.P(), refuses: false });
|
|
@@ -1748,10 +1690,6 @@ var criteriaRecordLineText = {
|
|
|
1748
1690
|
M5: (file, x, folder2) => `${file} has a row for "${x}", which is not a live criterion of this phase's features in ${folder2}. Remove it, or correct it.`,
|
|
1749
1691
|
M6: (crit) => `${crit} has more than one row. Keep one.`,
|
|
1750
1692
|
M7: (crit, x) => `${crit} is fixed by "${x}", but the plan has no build task "${x}" that names ${crit} in its criteria. Add the task, or name it there.`,
|
|
1751
|
-
M8Unmarked: (crit, folder2, slug) => `${crit} is not marked an edge case in ${folder2}/${slug}.md, so its row does not carry "edge".`,
|
|
1752
|
-
M8Marked: (crit, folder2, slug) => `${crit} is marked an edge case in ${folder2}/${slug}.md, so its row carries "edge": true.`,
|
|
1753
|
-
M9Evidence: (crit, t) => `${crit} is an edge case, so it is met only by the test written in "${t}", run on a task that builds it. Give its evidence as a test with "writtenIn": "${t}" and the task that built it.`,
|
|
1754
|
-
M9NoTestTask: (crit) => `${crit} is an edge case, and the plan has no task that writes its test, so it cannot be counted as met. Run check-tasks and add that task.`,
|
|
1755
1693
|
P: (met, fix) => `The record covers every criterion of this phase's features: ${met} met, ${fix} to fix.`
|
|
1756
1694
|
};
|
|
1757
1695
|
function criteriaRecordMismatch(record2, plan) {
|
|
@@ -1773,45 +1711,9 @@ function liveCriteria(specFiles) {
|
|
|
1773
1711
|
}
|
|
1774
1712
|
return out;
|
|
1775
1713
|
}
|
|
1776
|
-
function liveEdgeCriteria(specFiles) {
|
|
1777
|
-
const out = /* @__PURE__ */ new Set();
|
|
1778
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1779
|
-
for (const entry of specFiles.entries) {
|
|
1780
|
-
if (entry.criteria === null || seen.has(entry.slug))
|
|
1781
|
-
continue;
|
|
1782
|
-
seen.add(entry.slug);
|
|
1783
|
-
for (const n of liveEdgeCriterionNumbers(entry.criteria))
|
|
1784
|
-
out.add(`${entry.slug} AC${n}`);
|
|
1785
|
-
}
|
|
1786
|
-
return out;
|
|
1787
|
-
}
|
|
1788
|
-
function slugOf(criterion) {
|
|
1789
|
-
return criterion.slice(0, criterion.lastIndexOf(" AC"));
|
|
1790
|
-
}
|
|
1791
1714
|
function refusal2(code, text) {
|
|
1792
1715
|
return { code, text, refuses: true };
|
|
1793
1716
|
}
|
|
1794
|
-
function edgeRowLines(row, isEdge, testTask, plan, folder2) {
|
|
1795
|
-
const crit = row.criterion;
|
|
1796
|
-
const lines = [];
|
|
1797
|
-
const marked = row.edge === true;
|
|
1798
|
-
if (marked && !isEdge)
|
|
1799
|
-
lines.push(refusal2("M8", criteriaRecordLineText.M8Unmarked(crit, folder2, slugOf(crit))));
|
|
1800
|
-
if (!marked && isEdge)
|
|
1801
|
-
lines.push(refusal2("M8", criteriaRecordLineText.M8Marked(crit, folder2, slugOf(crit))));
|
|
1802
|
-
if (!isEdge || row.status !== "met")
|
|
1803
|
-
return lines;
|
|
1804
|
-
if (testTask === null) {
|
|
1805
|
-
lines.push(refusal2("M9", criteriaRecordLineText.M9NoTestTask(crit)));
|
|
1806
|
-
return lines;
|
|
1807
|
-
}
|
|
1808
|
-
const evidence = row.evidence;
|
|
1809
|
-
const builtBy = (id) => id !== testTask && plan.tasks.some((task) => task.id === id && task.kind === "build" && Array.isArray(task.criteria) && task.criteria.includes(crit));
|
|
1810
|
-
const held = evidence.kind === "test" && evidence.writtenIn === testTask && builtBy(evidence.task);
|
|
1811
|
-
if (!held)
|
|
1812
|
-
lines.push(refusal2("M9", criteriaRecordLineText.M9Evidence(crit, testTask)));
|
|
1813
|
-
return lines;
|
|
1814
|
-
}
|
|
1815
1717
|
function checkCriteriaRecord(record2, plan, request) {
|
|
1816
1718
|
const { file, specFiles } = request;
|
|
1817
1719
|
let met = 0;
|
|
@@ -1846,12 +1748,6 @@ function checkCriteriaRecord(record2, plan, request) {
|
|
|
1846
1748
|
}
|
|
1847
1749
|
const live = liveCriteria(specFiles);
|
|
1848
1750
|
const liveSet = new Set(live);
|
|
1849
|
-
const edges = liveEdgeCriteria(specFiles);
|
|
1850
|
-
const testTaskOf = /* @__PURE__ */ new Map();
|
|
1851
|
-
for (const edge of edgeCriterionTests(plan, specFiles)) {
|
|
1852
|
-
if (!testTaskOf.has(edge.criterion))
|
|
1853
|
-
testTaskOf.set(edge.criterion, edge.testTask);
|
|
1854
|
-
}
|
|
1855
1751
|
const named2 = /* @__PURE__ */ new Set();
|
|
1856
1752
|
const told = /* @__PURE__ */ new Set();
|
|
1857
1753
|
for (const row of record2.rows) {
|
|
@@ -1869,7 +1765,6 @@ function checkCriteriaRecord(record2, plan, request) {
|
|
|
1869
1765
|
told.add(crit);
|
|
1870
1766
|
}
|
|
1871
1767
|
named2.add(crit);
|
|
1872
|
-
lines.push(...edgeRowLines(row, edges.has(crit), testTaskOf.get(crit) ?? null, plan, specFiles.folder));
|
|
1873
1768
|
}
|
|
1874
1769
|
for (const row of record2.rows) {
|
|
1875
1770
|
if (row.status !== "fix" || !liveSet.has(row.criterion))
|
|
@@ -4133,7 +4028,8 @@ var timeSummarySchema = z11.object({
|
|
|
4133
4028
|
totals: timeTotalsSchema,
|
|
4134
4029
|
usage: timeUsageTotalsSchema
|
|
4135
4030
|
}).strict(),
|
|
4136
|
-
share: timeSummaryShareSchema.optional()
|
|
4031
|
+
share: timeSummaryShareSchema.optional(),
|
|
4032
|
+
workMinutes: timeCountSchema.nullable().optional()
|
|
4137
4033
|
}).strict().superRefine((v, ctx) => {
|
|
4138
4034
|
if (v.closed !== (v.closedAt !== null)) {
|
|
4139
4035
|
ctx.addIssue({ code: "custom", path: ["closedAt"], message: "must be set exactly when the phase is closed" });
|
|
@@ -6780,7 +6676,7 @@ function aboutShareOf(percent) {
|
|
|
6780
6676
|
}
|
|
6781
6677
|
var PLAN_USAGE_CAVEAT = "These figures are estimates. Many things affect usage, and some of them are outside Halfcycle's control.";
|
|
6782
6678
|
var SMALL_PROJECT_WORK_START = "A small project like this is usually about ";
|
|
6783
|
-
var SMALL_PROJECT_WORK_NOTE = "
|
|
6679
|
+
var SMALL_PROJECT_WORK_NOTE = "Once your build plan is written, its own figure replaces this one: the time it sets for each task, added up.";
|
|
6784
6680
|
function sizeAnswerPrintedLinesOf(usage5) {
|
|
6785
6681
|
const out = [];
|
|
6786
6682
|
for (const line of usage5) {
|
|
@@ -7824,11 +7720,11 @@ function expectedActualLineOf(expected, actual) {
|
|
|
7824
7720
|
const took = `The phase took ${timeDurationLabelOf(actual.seconds)} and ${tokenCountWordsOf(actual.tokens)} tokens.`;
|
|
7825
7721
|
if (expected.minutes === null)
|
|
7826
7722
|
return `Expected about ${tokenCountWordsOf(expected.tokens)} tokens. ${took}`;
|
|
7827
|
-
return `
|
|
7723
|
+
return `The plan expected about ${timeDurationLabelOf(expected.minutes * 60)} of agent work and about ${tokenCountWordsOf(expected.tokens)} tokens. ${took}`;
|
|
7828
7724
|
}
|
|
7829
7725
|
function expectedActualTimeLineOf(expectedMinutes, elapsedSeconds) {
|
|
7830
7726
|
const took = `The phase took ${timeDurationLabelOf(elapsedSeconds)}.`;
|
|
7831
|
-
return expectedMinutes === null ? took : `
|
|
7727
|
+
return expectedMinutes === null ? took : `The plan expected about ${timeDurationLabelOf(expectedMinutes * 60)} of agent work. ${took}`;
|
|
7832
7728
|
}
|
|
7833
7729
|
function joinedOf(names) {
|
|
7834
7730
|
if (names.length <= 1)
|
|
@@ -13269,9 +13165,13 @@ function repositoryNameFromRemote(remote) {
|
|
|
13269
13165
|
const name = segment.replace(/\.git$/i, "").trim();
|
|
13270
13166
|
return name.length > 0 ? name : void 0;
|
|
13271
13167
|
}
|
|
13168
|
+
function isLocalRemote(remote) {
|
|
13169
|
+
const r = remote.trim();
|
|
13170
|
+
return /^(\/|\.{1,2}[\\/]|~|file:|[A-Za-z]:[\\/]|\\\\)/.test(r);
|
|
13171
|
+
}
|
|
13272
13172
|
function deriveProjectName(targetRepoRoot) {
|
|
13273
13173
|
const remote = readRemote(targetRepoRoot);
|
|
13274
|
-
const fromRemote = remote ? repositoryNameFromRemote(remote) : void 0;
|
|
13174
|
+
const fromRemote = remote && !isLocalRemote(remote) ? repositoryNameFromRemote(remote) : void 0;
|
|
13275
13175
|
const candidate = fromRemote ?? basename(resolve2(targetRepoRoot));
|
|
13276
13176
|
const name = candidate.trim();
|
|
13277
13177
|
if (EMPTY_SEGMENTS.has(name))
|
|
@@ -17912,7 +17812,7 @@ function assertNever3(value) {
|
|
|
17912
17812
|
throw new Error(`unexpected answer: ${JSON.stringify(value)}`);
|
|
17913
17813
|
}
|
|
17914
17814
|
function readTimeTotalsUrl(credential, phase) {
|
|
17915
|
-
return `${credential.serviceUrl}/engagements/${encodeURIComponent(credential.engagementId)}/phases/${encodeURIComponent(phase)}/time?with=share`;
|
|
17815
|
+
return `${credential.serviceUrl}/engagements/${encodeURIComponent(credential.engagementId)}/phases/${encodeURIComponent(phase)}/time?with=share&work=true`;
|
|
17916
17816
|
}
|
|
17917
17817
|
async function readCloseTimeSummary(credential, phase) {
|
|
17918
17818
|
let res;
|
|
@@ -18107,15 +18007,19 @@ function readPhaseModels(engagementId, phase, openedAt, home) {
|
|
|
18107
18007
|
function usageTokensOf(usage5) {
|
|
18108
18008
|
return usage5.inputTokens + usage5.outputTokens + usage5.cacheWriteTokens + usage5.cacheReadTokens;
|
|
18109
18009
|
}
|
|
18010
|
+
function planWorkMinutesOf(summary) {
|
|
18011
|
+
return summary.workMinutes ?? null;
|
|
18012
|
+
}
|
|
18110
18013
|
function tierFigureLines(work, expected, summary, models, details = false) {
|
|
18111
18014
|
const lines = [];
|
|
18112
18015
|
const figures = work === null ? null : tierFiguresOf(work);
|
|
18113
18016
|
if (figures !== null)
|
|
18114
18017
|
lines.push(tierCountsLineOf(figures));
|
|
18115
|
-
|
|
18018
|
+
const planMinutes = summary === null ? null : planWorkMinutesOf(summary);
|
|
18019
|
+
if (expected === null && planMinutes === null)
|
|
18116
18020
|
lines.push(NO_EXPECTED_LINE);
|
|
18117
18021
|
else if (summary !== null) {
|
|
18118
|
-
lines.push(details ? expectedActualLineOf(expected, { seconds: summary.elapsedSeconds, tokens: usageTokensOf(summary.usage) }) : expectedActualTimeLineOf(
|
|
18022
|
+
lines.push(details && expected !== null ? expectedActualLineOf({ tokens: expected.tokens, minutes: planMinutes }, { seconds: summary.elapsedSeconds, tokens: usageTokensOf(summary.usage) }) : expectedActualTimeLineOf(planMinutes, summary.elapsedSeconds));
|
|
18119
18023
|
}
|
|
18120
18024
|
if (figures !== null && models !== null) {
|
|
18121
18025
|
for (const task of figures.hadCheaperAttempt) {
|
|
@@ -18848,8 +18752,18 @@ function readPlanSpecFiles(repoRoot, plan) {
|
|
|
18848
18752
|
return { kind: "ok", specFiles: { folder: SPEC_FOLDER, entries } };
|
|
18849
18753
|
}
|
|
18850
18754
|
function readCriteriaRecordFile(repoRoot, rel = CRITERIA_RECORD_FILE) {
|
|
18851
|
-
return readJsonFile(repoRoot, rel, (raw) =>
|
|
18755
|
+
return readJsonFile(repoRoot, rel, (raw) => {
|
|
18756
|
+
const parsed = workCriteriaRecordSchema.safeParse(raw);
|
|
18757
|
+
if (parsed.success)
|
|
18758
|
+
return parsed;
|
|
18759
|
+
const first = parsed.error.issues[0];
|
|
18760
|
+
if (first !== void 0 && (first.path[0] === "record" || first.path[0] === "subject")) {
|
|
18761
|
+
return { success: false, error: { issues: [{ path: [], message: CRITERIA_RECORD_START }] } };
|
|
18762
|
+
}
|
|
18763
|
+
return parsed;
|
|
18764
|
+
});
|
|
18852
18765
|
}
|
|
18766
|
+
var CRITERIA_RECORD_START = 'does not start the way a criteria record does. Begin it with "record": "criteria" and "subject": {"kind": "phase", "phase": "<the phase>"}, then "check" (the check task), "by" (who holds it) and "rows"';
|
|
18853
18767
|
function readCriteriaRecord(repoRoot, plan) {
|
|
18854
18768
|
const read = readCriteriaRecordFile(repoRoot);
|
|
18855
18769
|
if (read.kind === "missing")
|
|
@@ -20397,7 +20311,7 @@ function openingBannerContent(projectDir, env = process.env, probes = REAL_PROBE
|
|
|
20397
20311
|
facts,
|
|
20398
20312
|
tag: TAGLINE,
|
|
20399
20313
|
tagNarrow: TAGLINE_NARROW,
|
|
20400
|
-
url: BRAND_URL,
|
|
20314
|
+
url: probes.websiteHost(env) ?? BRAND_URL,
|
|
20401
20315
|
// `''` when even this package's own manifest could not be read. The one-line cut
|
|
20402
20316
|
// drops the whole `vX.Y.Z` clause in that case rather than printing a bare `v`.
|
|
20403
20317
|
version: version ?? ""
|