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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "halfcycle",
3
- "version": "0.12.0-rc.5",
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
  {
@@ -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: ".claude/context/tasks/*",
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
- paths.add(record2.slice(3));
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
  }
@@ -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; `edge-case-criteria` AC5, M8 and M9).
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–M9, the
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":"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"}
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 = "When your plan is written, this phase gets its own figure, counted from the time each task in it is given.";
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 `Expected ${timeDurationLabelOf(expected.minutes * 60)} and about ${tokenCountWordsOf(expected.tokens)} tokens. ${took}`;
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 : `Expected ${timeDurationLabelOf(expectedMinutes * 60)}. ${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
- if (expected === null)
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(expected.minutes, summary.elapsedSeconds));
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) => workCriteriaRecordSchema.safeParse(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 ?? ""